From 4a65c47dd8e386c243d747cbde0bc9ae95255a3a Mon Sep 17 00:00:00 2001 From: Alexei Sheplyakov Date: Wed, 28 Jan 2015 17:55:14 +0300 Subject: [PATCH] Added rabbitmq-server version 3.3.5 from rabbitmq.com The version of rabbitmq-server shipped in Ubuntu 14.04 (3.2.4) provides poor/buggy clustering/HA facilities and is not suitable for OpenStack (in HA configuration), hence the custom package. Related-Bug: #1355947 blueprint support-ubuntu-trusty Change-Id: I13375776a36f5dfbe86c5ad8800ac4a86028e748 --- debian/LICENSE.head | 5 + debian/LICENSE.tail | 516 + debian/README | 20 + debian/changelog | 325 + debian/compat | 1 + debian/control | 17 + debian/copyright | 567 ++ debian/dirs | 9 + debian/postinst | 60 + debian/postrm.in | 65 + debian/rabbitmq-script-wrapper | 44 + debian/rabbitmq-server.default | 11 + debian/rabbitmq-server.init | 187 + debian/rabbitmq-server.logrotate | 12 + debian/rabbitmq-server.ocf | 371 + debian/rules | 27 + debian/source/format | 1 + debian/watch | 4 + rabbitmq-server-3.3.5/INSTALL | 2 + rabbitmq-server-3.3.5/LICENSE | 556 + .../LICENSE-APACHE2-ExplorerCanvas | 202 + .../LICENSE-APL2-Stomp-Websocket | 202 + rabbitmq-server-3.3.5/LICENSE-Apache-Basho | 178 + rabbitmq-server-3.3.5/LICENSE-BSD-base64js | 25 + rabbitmq-server-3.3.5/LICENSE-BSD-glMatrix | 26 + rabbitmq-server-3.3.5/LICENSE-MIT-EJS10 | 23 + rabbitmq-server-3.3.5/LICENSE-MIT-Flot | 22 + rabbitmq-server-3.3.5/LICENSE-MIT-Mochi | 9 + rabbitmq-server-3.3.5/LICENSE-MIT-Sammy060 | 25 + rabbitmq-server-3.3.5/LICENSE-MIT-eldap | 21 + rabbitmq-server-3.3.5/LICENSE-MIT-jQuery164 | 21 + rabbitmq-server-3.3.5/LICENSE-MPL-RabbitMQ | 455 + rabbitmq-server-3.3.5/Makefile | 383 + rabbitmq-server-3.3.5/README | 1 + rabbitmq-server-3.3.5/calculate-relative | 45 + rabbitmq-server-3.3.5/codegen.py | 595 ++ rabbitmq-server-3.3.5/codegen/LICENSE | 6 + .../codegen/LICENSE-MPL-RabbitMQ | 455 + rabbitmq-server-3.3.5/codegen/Makefile | 8 + .../codegen/README.extensions.md | 189 + .../codegen/amqp-rabbitmq-0.8.json | 659 ++ .../codegen/amqp-rabbitmq-0.9.1.json | 473 + rabbitmq-server-3.3.5/codegen/amqp_codegen.py | 286 + .../codegen/credit_extension.json | 54 + .../codegen/demo_extension.json | 18 + rabbitmq-server-3.3.5/codegen/license_info | 4 + .../docs/examples-to-end.xsl | 93 + .../docs/html-to-website-xml.xsl | 90 + .../docs/rabbitmq-echopid.xml | 71 + .../docs/rabbitmq-env.conf.5.xml | 83 + .../docs/rabbitmq-plugins.1.xml | 182 + .../docs/rabbitmq-server.1.xml | 132 + .../docs/rabbitmq-service.xml | 218 + .../docs/rabbitmq.config.example | 567 ++ rabbitmq-server-3.3.5/docs/rabbitmqctl.1.xml | 1768 ++++ .../docs/remove-namespaces.xsl | 18 + rabbitmq-server-3.3.5/docs/usage.xsl | 74 + rabbitmq-server-3.3.5/ebin/rabbit_app.in | 77 + rabbitmq-server-3.3.5/generate_app | 16 + rabbitmq-server-3.3.5/generate_deps | 57 + rabbitmq-server-3.3.5/include/gm_specs.hrl | 28 + rabbitmq-server-3.3.5/include/rabbit.hrl | 133 + .../include/rabbit_msg_store.hrl | 25 + rabbitmq-server-3.3.5/plugins-src/Makefile | 174 + rabbitmq-server-3.3.5/plugins-src/README | 1 + .../plugins-src/all-packages.mk | 13 + rabbitmq-server-3.3.5/plugins-src/common.mk | 143 + .../plugins-src/cowboy-wrapper/.srcdist_done | 0 .../0001-R12-fake-iodata-type.patch | 40 + ...-drop-all-references-to-boolean-type.patch | 165 + ...rop-all-references-to-reference-type.patch | 55 + ...4-R12-drop-references-to-iodata-type.patch | 50 + ...-drop-references-to-Default-any-type.patch | 52 + ...er_to_list-and-lists-max-instead-of-.patch | 62 + ...R12-type-definitions-must-be-ordered.patch | 37 + .../0008-sec-websocket-protocol.patch | 16 + .../plugins-src/cowboy-wrapper/Makefile | 1 + .../plugins-src/cowboy-wrapper/README.md | 1 + .../cowboy-wrapper/cowboy-git/.done | 0 .../cowboy-wrapper/cowboy-git/.travis.yml | 7 + .../cowboy-wrapper/cowboy-git/AUTHORS | 18 + .../cowboy-wrapper/cowboy-git/CHANGELOG.md | 213 + .../cowboy-wrapper/cowboy-git/LICENSE | 13 + .../cowboy-wrapper/cowboy-git/Makefile | 36 + .../cowboy-wrapper/cowboy-git/README.md | 290 + .../cowboy-wrapper/cowboy-git/cover.spec | 1 + .../cowboy-git/doc/overview.edoc | 4 + .../cowboy-git/include/http.hrl | 55 + .../cowboy-wrapper/cowboy-git/rebar.config | 12 + .../cowboy-git/src/cowboy.app.src | 26 + .../cowboy-wrapper/cowboy-git/src/cowboy.erl | 85 + .../cowboy-git/src/cowboy_acceptor.erl | 59 + .../cowboy-git/src/cowboy_acceptors_sup.erl | 43 + .../cowboy-git/src/cowboy_app.erl | 53 + .../cowboy-git/src/cowboy_bstr.erl | 86 + .../cowboy-git/src/cowboy_clock.erl | 241 + .../cowboy-git/src/cowboy_cookies.erl | 392 + .../cowboy-git/src/cowboy_dispatcher.erl | 309 + .../cowboy-git/src/cowboy_http.erl | 974 ++ .../cowboy-git/src/cowboy_http_handler.erl | 48 + .../cowboy-git/src/cowboy_http_protocol.erl | 472 + .../cowboy-git/src/cowboy_http_req.erl | 820 ++ .../cowboy-git/src/cowboy_http_req.erl.orig | 815 ++ .../cowboy-git/src/cowboy_http_rest.erl | 905 ++ .../cowboy-git/src/cowboy_http_static.erl | 456 + .../cowboy-git/src/cowboy_http_websocket.erl | 530 + .../src/cowboy_http_websocket_handler.erl | 60 + .../cowboy-git/src/cowboy_listener.erl | 174 + .../cowboy-git/src/cowboy_listener_sup.erl | 45 + .../cowboy-git/src/cowboy_multipart.erl | 249 + .../cowboy-git/src/cowboy_protocol.erl | 61 + .../cowboy-git/src/cowboy_requests_sup.erl | 38 + .../cowboy-git/src/cowboy_ssl_transport.erl | 164 + .../cowboy-git/src/cowboy_sup.erl | 36 + .../cowboy-git/src/cowboy_tcp_transport.erl | 106 + .../cowboy-git/test/chunked_handler.erl | 17 + .../cowboy-git/test/dispatcher_prop.erl | 68 + .../cowboy-git/test/http_SUITE.erl | 613 ++ .../cowboy-git/test/http_SUITE_data/cert.pem | 14 + .../cowboy-git/test/http_SUITE_data/key.pem | 18 + .../cowboy-git/test/http_handler.erl | 19 + .../cowboy-git/test/http_handler_errors.erl | 40 + .../test/http_handler_init_shutdown.erl | 17 + .../test/http_handler_long_polling.erl | 22 + .../test/http_handler_multipart.erl | 29 + .../cowboy-git/test/http_handler_set_resp.erl | 33 + .../test/http_handler_stream_body.erl | 24 + .../cowboy-git/test/proper_SUITE.erl | 37 + .../test/rest_forbidden_resource.erl | 40 + .../cowboy-git/test/rest_simple_resource.erl | 12 + .../cowboy-git/test/websocket_handler.erl | 38 + .../test/websocket_handler_init_shutdown.erl | 30 + .../cowboy-git/test/ws_SUITE.erl | 318 + .../test/ws_timeout_hibernate_handler.erl | 29 + .../plugins-src/cowboy-wrapper/hash.mk | 1 + .../plugins-src/cowboy-wrapper/package.mk | 24 + .../plugins-src/do-package.mk | 574 ++ .../plugins-src/eldap-wrapper/.srcdist_done | 0 .../eldap-wrapper/LICENSE-MIT-eldap | 21 + .../plugins-src/eldap-wrapper/Makefile | 1 + .../eldap-wrapper/eldap-appify.patch | 14 + .../plugins-src/eldap-wrapper/eldap-git/.done | 0 .../eldap-wrapper/eldap-git/LICENSE | 21 + .../eldap-wrapper/eldap-git/Makefile | 7 + .../eldap-wrapper/eldap-git/README | 33 + .../eldap-git/doc/README.example | 44 + .../eldap-wrapper/eldap-git/doc/short-desc | 1 + .../eldap-wrapper/eldap-git/ebin/eldap.app | 10 + .../eldap-wrapper/eldap-git/include/eldap.hrl | 32 + .../eldap-wrapper/eldap-git/src/ELDAPv3.asn | 291 + .../eldap-wrapper/eldap-git/src/Makefile | 26 + .../eldap-wrapper/eldap-git/src/eldap.erl | 1078 ++ .../eldap-wrapper/eldap-git/test/README.test | 96 + .../eldap-wrapper/eldap-git/test/bill.ldif | 13 + .../eldap-git/test/bluetail.ldif | 18 + .../eldap-wrapper/eldap-git/test/crl.ldif | 5 + .../eldap-git/test/eldap_test.erl | 537 + .../eldap-wrapper/eldap-git/test/ldap.rc | 103 + .../eldap-wrapper/eldap-git/test/people.ldif | 11 + .../eldap-git/test/post_danmark.ldif | 5 + .../eldap-wrapper/eldap-git/test/server1.crl | Bin 0 -> 47075 bytes .../eldap-wrapper/eldap-git/test/slapd.conf | 41 + .../eldap-wrapper/eldap-git/test/tobbe.ldif | 6 + .../eldap-wrapper/eldap-no-ssl-seed.patch | 17 + .../plugins-src/eldap-wrapper/hash.mk | 1 + .../plugins-src/eldap-wrapper/license_info | 3 + .../plugins-src/eldap-wrapper/package.mk | 30 + .../eldap-wrapper/remove-eldap-fsm.patch | 952 ++ .../eldap-wrapper/remove-ietf-doc.patch | 3036 ++++++ .../plugins-src/generate_app | 16 + .../plugins-src/generate_deps | 61 + .../licensing/LICENSE-APACHE2-ExplorerCanvas | 202 + .../licensing/LICENSE-APL2-Stomp-Websocket | 202 + .../licensing/LICENSE-Apache-Basho | 178 + .../licensing/LICENSE-BSD-base64js | 25 + .../licensing/LICENSE-BSD-glMatrix | 26 + .../plugins-src/licensing/LICENSE-MIT-EJS10 | 23 + .../plugins-src/licensing/LICENSE-MIT-Flot | 22 + .../plugins-src/licensing/LICENSE-MIT-Mochi | 9 + .../licensing/LICENSE-MIT-Sammy060 | 25 + .../plugins-src/licensing/LICENSE-MIT-eldap | 21 + .../licensing/LICENSE-MIT-jQuery164 | 21 + .../licensing/LICENSE-MPL-RabbitMQ | 455 + .../licensing/license_info_eldap-wrapper | 3 + .../licensing/license_info_mochiweb-wrapper | 4 + .../license_info_rabbitmq-management | 17 + ...icense_info_rabbitmq-management-visualiser | 4 + .../licensing/license_info_webmachine-wrapper | 3 + .../mochiweb-wrapper/.srcdist_done | 0 .../mochiweb-wrapper/10-build-on-R12B-5.patch | 303 + .../mochiweb-wrapper/20-MAX_RECV_BODY.patch | 13 + .../30-remove-crypto-ssl-dependencies.patch | 104 + ...e-compiler-syntax_tools-dependencies.patch | 124 + .../mochiweb-wrapper/50-remove-json.patch | 1255 +++ .../mochiweb-wrapper/LICENSE-MIT-Mochi | 9 + .../plugins-src/mochiweb-wrapper/Makefile | 1 + .../plugins-src/mochiweb-wrapper/hash.mk | 1 + .../plugins-src/mochiweb-wrapper/license_info | 4 + .../mochiweb-wrapper/mochiweb-git/.done | 0 .../mochiweb-wrapper/mochiweb-git/.travis.yml | 7 + .../mochiweb-wrapper/mochiweb-git/CHANGES.md | 91 + .../mochiweb-wrapper/mochiweb-git/LICENSE | 9 + .../mochiweb-wrapper/mochiweb-git/Makefile | 29 + .../mochiweb-wrapper/mochiweb-git/README | 17 + .../mochiweb-git/examples/hmac_api/README | 206 + .../examples/hmac_api/hmac_api.hrl | 43 + .../examples/hmac_api/hmac_api_client.erl | 34 + .../examples/hmac_api/hmac_api_lib.erl | 435 + .../examples/https/https_store.erl | 146 + .../examples/https/server_cert.pem | 19 + .../examples/https/server_key.pem | 27 + .../examples/keepalive/keepalive.erl | 81 + .../mochiweb-git/include/internal.hrl | 3 + .../mochiweb-wrapper/mochiweb-git/rebar | Bin 0 -> 95259 bytes .../mochiweb-git/rebar.config | 16 + .../mochiweb-git/scripts/entities.erl | 45 + .../mochiweb-git/scripts/new_mochiweb.erl | 23 + .../mochiweb-git/src/mochifmt.erl | 425 + .../mochiweb-git/src/mochifmt_records.erl | 42 + .../mochiweb-git/src/mochifmt_std.erl | 33 + .../mochiweb-git/src/mochihex.erl | 88 + .../mochiweb-git/src/mochijson.erl | 529 + .../mochiweb-git/src/mochilists.erl | 104 + .../mochiweb-git/src/mochilogfile2.erl | 140 + .../mochiweb-git/src/mochitemp.erl | 307 + .../mochiweb-git/src/mochiutf8.erl | 317 + .../mochiweb-git/src/mochiweb.app.src | 8 + .../mochiweb-git/src/mochiweb.erl | 76 + .../mochiweb-git/src/mochiweb_acceptor.erl | 50 + .../mochiweb-git/src/mochiweb_base64url.erl | 83 + .../mochiweb-git/src/mochiweb_charref.erl | 2183 ++++ .../mochiweb-git/src/mochiweb_cookies.erl | 331 + .../mochiweb-git/src/mochiweb_cover.erl | 75 + .../mochiweb-git/src/mochiweb_echo.erl | 41 + .../mochiweb-git/src/mochiweb_headers.erl | 420 + .../mochiweb-git/src/mochiweb_html.erl | 774 ++ .../mochiweb-git/src/mochiweb_http.erl | 268 + .../mochiweb-git/src/mochiweb_io.erl | 43 + .../mochiweb-git/src/mochiweb_mime.erl | 415 + .../mochiweb-git/src/mochiweb_multipart.erl | 872 ++ .../mochiweb-git/src/mochiweb_request.erl | 857 ++ .../src/mochiweb_request.erl.orig | 857 ++ .../src/mochiweb_request_tests.erl | 182 + .../mochiweb-git/src/mochiweb_response.erl | 72 + .../mochiweb-git/src/mochiweb_session.erl | 189 + .../mochiweb-git/src/mochiweb_socket.erl | 84 + .../src/mochiweb_socket_server.erl | 348 + .../mochiweb-git/src/mochiweb_util.erl | 992 ++ .../mochiweb-git/src/reloader.erl | 161 + .../support/templates/mochiwebapp.template | 22 + .../mochiwebapp_skel/priv/www/index.html | 8 + .../templates/mochiwebapp_skel/rebar.config | 7 + .../mochiwebapp_skel/src/mochiapp.app.src | 9 + .../mochiwebapp_skel/src/mochiapp.erl | 30 + .../mochiwebapp_skel/src/mochiapp_app.erl | 22 + .../mochiwebapp_skel/src/mochiapp_deps.erl | 84 + .../mochiwebapp_skel/src/mochiapp_sup.erl | 56 + .../mochiwebapp_skel/src/mochiapp_web.erl | 69 + .../templates/mochiwebapp_skel/start-dev.sh | 6 + .../support/test-materials/test_ssl_cert.pem | 19 + .../support/test-materials/test_ssl_key.pem | 27 + .../test/mochiweb_base64url_tests.erl | 27 + .../mochiweb-git/test/mochiweb_html_tests.erl | 589 ++ .../mochiweb-git/test/mochiweb_http_tests.erl | 45 + .../mochiweb-git/test/mochiweb_tests.erl | 199 + .../plugins-src/mochiweb-wrapper/package.mk | 40 + .../rabbitmq-amqp1.0/.srcdist_done | 0 .../plugins-src/rabbitmq-amqp1.0/Makefile | 1 + .../plugins-src/rabbitmq-amqp1.0/README.md | 205 + .../plugins-src/rabbitmq-amqp1.0/codegen.py | 123 + .../include/rabbit_amqp1_0.hrl | 34 + .../plugins-src/rabbitmq-amqp1.0/package.mk | 26 + .../rabbitmq-amqp1.0/spec/messaging.xml | 168 + .../rabbitmq-amqp1.0/spec/security.xml | 76 + .../rabbitmq-amqp1.0/spec/transactions.xml | 73 + .../rabbitmq-amqp1.0/spec/transport.xml | 200 + .../rabbitmq-amqp1.0/spec/types.xml | 125 + .../src/rabbit_amqp1_0_binary_generator.erl | 127 + .../src/rabbit_amqp1_0_binary_parser.erl | 156 + .../src/rabbit_amqp1_0_channel.erl | 70 + .../src/rabbit_amqp1_0_framing.erl | 155 + .../src/rabbit_amqp1_0_incoming_link.erl | 235 + .../src/rabbit_amqp1_0_link_util.erl | 67 + .../src/rabbit_amqp1_0_message.erl | 270 + .../src/rabbit_amqp1_0_outgoing_link.erl | 245 + .../src/rabbit_amqp1_0_reader.erl | 695 ++ .../src/rabbit_amqp1_0_session.erl | 402 + .../src/rabbit_amqp1_0_session_process.erl | 375 + .../src/rabbit_amqp1_0_session_sup.erl | 69 + .../src/rabbit_amqp1_0_session_sup_sup.erl | 51 + .../src/rabbit_amqp1_0_util.erl | 86 + .../src/rabbit_amqp1_0_writer.erl | 305 + .../src/rabbitmq_amqp1_0.app.src | 9 + .../rabbitmq-amqp1.0/test/lib-java/junit.jar | Bin 0 -> 121070 bytes .../rabbitmq-amqp1.0/test/proton/Makefile | 23 + .../rabbitmq-amqp1.0/test/proton/build.xml | 30 + .../amqp1_0/tests/proton/ProtonTests.java | 34 + .../test/src/rabbit_amqp1_0_test.erl | 38 + .../rabbitmq-amqp1.0/test/swiftmq/Makefile | 26 + .../rabbitmq-amqp1.0/test/swiftmq/build.xml | 30 + .../test/swiftmq/run-tests.sh | 2 + .../amqp1_0/tests/swiftmq/SwiftMQTests.java | 343 + .../rabbitmq-auth-backend-ldap/.srcdist_done | 0 .../rabbitmq-auth-backend-ldap/Makefile | 1 + .../rabbitmq-auth-backend-ldap/README | 20 + .../README-authorisation | 1 + .../rabbitmq-auth-backend-ldap/README-tests | 13 + .../etc/rabbit-test.config | 42 + .../rabbitmq-auth-backend-ldap/example/README | 11 + .../example/global.ldif | 27 + .../example/groups.ldif | 8 + .../example/people.ldif | 24 + .../example/rabbit.ldif | 8 + .../example/setup.sh | 17 + .../rabbitmq-auth-backend-ldap/package.mk | 7 + .../src/rabbit_auth_backend_ldap.erl | 397 + .../src/rabbit_auth_backend_ldap_app.erl | 52 + .../src/rabbit_auth_backend_ldap_util.erl | 31 + .../src/rabbitmq_auth_backend_ldap.app.src | 22 + .../src/rabbit_auth_backend_ldap_test.erl | 138 + .../rabbitmq-auth-mechanism-ssl/.srcdist_done | 0 .../rabbitmq-auth-mechanism-ssl/Makefile | 1 + .../rabbitmq-auth-mechanism-ssl/README | 44 + .../rabbitmq-auth-mechanism-ssl/package.mk | 2 + .../src/rabbit_auth_mechanism_ssl.erl | 75 + .../src/rabbit_auth_mechanism_ssl_app.erl | 35 + .../src/rabbitmq_auth_mechanism_ssl.app.src | 9 + .../.srcdist_done | 0 .../rabbitmq-consistent-hash-exchange/LICENSE | 5 + .../LICENSE-MPL-RabbitMQ | 455 + .../Makefile | 1 + .../README.md | 137 + .../package.mk | 3 + .../rabbit_exchange_type_consistent_hash.erl | 165 + .../rabbitmq_consistent_hash_exchange.app.src | 7 + ...bit_exchange_type_consistent_hash_test.erl | 98 + .../rabbitmq-erlang-client/.srcdist_done | 0 .../rabbitmq-erlang-client/Makefile | 125 + .../rabbitmq-erlang-client/Makefile.in | 26 + .../rabbitmq-erlang-client/README.in | 10 + .../rabbitmq-erlang-client/common.mk | 201 + .../ebin/amqp_client.app.in | 8 + .../include/amqp_client.hrl | 56 + .../include/amqp_client_internal.hrl | 37 + .../include/amqp_gen_consumer_spec.hrl | 43 + .../include/rabbit_routing_prefixes.hrl | 24 + .../rabbit_common.app.in | 42 + .../src/amqp_auth_mechanisms.erl | 51 + .../src/amqp_channel.erl | 924 ++ .../src/amqp_channel_sup.erl | 78 + .../src/amqp_channel_sup_sup.erl | 45 + .../src/amqp_channels_manager.erl | 258 + .../src/amqp_client.erl | 40 + .../src/amqp_connection.erl | 344 + .../src/amqp_connection_sup.erl | 50 + .../src/amqp_connection_type_sup.erl | 88 + .../src/amqp_direct_connection.erl | 200 + .../src/amqp_direct_consumer.erl | 104 + .../src/amqp_gen_connection.erl | 378 + .../src/amqp_gen_consumer.erl | 267 + .../src/amqp_main_reader.erl | 157 + .../src/amqp_network_connection.erl | 365 + .../src/amqp_rpc_client.erl | 184 + .../src/amqp_rpc_server.erl | 147 + .../src/amqp_selective_consumer.erl | 256 + .../rabbitmq-erlang-client/src/amqp_sup.erl | 44 + .../rabbitmq-erlang-client/src/amqp_uri.erl | 247 + .../src/overview.edoc.in | 27 + .../src/rabbit_routing_util.erl | 196 + .../rabbitmq-erlang-client/src/uri_parser.erl | 121 + .../rabbitmq-erlang-client/test.mk | 130 + .../rabbitmq-erlang-client/test/Makefile | 33 + .../test/amqp_client_SUITE.erl | 125 + .../rabbitmq-erlang-client/test/amqp_dbg.erl | 122 + .../test/negative_test_util.erl | 193 + .../rabbitmq-erlang-client/test/test_util.erl | 1168 +++ .../.srcdist_done | 0 .../rabbitmq-federation-management/Makefile | 1 + .../rabbitmq-federation-management/README | 8 + .../rabbitmq-federation-management/package.mk | 7 + .../priv/www/js/federation.js | 70 + .../priv/www/js/tmpl/federation-upstream.ejs | 57 + .../priv/www/js/tmpl/federation-upstreams.ejs | 186 + .../priv/www/js/tmpl/federation.ejs | 90 + .../src/rabbit_federation_mgmt.erl | 97 + .../rabbitmq_federation_management.app.src | 8 + .../rabbitmq-federation/.srcdist_done | 0 .../plugins-src/rabbitmq-federation/Makefile | 2 + .../plugins-src/rabbitmq-federation/README | 4 + .../rabbitmq-federation/README-hacking | 143 + .../rabbitmq-federation/etc/rabbit-test.sh | 24 + .../etc/setup-rabbit-test.sh | 2 + .../include/rabbit_federation.hrl | 43 + .../rabbitmq-federation/package.mk | 15 + .../src/rabbit_federation_app.erl | 47 + .../src/rabbit_federation_db.erl | 56 + .../src/rabbit_federation_event.erl | 59 + .../src/rabbit_federation_exchange.erl | 112 + .../src/rabbit_federation_exchange_link.erl | 540 + ...abbit_federation_exchange_link_sup_sup.erl | 65 + .../src/rabbit_federation_link_sup.erl | 112 + .../src/rabbit_federation_link_util.erl | 288 + .../src/rabbit_federation_parameters.erl | 123 + .../src/rabbit_federation_queue.erl | 114 + .../src/rabbit_federation_queue_link.erl | 305 + .../rabbit_federation_queue_link_sup_sup.erl | 74 + .../src/rabbit_federation_status.erl | 139 + .../src/rabbit_federation_sup.erl | 61 + .../src/rabbit_federation_upstream.erl | 153 + .../rabbit_federation_upstream_exchange.erl | 73 + .../src/rabbit_federation_util.erl | 73 + .../src/rabbitmq_federation.app.src | 8 + .../src/rabbit_federation_exchange_test.erl | 704 ++ .../test/src/rabbit_federation_queue_test.erl | 208 + .../test/src/rabbit_federation_test_util.erl | 158 + .../test/src/rabbit_federation_unit_test.erl | 107 + .../rabbitmq-management-agent/.srcdist_done | 0 .../rabbitmq-management-agent/Makefile | 1 + .../rabbitmq-management-agent/package.mk | 1 + .../src/rabbit_mgmt_agent_app.erl | 36 + .../src/rabbit_mgmt_agent_sup.erl | 31 + .../src/rabbit_mgmt_db_handler.erl | 71 + .../src/rabbit_mgmt_external_stats.erl | 288 + .../src/rabbitmq_management_agent.app.src | 8 + .../.srcdist_done | 0 .../rabbitmq-management-visualiser/LICENSE | 8 + .../LICENSE-BSD-glMatrix | 26 + .../LICENSE-MPL-RabbitMQ | 455 + .../rabbitmq-management-visualiser/Makefile | 1 + .../rabbitmq-management-visualiser/README | 56 + .../license_info | 4 + .../rabbitmq-management-visualiser/package.mk | 7 + .../priv/www/js/visualiser.js | 3 + .../priv/www/visualiser/index.html | 187 + .../priv/www/visualiser/js/glMatrix-min.js | 32 + .../priv/www/visualiser/js/glMatrix.js | 1815 ++++ .../priv/www/visualiser/js/main.js | 682 ++ .../priv/www/visualiser/js/model.js | 1022 ++ .../priv/www/visualiser/js/octtree.js | 419 + .../priv/www/visualiser/js/physics.js | 106 + .../src/rabbit_mgmt_wm_all.erl | 50 + .../src/rabbit_visualiser_mgmt.erl | 24 + .../rabbitmq_management_visualiser.app.src | 6 + .../rabbitmq-management/.srcdist_done | 0 .../plugins-src/rabbitmq-management/LICENSE | 12 + .../LICENSE-APACHE2-ExplorerCanvas | 202 + .../rabbitmq-management/LICENSE-BSD-base64js | 25 + .../rabbitmq-management/LICENSE-MIT-EJS10 | 23 + .../rabbitmq-management/LICENSE-MIT-Flot | 22 + .../rabbitmq-management/LICENSE-MIT-Sammy060 | 25 + .../rabbitmq-management/LICENSE-MIT-jQuery164 | 21 + .../rabbitmq-management/LICENSE-MPL-RabbitMQ | 455 + .../plugins-src/rabbitmq-management/Makefile | 28 + .../plugins-src/rabbitmq-management/README | 12 + .../rabbitmq-management/bin/rabbitmqadmin | 943 ++ .../rabbitmq-management/etc/bunny.config | 1 + .../rabbitmq-management/etc/hare.config | 1 + .../etc/rabbit-test.config | 10 + .../include/rabbit_mgmt.hrl | 21 + .../include/rabbit_mgmt_test.hrl | 12 + .../rabbitmq-management/license_info | 17 + .../rabbitmq-management/package.mk | 25 + .../priv/www/api/index.html | 676 ++ .../priv/www/cli/index.html | 31 + .../rabbitmq-management/priv/www/css/evil.css | 1 + .../rabbitmq-management/priv/www/css/main.css | 277 + .../priv/www/doc/stats.html | 736 ++ .../rabbitmq-management/priv/www/favicon.ico | Bin 0 -> 318 bytes .../priv/www/img/bg-green-dark.png | Bin 0 -> 190 bytes .../priv/www/img/bg-red-dark.png | Bin 0 -> 190 bytes .../priv/www/img/bg-red.png | Bin 0 -> 220 bytes .../priv/www/img/bg-yellow-dark.png | Bin 0 -> 190 bytes .../priv/www/img/collapse.png | Bin 0 -> 226 bytes .../priv/www/img/expand.png | Bin 0 -> 221 bytes .../priv/www/img/rabbitmqlogo.png | Bin 0 -> 3296 bytes .../rabbitmq-management/priv/www/index.html | 31 + .../rabbitmq-management/priv/www/js/base64.js | 154 + .../rabbitmq-management/priv/www/js/charts.js | 72 + .../priv/www/js/dispatcher.js | 227 + .../rabbitmq-management/priv/www/js/ejs.js | 505 + .../priv/www/js/ejs.min.js | 1 + .../priv/www/js/excanvas.js | 1428 +++ .../priv/www/js/excanvas.min.js | 1 + .../priv/www/js/formatters.js | 881 ++ .../rabbitmq-management/priv/www/js/global.js | 177 + .../rabbitmq-management/priv/www/js/help.js | 282 + .../priv/www/js/jquery-1.6.4.js | 9046 +++++++++++++++++ .../priv/www/js/jquery-1.6.4.min.js | 4 + .../priv/www/js/jquery.flot.js | 3061 ++++++ .../priv/www/js/jquery.flot.min.js | 29 + .../priv/www/js/jquery.flot.time.js | 431 + .../priv/www/js/jquery.flot.time.min.js | 9 + .../rabbitmq-management/priv/www/js/json2.js | 482 + .../rabbitmq-management/priv/www/js/main.js | 1206 +++ .../rabbitmq-management/priv/www/js/prefs.js | 84 + .../priv/www/js/sammy-0.6.0.js | 1696 +++ .../priv/www/js/sammy-0.6.0.min.js | 5 + .../priv/www/js/tmpl/404.ejs | 3 + .../priv/www/js/tmpl/add-binding.ejs | 49 + .../priv/www/js/tmpl/bindings.ejs | 66 + .../priv/www/js/tmpl/channel.ejs | 105 + .../priv/www/js/tmpl/channels-list.ejs | 125 + .../priv/www/js/tmpl/channels.ejs | 5 + .../priv/www/js/tmpl/cluster-name.ejs | 31 + .../priv/www/js/tmpl/connection.ejs | 148 + .../priv/www/js/tmpl/connections.ejs | 62 + .../priv/www/js/tmpl/consumers.ejs | 39 + .../priv/www/js/tmpl/error-popup.ejs | 6 + .../priv/www/js/tmpl/exchange.ejs | 102 + .../priv/www/js/tmpl/exchanges.ejs | 129 + .../priv/www/js/tmpl/import-succeeded.ejs | 4 + .../priv/www/js/tmpl/layout.ejs | 43 + .../priv/www/js/tmpl/login.ejs | 18 + .../priv/www/js/tmpl/memory.ejs | 96 + .../priv/www/js/tmpl/messages.ejs | 39 + .../www/js/tmpl/msg-detail-deliveries.ejs | 38 + .../priv/www/js/tmpl/msg-detail-publishes.ejs | 53 + .../priv/www/js/tmpl/node.ejs | 185 + .../priv/www/js/tmpl/overview.ejs | 278 + .../priv/www/js/tmpl/partition.ejs | 54 + .../priv/www/js/tmpl/permissions.ejs | 91 + .../priv/www/js/tmpl/policies.ejs | 96 + .../priv/www/js/tmpl/policy.ejs | 41 + .../priv/www/js/tmpl/publish.ejs | 63 + .../priv/www/js/tmpl/queue.ejs | 252 + .../priv/www/js/tmpl/queues.ejs | 176 + .../priv/www/js/tmpl/rate-options.ejs | 45 + .../priv/www/js/tmpl/registry.ejs | 25 + .../priv/www/js/tmpl/status.ejs | 1 + .../priv/www/js/tmpl/user.ejs | 85 + .../priv/www/js/tmpl/users.ejs | 92 + .../priv/www/js/tmpl/vhost.ejs | 40 + .../priv/www/js/tmpl/vhosts.ejs | 75 + .../src/rabbit_mgmt_app.erl | 104 + .../src/rabbit_mgmt_db.erl | 1083 ++ .../src/rabbit_mgmt_dispatcher.erl | 85 + .../src/rabbit_mgmt_extension.erl | 32 + .../src/rabbit_mgmt_format.erl | 315 + .../src/rabbit_mgmt_load_definitions.erl | 48 + .../src/rabbit_mgmt_stats.erl | 201 + .../src/rabbit_mgmt_sup.erl | 34 + .../src/rabbit_mgmt_sup_sup.erl | 64 + .../src/rabbit_mgmt_util.erl | 589 ++ .../src/rabbit_mgmt_wm_aliveness_test.erl | 59 + .../src/rabbit_mgmt_wm_binding.erl | 137 + .../src/rabbit_mgmt_wm_bindings.erl | 136 + .../src/rabbit_mgmt_wm_channel.erl | 51 + .../src/rabbit_mgmt_wm_channels.erl | 44 + .../src/rabbit_mgmt_wm_cluster_name.erl | 57 + .../src/rabbit_mgmt_wm_connection.erl | 66 + .../rabbit_mgmt_wm_connection_channels.erl | 54 + .../src/rabbit_mgmt_wm_connections.erl | 44 + .../src/rabbit_mgmt_wm_definitions.erl | 281 + .../src/rabbit_mgmt_wm_exchange.erl | 82 + .../src/rabbit_mgmt_wm_exchange_publish.erl | 99 + .../src/rabbit_mgmt_wm_exchanges.erl | 56 + .../src/rabbit_mgmt_wm_extensions.erl | 37 + .../src/rabbit_mgmt_wm_node.erl | 63 + .../src/rabbit_mgmt_wm_nodes.erl | 48 + .../src/rabbit_mgmt_wm_overview.erl | 97 + .../src/rabbit_mgmt_wm_parameter.erl | 88 + .../src/rabbit_mgmt_wm_parameters.erl | 63 + .../src/rabbit_mgmt_wm_permission.erl | 97 + .../src/rabbit_mgmt_wm_permissions.erl | 43 + .../src/rabbit_mgmt_wm_permissions_user.erl | 39 + .../src/rabbit_mgmt_wm_permissions_vhost.erl | 39 + .../src/rabbit_mgmt_wm_policies.erl | 54 + .../src/rabbit_mgmt_wm_policy.erl | 88 + .../src/rabbit_mgmt_wm_queue.erl | 80 + .../src/rabbit_mgmt_wm_queue_actions.erl | 70 + .../src/rabbit_mgmt_wm_queue_get.erl | 124 + .../src/rabbit_mgmt_wm_queue_purge.erl | 45 + .../src/rabbit_mgmt_wm_queues.erl | 57 + .../src/rabbit_mgmt_wm_user.erl | 109 + .../src/rabbit_mgmt_wm_users.erl | 47 + .../src/rabbit_mgmt_wm_vhost.erl | 83 + .../src/rabbit_mgmt_wm_vhosts.erl | 47 + .../src/rabbit_mgmt_wm_whoami.erl | 35 + .../src/rabbitmq_management.app.src | 17 + .../test/src/default-config | 15 + .../test/src/rabbit_mgmt_test_clustering.erl | 98 + .../test/src/rabbit_mgmt_test_db.erl | 296 + .../test/src/rabbit_mgmt_test_db_unit.erl | 135 + .../test/src/rabbit_mgmt_test_http.erl | 1248 +++ .../test/src/rabbit_mgmt_test_unit.erl | 63 + .../test/src/rabbit_mgmt_test_util.erl | 45 + .../test/src/rabbitmqadmin-test.py | 244 + .../rabbitmq-management/test/src/test-config | 15 + .../plugins-src/rabbitmq-mqtt/.srcdist_done | 0 .../plugins-src/rabbitmq-mqtt/Makefile | 1 + .../plugins-src/rabbitmq-mqtt/README.md | 9 + .../rabbitmq-mqtt/include/rabbit_mqtt.hrl | 43 + .../include/rabbit_mqtt_frame.hrl | 93 + .../plugins-src/rabbitmq-mqtt/lib/junit.jar | Bin 0 -> 121070 bytes .../plugins-src/rabbitmq-mqtt/package.mk | 5 + .../rabbitmq-mqtt/src/rabbit_mqtt.erl | 28 + .../src/rabbit_mqtt_collector.erl | 94 + .../src/rabbit_mqtt_connection_sup.erl | 51 + .../rabbitmq-mqtt/src/rabbit_mqtt_frame.erl | 232 + .../src/rabbit_mqtt_processor.erl | 511 + .../rabbitmq-mqtt/src/rabbit_mqtt_reader.erl | 284 + .../rabbitmq-mqtt/src/rabbit_mqtt_sup.erl | 81 + .../rabbitmq-mqtt/src/rabbit_mqtt_util.erl | 53 + .../rabbitmq-mqtt/src/rabbitmq_mqtt.app.src | 21 + .../plugins-src/rabbitmq-mqtt/test/Makefile | 46 + .../src/com/rabbitmq/mqtt/test/MqttTest.java | 430 + .../plugins-src/rabbitmq-mqtt/test/test.sh | 3 + .../rabbitmq-shovel-management/.srcdist_done | 0 .../rabbitmq-shovel-management/Makefile | 1 + .../rabbitmq-shovel-management/README | 11 + .../etc/rabbit-test.config | 19 + .../rabbitmq-shovel-management/package.mk | 9 + .../priv/www/js/shovel.js | 88 + .../priv/www/js/tmpl/dynamic-shovel.ejs | 61 + .../priv/www/js/tmpl/dynamic-shovels.ejs | 237 + .../priv/www/js/tmpl/shovels.ejs | 70 + .../src/rabbit_shovel_mgmt.erl | 124 + .../src/rabbitmq_shovel_management.app.src | 6 + .../test/src/rabbit_shovel_mgmt_test_all.erl | 28 + .../test/src/rabbit_shovel_mgmt_test_http.erl | 181 + .../plugins-src/rabbitmq-shovel/.srcdist_done | 0 .../plugins-src/rabbitmq-shovel/Makefile | 1 + .../plugins-src/rabbitmq-shovel/README | 4 + .../plugins-src/rabbitmq-shovel/generate_deps | 54 + .../rabbitmq-shovel/include/rabbit_shovel.hrl | 32 + .../plugins-src/rabbitmq-shovel/package.mk | 3 + .../rabbitmq-shovel/src/rabbit_shovel.erl | 27 + .../src/rabbit_shovel_config.erl | 242 + .../src/rabbit_shovel_dyn_worker_sup.erl | 40 + .../src/rabbit_shovel_dyn_worker_sup_sup.erl | 75 + .../src/rabbit_shovel_parameters.erl | 245 + .../src/rabbit_shovel_status.erl | 84 + .../rabbitmq-shovel/src/rabbit_shovel_sup.erl | 87 + .../src/rabbit_shovel_worker.erl | 262 + .../src/rabbit_shovel_worker_sup.erl | 40 + .../src/rabbitmq_shovel.app.src | 13 + .../test/src/rabbit_shovel_test.erl | 247 + .../test/src/rabbit_shovel_test_all.erl | 33 + .../test/src/rabbit_shovel_test_dyn.erl | 275 + .../plugins-src/rabbitmq-stomp/.srcdist_done | 0 .../plugins-src/rabbitmq-stomp/Makefile | 1 + .../plugins-src/rabbitmq-stomp/NOTES | 71 + .../plugins-src/rabbitmq-stomp/README.md | 13 + .../rabbitmq-stomp/deps/stomppy/Makefile | 28 + .../rabbitmq-stomp/deps/stomppy/rabbit.patch | 107 + .../examples/perl/rabbitmq_stomp_recv.pl | 13 + .../perl/rabbitmq_stomp_rpc_client.pl | 15 + .../perl/rabbitmq_stomp_rpc_service.pl | 21 + .../examples/perl/rabbitmq_stomp_send.pl | 9 + .../examples/perl/rabbitmq_stomp_send_many.pl | 11 + .../examples/perl/rabbitmq_stomp_slow_recv.pl | 14 + .../examples/ruby/cb-receiver.rb | 8 + .../rabbitmq-stomp/examples/ruby/cb-sender.rb | 6 + .../examples/ruby/cb-slow-receiver.rb | 13 + .../examples/ruby/persistent-receiver.rb | 11 + .../examples/ruby/persistent-sender.rb | 13 + .../examples/ruby/topic-broadcast-receiver.rb | 11 + .../ruby/topic-broadcast-with-unsubscribe.rb | 13 + .../examples/ruby/topic-sender.rb | 7 + .../rabbitmq-stomp/include/rabbit_stomp.hrl | 22 + .../include/rabbit_stomp_frame.hrl | 17 + .../include/rabbit_stomp_headers.hrl | 51 + .../plugins-src/rabbitmq-stomp/package.mk | 48 + .../rabbitmq-stomp/src/rabbit_stomp.erl | 87 + .../src/rabbit_stomp_client_sup.erl | 55 + .../rabbitmq-stomp/src/rabbit_stomp_frame.erl | 250 + .../src/rabbit_stomp_processor.erl | 1051 ++ .../src/rabbit_stomp_reader.erl | 222 + .../rabbitmq-stomp/src/rabbit_stomp_sup.erl | 80 + .../rabbitmq-stomp/src/rabbit_stomp_util.erl | 320 + .../rabbitmq-stomp/src/rabbitmq_stomp.app.src | 20 + .../rabbitmq-stomp/test/src/ack.py | 197 + .../rabbitmq-stomp/test/src/base.py | 175 + .../test/src/connect_options.py | 42 + .../rabbitmq-stomp/test/src/destinations.py | 486 + .../rabbitmq-stomp/test/src/errors.py | 67 + .../rabbitmq-stomp/test/src/lifecycle.py | 164 + .../rabbitmq-stomp/test/src/non_ssl.config | 6 + .../rabbitmq-stomp/test/src/parsing.py | 351 + .../test/src/rabbit_stomp_amqqueue_test.erl | 225 + .../test/src/rabbit_stomp_client.erl | 81 + .../test/src/rabbit_stomp_publish_test.erl | 88 + .../test/src/rabbit_stomp_test.erl | 41 + .../test/src/rabbit_stomp_test_frame.erl | 193 + .../test/src/rabbit_stomp_test_util.erl | 226 + .../rabbitmq-stomp/test/src/reliability.py | 34 + .../rabbitmq-stomp/test/src/ssl.config | 12 + .../rabbitmq-stomp/test/src/ssl_lifecycle.py | 70 + .../rabbitmq-stomp/test/src/test.py | 9 + .../test/src/test_connect_options.py | 8 + .../rabbitmq-stomp/test/src/test_runner.py | 25 + .../rabbitmq-stomp/test/src/test_ssl.py | 10 + .../rabbitmq-stomp/test/src/test_util.py | 42 + .../rabbitmq-stomp/test/src/transactions.py | 54 + .../plugins-src/rabbitmq-test/.srcdist_done | 0 .../plugins-src/rabbitmq-test/Makefile | 172 + .../plugins-src/rabbitmq-test/README | 18 + .../plugins-src/rabbitmq-test/certs/Makefile | 58 + .../rabbitmq-test/certs/openssl.cnf | 54 + .../plugins-src/rabbitmq-test/package.mk | 10 + .../plugins-src/rabbitmq-test/qpid_config.py | 26 + .../plugins-src/rabbitmq-test/qpid_patch | 142 + .../rabbitmq-test/rabbit_failing.txt | 9 + .../rabbitmq-test/src/inet_proxy_dist.erl | 199 + .../rabbitmq-test/src/inet_tcp_proxy.erl | 106 + .../src/inet_tcp_proxy_manager.erl | 99 + .../src/rabbit_ha_test_consumer.erl | 114 + .../src/rabbit_ha_test_producer.erl | 119 + .../rabbitmq-test/src/rabbit_test_configs.erl | 234 + .../rabbitmq-test/src/rabbit_test_runner.erl | 214 + .../rabbitmq-test/src/rabbit_test_util.erl | 134 + .../rabbitmq-test/src/rabbitmq_test.app.src | 11 + .../test/src/clustering_management.erl | 447 + .../rabbitmq-test/test/src/dynamic_ha.erl | 220 + .../rabbitmq-test/test/src/eager_sync.erl | 205 + .../rabbitmq-test/test/src/many_node_ha.erl | 64 + .../rabbitmq-test/test/src/partitions.erl | 245 + .../rabbitmq-test/test/src/simple_ha.erl | 123 + .../rabbitmq-test/test/src/sync_detection.erl | 189 + .../rabbitmq-tracing/.srcdist_done | 0 .../plugins-src/rabbitmq-tracing/Makefile | 1 + .../plugins-src/rabbitmq-tracing/README | 40 + .../plugins-src/rabbitmq-tracing/package.mk | 8 + .../priv/www/js/tmpl/traces.ejs | 145 + .../rabbitmq-tracing/priv/www/js/tracing.js | 38 + .../src/rabbit_tracing_app.erl | 26 + .../src/rabbit_tracing_consumer.erl | 168 + .../src/rabbit_tracing_consumer_sup.erl | 34 + .../src/rabbit_tracing_files.erl | 51 + .../src/rabbit_tracing_mgmt.erl | 29 + .../src/rabbit_tracing_sup.erl | 50 + .../src/rabbit_tracing_traces.erl | 123 + .../src/rabbit_tracing_wm_file.erl | 49 + .../src/rabbit_tracing_wm_files.erl | 37 + .../src/rabbit_tracing_wm_trace.erl | 82 + .../src/rabbit_tracing_wm_traces.erl | 37 + .../src/rabbitmq_tracing.app.src | 8 + .../test/src/rabbit_tracing_test.erl | 186 + .../rabbitmq-web-dispatch/.srcdist_done | 0 .../plugins-src/rabbitmq-web-dispatch/LICENSE | 470 + .../rabbitmq-web-dispatch/Makefile | 1 + .../rabbitmq-web-dispatch/README.md | 25 + .../rabbitmq-web-dispatch/package.mk | 3 + .../src/rabbit_web_dispatch.erl | 114 + .../src/rabbit_web_dispatch_app.erl | 30 + .../src/rabbit_web_dispatch_registry.erl | 199 + .../src/rabbit_web_dispatch_sup.erl | 104 + .../src/rabbit_web_dispatch_util.erl | 63 + .../src/rabbit_webmachine.erl | 67 + .../src/rabbitmq_web_dispatch.app.src | 8 + .../test/priv/www/index.html | 7 + .../test/src/rabbit_web_dispatch_test.erl | 38 + .../src/rabbit_web_dispatch_test_unit.erl | 36 + .../rabbitmq-web-stomp-examples/.srcdist_done | 0 .../rabbitmq-web-stomp-examples/LICENSE | 9 + .../LICENSE-APL2-Stomp-Websocket | 202 + .../LICENSE-MPL-RabbitMQ | 455 + .../rabbitmq-web-stomp-examples/Makefile | 1 + .../rabbitmq-web-stomp-examples/README.md | 22 + .../rabbitmq-web-stomp-examples/package.mk | 6 + .../priv/bunny.html | 141 + .../priv/bunny.png | Bin 0 -> 38296 bytes .../priv/echo.html | 111 + .../priv/index.html | 16 + .../rabbitmq-web-stomp-examples/priv/main.css | 38 + .../priv/pencil.cur | Bin 0 -> 2238 bytes .../rabbitmq-web-stomp-examples/priv/stomp.js | 396 + .../priv/temp-queue.html | 102 + .../src/rabbit_web_stomp_examples_app.erl | 38 + .../src/rabbitmq_web_stomp_examples.app.src | 8 + .../rabbitmq-web-stomp/.srcdist_done | 0 .../plugins-src/rabbitmq-web-stomp/LICENSE | 5 + .../rabbitmq-web-stomp/LICENSE-MPL-RabbitMQ | 455 + .../plugins-src/rabbitmq-web-stomp/Makefile | 1 + .../plugins-src/rabbitmq-web-stomp/README.md | 45 + .../plugins-src/rabbitmq-web-stomp/package.mk | 4 + .../rabbitmq-web-stomp/src/rabbit_ws_app.erl | 31 + .../src/rabbit_ws_client.erl | 97 + .../src/rabbit_ws_client_sup.erl | 72 + .../src/rabbit_ws_sockjs.erl | 77 + .../rabbitmq-web-stomp/src/rabbit_ws_sup.erl | 36 + .../src/rabbitmq_web_stomp.app.src | 11 + .../test/src/rabbit_ws_test_all.erl | 25 + .../test/src/rabbit_ws_test_raw_websocket.erl | 71 + .../src/rabbit_ws_test_sockjs_websocket.erl | 85 + .../test/src/rfc6455_client.erl | 236 + .../rabbitmq-web-stomp/test/src/stomp.erl | 54 + rabbitmq-server-3.3.5/plugins-src/release.mk | 272 + .../sockjs-erlang-wrapper/.srcdist_done | 0 .../0000-remove-spec-patch.diff | 816 ++ .../sockjs-erlang-wrapper/0001-a2b-b2a.diff | 22 + .../0002-parameterised-modules-r16a.diff | 477 + .../0003-websocket-subprotocol | 93 + .../sockjs-erlang-wrapper/Makefile | 1 + .../generate-0000-remove-spec-patch.sh | 10 + .../plugins-src/sockjs-erlang-wrapper/hash.mk | 1 + .../sockjs-erlang-wrapper/package.mk | 27 + .../sockjs-erlang-git/.done | 0 .../sockjs-erlang-git/COPYING | 10 + .../sockjs-erlang-git/Changelog | 30 + .../sockjs-erlang-git/LICENSE-APL2-Rebar | 178 + .../sockjs-erlang-git/LICENSE-MIT-Mochiweb | 22 + .../sockjs-erlang-git/LICENSE-MIT-SockJS | 19 + .../sockjs-erlang-git/Makefile | 80 + .../sockjs-erlang-git/README.md | 220 + .../examples/cowboy_echo.erl | 50 + .../examples/cowboy_test_server.erl | 99 + .../sockjs-erlang-git/examples/echo.html | 72 + .../examples/multiplex/cowboy_multiplex.erl | 86 + .../examples/multiplex/index.html | 96 + .../examples/multiplex/multiplex.js | 80 + .../sockjs-erlang-git/rebar | Bin 0 -> 114094 bytes .../sockjs-erlang-git/rebar.config | 16 + .../sockjs-erlang-git/src/mochijson2_fork.erl | 927 ++ .../sockjs-erlang-git/src/mochinum_fork.erl | 354 + .../sockjs-erlang-git/src/pmod_pt.erl | 461 + .../sockjs-erlang-git/src/sockjs.app.src | 12 + .../sockjs-erlang-git/src/sockjs.erl | 24 + .../sockjs-erlang-git/src/sockjs_action.erl | 279 + .../sockjs-erlang-git/src/sockjs_app.erl | 14 + .../src/sockjs_cowboy_handler.erl | 97 + .../sockjs-erlang-git/src/sockjs_filters.erl | 69 + .../sockjs-erlang-git/src/sockjs_handler.erl | 230 + .../sockjs-erlang-git/src/sockjs_http.erl | 137 + .../sockjs-erlang-git/src/sockjs_internal.hrl | 33 + .../sockjs-erlang-git/src/sockjs_json.erl | 17 + .../src/sockjs_multiplex.erl | 79 + .../src/sockjs_multiplex_channel.erl | 18 + .../sockjs-erlang-git/src/sockjs_service.erl | 13 + .../sockjs-erlang-git/src/sockjs_session.erl | 306 + .../src/sockjs_session_sup.erl | 20 + .../sockjs-erlang-git/src/sockjs_util.erl | 48 + .../src/sockjs_ws_handler.erl | 58 + rabbitmq-server-3.3.5/plugins-src/umbrella.mk | 55 + .../webmachine-wrapper/.srcdist_done | 0 .../10-remove-crypto-dependency.patch | 78 + .../webmachine-wrapper/LICENSE-Apache-Basho | 178 + .../plugins-src/webmachine-wrapper/Makefile | 1 + .../plugins-src/webmachine-wrapper/hash.mk | 1 + .../webmachine-wrapper/license_info | 3 + .../plugins-src/webmachine-wrapper/package.mk | 19 + .../webmachine-wrapper/webmachine-git/.done | 0 .../webmachine-git/.travis.yml | 9 + .../webmachine-git/Emakefile | 6 + .../webmachine-wrapper/webmachine-git/LICENSE | 178 + .../webmachine-git/Makefile | 24 + .../webmachine-git/README.org | 66 + .../webmachine-wrapper/webmachine-git/THANKS | 21 + .../webmachine-git/demo/Makefile | 19 + .../webmachine-git/demo/README | 43 + .../webmachine-git/demo/priv/dispatch.conf | 3 + .../webmachine-git/demo/rebar.config | 3 + .../demo/src/webmachine_demo.app.src | 17 + .../demo/src/webmachine_demo.erl | 42 + .../demo/src/webmachine_demo_app.erl | 20 + .../demo/src/webmachine_demo_fs_resource.erl | 159 + .../demo/src/webmachine_demo_resource.erl | 51 + .../demo/src/webmachine_demo_sup.erl | 56 + .../webmachine-git/demo/start.sh | 3 + .../docs/http-headers-status-v3.png | Bin 0 -> 412634 bytes .../webmachine-git/include/webmachine.hrl | 8 + .../include/webmachine_logger.hrl | 16 + .../webmachine-git/include/wm_reqdata.hrl | 8 + .../webmachine-git/include/wm_reqstate.hrl | 10 + .../webmachine-git/include/wm_resource.hrl | 1 + .../webmachine-git/priv/templates/Makefile | 19 + .../webmachine-git/priv/templates/README | 35 + .../priv/templates/priv/dispatch.conf | 2 + .../priv/templates/rebar.config | 3 + .../priv/templates/src/wmskel.app.src | 18 + .../priv/templates/src/wmskel.erl | 48 + .../priv/templates/src/wmskel_app.erl | 21 + .../priv/templates/src/wmskel_resource.erl | 13 + .../priv/templates/src/wmskel_sup.erl | 72 + .../webmachine-git/priv/templates/start.sh | 3 + .../priv/templates/wmskel.template | 35 + .../priv/trace/http-headers-status-v3.png | Bin 0 -> 412634 bytes .../webmachine-git/priv/trace/wmtrace.css | 107 + .../webmachine-git/priv/trace/wmtrace.js | 713 ++ .../webmachine-git/priv/www/index.html | 8 + .../webmachine-wrapper/webmachine-git/rebar | Bin 0 -> 119212 bytes .../webmachine-git/rebar.config | 9 + .../webmachine-git/rebar.config.script | 11 + .../webmachine-git/scripts/new_webmachine.sh | 39 + .../webmachine-git/src/webmachine.app.src | 13 + .../webmachine-git/src/webmachine.erl | 78 + .../webmachine-git/src/webmachine_app.erl | 50 + .../src/webmachine_decision_core.erl | 764 ++ .../webmachine-git/src/webmachine_deps.erl | 91 + .../src/webmachine_dispatcher.erl | 564 + .../webmachine-git/src/webmachine_error.erl | 75 + .../src/webmachine_error_handler.erl | 96 + .../webmachine-git/src/webmachine_log.erl | 239 + .../src/webmachine_log_handler.erl | 121 + .../src/webmachine_logger_watcher.erl | 88 + .../src/webmachine_logger_watcher_sup.erl | 39 + .../src/webmachine_mochiweb.erl | 170 + .../src/webmachine_multipart.erl | 204 + .../src/webmachine_perf_log_handler.erl | 114 + .../webmachine-git/src/webmachine_request.erl | 962 ++ .../src/webmachine_resource.erl | 260 + .../webmachine-git/src/webmachine_router.erl | 276 + .../webmachine-git/src/webmachine_sup.erl | 65 + .../webmachine-git/src/webmachine_util.erl | 549 + .../webmachine-git/src/wmtrace_resource.erl | 351 + .../webmachine-git/src/wrq.erl | 338 + .../webmachine-git/start-dev.sh | 3 + .../webmachine-git/start.sh | 3 + .../webmachine-git/test/etag_test.erl | 145 + .../webmachine-git/www/blogs.html | 67 + .../webmachine-git/www/contact.html | 48 + .../webmachine-git/www/css/style-1c.css | 103 + .../webmachine-git/www/css/style.css | 103 + .../webmachine-git/www/debugging.html | 292 + .../webmachine-git/www/diagram.html | 57 + .../webmachine-git/www/dispatcher.html | 121 + .../webmachine-git/www/docs.html | 58 + .../webmachine-git/www/example_resources.html | 250 + .../webmachine-git/www/favicon.ico | Bin 0 -> 1150 bytes .../webmachine-git/www/images/WM200-crop.png | Bin 0 -> 14637 bytes .../www/images/basho-landscape.gif | Bin 0 -> 2687 bytes .../www/images/basic-trace-decision-tab.png | Bin 0 -> 85598 bytes .../www/images/basic-trace-labeled.png | Bin 0 -> 125655 bytes .../www/images/basic-trace-request-tab.png | Bin 0 -> 28617 bytes .../www/images/basic-trace-response-tab.png | Bin 0 -> 12077 bytes .../webmachine-git/www/images/bg.gif | Bin 0 -> 4969 bytes .../webmachine-git/www/images/blankbox.gif | Bin 0 -> 1181 bytes .../webmachine-git/www/images/chash.gif | Bin 0 -> 10589 bytes .../webmachine-git/www/images/easy-ops.gif | Bin 0 -> 10991 bytes .../webmachine-git/www/images/gossip4.gif | Bin 0 -> 4917 bytes .../www/images/halfblankbox.gif | Bin 0 -> 1045 bytes .../www/images/http-headers-status-v3.png | Bin 0 -> 412634 bytes .../webmachine-git/www/images/more.gif | Bin 0 -> 38753 bytes .../webmachine-git/www/images/site.gif | Bin 0 -> 862 bytes .../webmachine-git/www/images/splash250.gif | Bin 0 -> 1718 bytes .../webmachine-git/www/images/vclock.gif | Bin 0 -> 5759 bytes .../webmachine-git/www/index.html | 73 + .../webmachine-git/www/intros.html | 62 + .../webmachine-git/www/mechanics.html | 108 + .../webmachine-git/www/quickstart.html | 77 + .../webmachine-git/www/reftrans.html | 52 + .../webmachine-git/www/reqdata.html | 143 + .../webmachine-git/www/resources.html | 142 + .../webmachine-git/www/streambody.html | 176 + .../scripts/rabbitmq-defaults | 36 + .../scripts/rabbitmq-echopid.bat | 49 + rabbitmq-server-3.3.5/scripts/rabbitmq-env | 60 + .../scripts/rabbitmq-plugins | 38 + .../scripts/rabbitmq-plugins.bat | 57 + rabbitmq-server-3.3.5/scripts/rabbitmq-server | 148 + .../scripts/rabbitmq-server.bat | 166 + .../scripts/rabbitmq-service.bat | 266 + rabbitmq-server-3.3.5/scripts/rabbitmqctl | 38 + rabbitmq-server-3.3.5/scripts/rabbitmqctl.bat | 49 + rabbitmq-server-3.3.5/src/app_utils.erl | 138 + rabbitmq-server-3.3.5/src/background_gc.erl | 81 + rabbitmq-server-3.3.5/src/credit_flow.erl | 158 + rabbitmq-server-3.3.5/src/delegate.erl | 244 + rabbitmq-server-3.3.5/src/delegate_sup.erl | 59 + rabbitmq-server-3.3.5/src/dtree.erl | 172 + .../src/file_handle_cache.erl | 1280 +++ rabbitmq-server-3.3.5/src/gatherer.erl | 145 + rabbitmq-server-3.3.5/src/gen_server2.erl | 1337 +++ rabbitmq-server-3.3.5/src/gm.erl | 1463 +++ rabbitmq-server-3.3.5/src/gm_soak_test.erl | 133 + rabbitmq-server-3.3.5/src/gm_speed_test.erl | 83 + rabbitmq-server-3.3.5/src/gm_tests.erl | 186 + rabbitmq-server-3.3.5/src/lqueue.erl | 90 + .../src/mirrored_supervisor.erl | 534 + .../src/mirrored_supervisor_tests.erl | 346 + rabbitmq-server-3.3.5/src/mnesia_sync.erl | 77 + rabbitmq-server-3.3.5/src/mochijson2.erl | 893 ++ rabbitmq-server-3.3.5/src/mochinum.erl | 358 + rabbitmq-server-3.3.5/src/pg2_fixed.erl | 400 + rabbitmq-server-3.3.5/src/pg_local.erl | 213 + rabbitmq-server-3.3.5/src/pmon.erl | 96 + rabbitmq-server-3.3.5/src/priority_queue.erl | 227 + rabbitmq-server-3.3.5/src/rabbit.erl | 824 ++ .../src/rabbit_access_control.erl | 134 + rabbitmq-server-3.3.5/src/rabbit_alarm.erl | 239 + rabbitmq-server-3.3.5/src/rabbit_amqqueue.erl | 742 ++ .../src/rabbit_amqqueue_process.erl | 1269 +++ .../src/rabbit_amqqueue_sup.erl | 52 + .../src/rabbit_auth_backend.erl | 72 + .../src/rabbit_auth_backend_dummy.erl | 49 + .../src/rabbit_auth_backend_internal.erl | 347 + .../src/rabbit_auth_mechanism.erl | 56 + .../src/rabbit_auth_mechanism_amqplain.erl | 55 + .../src/rabbit_auth_mechanism_cr_demo.erl | 57 + .../src/rabbit_auth_mechanism_plain.erl | 72 + rabbitmq-server-3.3.5/src/rabbit_autoheal.erl | 227 + .../src/rabbit_backing_queue.erl | 251 + .../src/rabbit_backing_queue_qc.erl | 472 + rabbitmq-server-3.3.5/src/rabbit_basic.erl | 280 + .../src/rabbit_binary_generator.erl | 241 + .../src/rabbit_binary_parser.erl | 119 + rabbitmq-server-3.3.5/src/rabbit_binding.erl | 544 + rabbitmq-server-3.3.5/src/rabbit_channel.erl | 1700 ++++ .../src/rabbit_channel_interceptor.erl | 96 + .../src/rabbit_channel_sup.erl | 92 + .../src/rabbit_channel_sup_sup.erl | 48 + .../src/rabbit_client_sup.erl | 57 + .../src/rabbit_command_assembler.erl | 137 + .../src/rabbit_connection_helper_sup.erl | 59 + .../src/rabbit_connection_sup.erl | 68 + .../src/rabbit_control_main.erl | 756 ++ .../src/rabbit_dead_letter.erl | 145 + .../src/rabbit_diagnostics.erl | 79 + rabbitmq-server-3.3.5/src/rabbit_direct.erl | 115 + .../src/rabbit_disk_monitor.erl | 227 + .../src/rabbit_error_logger.erl | 102 + .../src/rabbit_error_logger_file_h.erl | 134 + rabbitmq-server-3.3.5/src/rabbit_event.erl | 151 + rabbitmq-server-3.3.5/src/rabbit_exchange.erl | 479 + .../src/rabbit_exchange_decorator.erl | 106 + .../src/rabbit_exchange_type.erl | 81 + .../src/rabbit_exchange_type_direct.erl | 51 + .../src/rabbit_exchange_type_fanout.erl | 50 + .../src/rabbit_exchange_type_headers.erl | 125 + .../src/rabbit_exchange_type_invalid.erl | 52 + .../src/rabbit_exchange_type_topic.erl | 270 + rabbitmq-server-3.3.5/src/rabbit_file.erl | 307 + rabbitmq-server-3.3.5/src/rabbit_framing.erl | 49 + rabbitmq-server-3.3.5/src/rabbit_guid.erl | 177 + .../src/rabbit_heartbeat.erl | 166 + rabbitmq-server-3.3.5/src/rabbit_limiter.erl | 441 + rabbitmq-server-3.3.5/src/rabbit_log.erl | 110 + .../src/rabbit_memory_monitor.erl | 269 + .../src/rabbit_mirror_queue_coordinator.erl | 427 + .../src/rabbit_mirror_queue_master.erl | 490 + .../src/rabbit_mirror_queue_misc.erl | 400 + .../src/rabbit_mirror_queue_mode.erl | 57 + .../src/rabbit_mirror_queue_mode_all.erl | 41 + .../src/rabbit_mirror_queue_mode_exactly.erl | 56 + .../src/rabbit_mirror_queue_mode_nodes.erl | 70 + .../src/rabbit_mirror_queue_slave.erl | 948 ++ .../src/rabbit_mirror_queue_slave_sup.erl | 37 + .../src/rabbit_mirror_queue_sync.erl | 264 + rabbitmq-server-3.3.5/src/rabbit_misc.erl | 1116 ++ rabbitmq-server-3.3.5/src/rabbit_mnesia.erl | 842 ++ rabbitmq-server-3.3.5/src/rabbit_msg_file.erl | 125 + .../src/rabbit_msg_store.erl | 2069 ++++ .../src/rabbit_msg_store_ets_index.erl | 79 + .../src/rabbit_msg_store_gc.erl | 137 + .../src/rabbit_msg_store_index.erl | 59 + rabbitmq-server-3.3.5/src/rabbit_net.erl | 246 + .../src/rabbit_networking.erl | 484 + .../src/rabbit_node_monitor.erl | 526 + rabbitmq-server-3.3.5/src/rabbit_nodes.erl | 199 + .../src/rabbit_parameter_validation.erl | 87 + rabbitmq-server-3.3.5/src/rabbit_plugins.erl | 225 + .../src/rabbit_plugins_main.erl | 267 + rabbitmq-server-3.3.5/src/rabbit_policies.erl | 81 + rabbitmq-server-3.3.5/src/rabbit_policy.erl | 347 + .../src/rabbit_policy_validator.erl | 39 + .../src/rabbit_prelaunch.erl | 122 + .../src/rabbit_queue_collector.erl | 92 + .../src/rabbit_queue_consumers.erl | 462 + .../src/rabbit_queue_decorator.erl | 43 + .../src/rabbit_queue_index.erl | 1117 ++ rabbitmq-server-3.3.5/src/rabbit_reader.erl | 1201 +++ .../src/rabbit_recovery_terms.erl | 121 + rabbitmq-server-3.3.5/src/rabbit_registry.erl | 165 + .../src/rabbit_restartable_sup.erl | 48 + rabbitmq-server-3.3.5/src/rabbit_router.erl | 83 + .../src/rabbit_runtime_parameter.erl | 42 + .../src/rabbit_runtime_parameters.erl | 270 + .../src/rabbit_runtime_parameters_test.erl | 72 + .../src/rabbit_sasl_report_file_h.erl | 100 + rabbitmq-server-3.3.5/src/rabbit_ssl.erl | 298 + rabbitmq-server-3.3.5/src/rabbit_sup.erl | 102 + rabbitmq-server-3.3.5/src/rabbit_table.erl | 311 + rabbitmq-server-3.3.5/src/rabbit_tests.erl | 2987 ++++++ .../src/rabbit_tests_event_receiver.erl | 58 + rabbitmq-server-3.3.5/src/rabbit_trace.erl | 119 + rabbitmq-server-3.3.5/src/rabbit_types.erl | 163 + rabbitmq-server-3.3.5/src/rabbit_upgrade.erl | 286 + .../src/rabbit_upgrade_functions.erl | 409 + .../src/rabbit_variable_queue.erl | 1813 ++++ rabbitmq-server-3.3.5/src/rabbit_version.erl | 175 + rabbitmq-server-3.3.5/src/rabbit_vhost.erl | 156 + rabbitmq-server-3.3.5/src/rabbit_vm.erl | 227 + rabbitmq-server-3.3.5/src/rabbit_writer.erl | 353 + .../src/supervised_lifecycle.erl | 68 + rabbitmq-server-3.3.5/src/supervisor2.erl | 1557 +++ .../src/supervisor2_tests.erl | 70 + rabbitmq-server-3.3.5/src/tcp_acceptor.erl | 105 + .../src/tcp_acceptor_sup.erl | 43 + rabbitmq-server-3.3.5/src/tcp_listener.erl | 98 + .../src/tcp_listener_sup.erl | 70 + rabbitmq-server-3.3.5/src/test_sup.erl | 93 + rabbitmq-server-3.3.5/src/truncate.erl | 194 + .../src/vm_memory_monitor.erl | 388 + .../src/vm_memory_monitor_tests.erl | 35 + rabbitmq-server-3.3.5/src/worker_pool.erl | 136 + rabbitmq-server-3.3.5/src/worker_pool_sup.erl | 53 + .../src/worker_pool_worker.erl | 131 + rabbitmq-server-3.3.5/version.mk | 1 + 1098 files changed, 182327 insertions(+) create mode 100644 debian/LICENSE.head create mode 100644 debian/LICENSE.tail create mode 100644 debian/README create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/dirs create mode 100644 debian/postinst create mode 100644 debian/postrm.in create mode 100644 debian/rabbitmq-script-wrapper create mode 100644 debian/rabbitmq-server.default create mode 100644 debian/rabbitmq-server.init create mode 100644 debian/rabbitmq-server.logrotate create mode 100644 debian/rabbitmq-server.ocf create mode 100644 debian/rules create mode 100644 debian/source/format create mode 100644 debian/watch create mode 100644 rabbitmq-server-3.3.5/INSTALL create mode 100644 rabbitmq-server-3.3.5/LICENSE create mode 100644 rabbitmq-server-3.3.5/LICENSE-APACHE2-ExplorerCanvas create mode 100644 rabbitmq-server-3.3.5/LICENSE-APL2-Stomp-Websocket create mode 100644 rabbitmq-server-3.3.5/LICENSE-Apache-Basho create mode 100644 rabbitmq-server-3.3.5/LICENSE-BSD-base64js create mode 100644 rabbitmq-server-3.3.5/LICENSE-BSD-glMatrix create mode 100644 rabbitmq-server-3.3.5/LICENSE-MIT-EJS10 create mode 100644 rabbitmq-server-3.3.5/LICENSE-MIT-Flot create mode 100644 rabbitmq-server-3.3.5/LICENSE-MIT-Mochi create mode 100644 rabbitmq-server-3.3.5/LICENSE-MIT-Sammy060 create mode 100644 rabbitmq-server-3.3.5/LICENSE-MIT-eldap create mode 100644 rabbitmq-server-3.3.5/LICENSE-MIT-jQuery164 create mode 100644 rabbitmq-server-3.3.5/LICENSE-MPL-RabbitMQ create mode 100644 rabbitmq-server-3.3.5/Makefile create mode 100644 rabbitmq-server-3.3.5/README create mode 100755 rabbitmq-server-3.3.5/calculate-relative create mode 100644 rabbitmq-server-3.3.5/codegen.py create mode 100644 rabbitmq-server-3.3.5/codegen/LICENSE create mode 100644 rabbitmq-server-3.3.5/codegen/LICENSE-MPL-RabbitMQ create mode 100644 rabbitmq-server-3.3.5/codegen/Makefile create mode 100644 rabbitmq-server-3.3.5/codegen/README.extensions.md create mode 100644 rabbitmq-server-3.3.5/codegen/amqp-rabbitmq-0.8.json create mode 100644 rabbitmq-server-3.3.5/codegen/amqp-rabbitmq-0.9.1.json create mode 100644 rabbitmq-server-3.3.5/codegen/amqp_codegen.py create mode 100644 rabbitmq-server-3.3.5/codegen/credit_extension.json create mode 100644 rabbitmq-server-3.3.5/codegen/demo_extension.json create mode 100644 rabbitmq-server-3.3.5/codegen/license_info create mode 100644 rabbitmq-server-3.3.5/docs/examples-to-end.xsl create mode 100644 rabbitmq-server-3.3.5/docs/html-to-website-xml.xsl create mode 100644 rabbitmq-server-3.3.5/docs/rabbitmq-echopid.xml create mode 100644 rabbitmq-server-3.3.5/docs/rabbitmq-env.conf.5.xml create mode 100644 rabbitmq-server-3.3.5/docs/rabbitmq-plugins.1.xml create mode 100644 rabbitmq-server-3.3.5/docs/rabbitmq-server.1.xml create mode 100644 rabbitmq-server-3.3.5/docs/rabbitmq-service.xml create mode 100644 rabbitmq-server-3.3.5/docs/rabbitmq.config.example create mode 100644 rabbitmq-server-3.3.5/docs/rabbitmqctl.1.xml create mode 100644 rabbitmq-server-3.3.5/docs/remove-namespaces.xsl create mode 100644 rabbitmq-server-3.3.5/docs/usage.xsl create mode 100644 rabbitmq-server-3.3.5/ebin/rabbit_app.in create mode 100644 rabbitmq-server-3.3.5/generate_app create mode 100644 rabbitmq-server-3.3.5/generate_deps create mode 100644 rabbitmq-server-3.3.5/include/gm_specs.hrl create mode 100644 rabbitmq-server-3.3.5/include/rabbit.hrl create mode 100644 rabbitmq-server-3.3.5/include/rabbit_msg_store.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/README create mode 100644 rabbitmq-server-3.3.5/plugins-src/all-packages.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/common.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0001-R12-fake-iodata-type.patch create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0002-R12-drop-all-references-to-boolean-type.patch create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0003-R12-drop-all-references-to-reference-type.patch create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0004-R12-drop-references-to-iodata-type.patch create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0005-R12-drop-references-to-Default-any-type.patch create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0006-Use-erlang-integer_to_list-and-lists-max-instead-of-.patch create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0007-R12-type-definitions-must-be-ordered.patch create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0008-sec-websocket-protocol.patch create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/README.md create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/.done create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/.travis.yml create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/AUTHORS create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/CHANGELOG.md create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/LICENSE create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/README.md create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/cover.spec create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/doc/overview.edoc create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/include/http.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/rebar.config create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_acceptor.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_acceptors_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_app.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_bstr.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_clock.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_cookies.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_dispatcher.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_handler.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_protocol.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_req.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_req.erl.orig create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_rest.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_static.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_websocket.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_websocket_handler.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_listener.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_listener_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_multipart.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_protocol.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_requests_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_ssl_transport.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_tcp_transport.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/chunked_handler.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/dispatcher_prop.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_SUITE.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_SUITE_data/cert.pem create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_SUITE_data/key.pem create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_errors.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_init_shutdown.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_long_polling.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_multipart.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_set_resp.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_stream_body.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/proper_SUITE.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/rest_forbidden_resource.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/rest_simple_resource.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/websocket_handler.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/websocket_handler_init_shutdown.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/ws_SUITE.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/ws_timeout_hibernate_handler.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/hash.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/do-package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/LICENSE-MIT-eldap create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-appify.patch create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/.done create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/LICENSE create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/README create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/doc/README.example create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/doc/short-desc create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/ebin/eldap.app create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/include/eldap.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/src/ELDAPv3.asn create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/src/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/src/eldap.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/README.test create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/bill.ldif create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/bluetail.ldif create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/crl.ldif create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/eldap_test.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/ldap.rc create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/people.ldif create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/post_danmark.ldif create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/server1.crl create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/slapd.conf create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/tobbe.ldif create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-no-ssl-seed.patch create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/hash.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/license_info create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/remove-eldap-fsm.patch create mode 100644 rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/remove-ietf-doc.patch create mode 100644 rabbitmq-server-3.3.5/plugins-src/generate_app create mode 100644 rabbitmq-server-3.3.5/plugins-src/generate_deps create mode 100644 rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-APACHE2-ExplorerCanvas create mode 100644 rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-APL2-Stomp-Websocket create mode 100644 rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-Apache-Basho create mode 100644 rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-BSD-base64js create mode 100644 rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-BSD-glMatrix create mode 100644 rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-EJS10 create mode 100644 rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-Flot create mode 100644 rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-Mochi create mode 100644 rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-Sammy060 create mode 100644 rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-eldap create mode 100644 rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-jQuery164 create mode 100644 rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MPL-RabbitMQ create mode 100644 rabbitmq-server-3.3.5/plugins-src/licensing/license_info_eldap-wrapper create mode 100644 rabbitmq-server-3.3.5/plugins-src/licensing/license_info_mochiweb-wrapper create mode 100644 rabbitmq-server-3.3.5/plugins-src/licensing/license_info_rabbitmq-management create mode 100644 rabbitmq-server-3.3.5/plugins-src/licensing/license_info_rabbitmq-management-visualiser create mode 100644 rabbitmq-server-3.3.5/plugins-src/licensing/license_info_webmachine-wrapper create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/10-build-on-R12B-5.patch create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/20-MAX_RECV_BODY.patch create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/30-remove-crypto-ssl-dependencies.patch create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/40-remove-compiler-syntax_tools-dependencies.patch create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/50-remove-json.patch create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/LICENSE-MIT-Mochi create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/hash.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/license_info create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/.done create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/.travis.yml create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/CHANGES.md create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/LICENSE create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/README create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/hmac_api/README create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/hmac_api/hmac_api.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/hmac_api/hmac_api_client.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/hmac_api/hmac_api_lib.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/https/https_store.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/https/server_cert.pem create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/https/server_key.pem create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/keepalive/keepalive.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/include/internal.hrl create mode 100755 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/rebar create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/rebar.config create mode 100755 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/scripts/entities.erl create mode 100755 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/scripts/new_mochiweb.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochifmt.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochifmt_records.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochifmt_std.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochihex.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochijson.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochilists.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochilogfile2.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochitemp.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiutf8.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_acceptor.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_base64url.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_charref.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_cookies.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_cover.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_echo.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_headers.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_html.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_http.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_io.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_mime.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_multipart.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_request.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_request.erl.orig create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_request_tests.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_response.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_session.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_socket.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_socket_server.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_util.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/reloader.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp.template create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/priv/www/index.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/rebar.config create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp_app.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp_deps.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp_web.erl create mode 100755 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/start-dev.sh create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/test-materials/test_ssl_cert.pem create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/test-materials/test_ssl_key.pem create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/test/mochiweb_base64url_tests.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/test/mochiweb_html_tests.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/test/mochiweb_http_tests.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/test/mochiweb_tests.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/README.md create mode 100755 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/codegen.py create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/include/rabbit_amqp1_0.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/spec/messaging.xml create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/spec/security.xml create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/spec/transactions.xml create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/spec/transport.xml create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/spec/types.xml create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/src/rabbit_amqp1_0_binary_generator.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/src/rabbit_amqp1_0_binary_parser.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/src/rabbit_amqp1_0_channel.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/src/rabbit_amqp1_0_framing.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/src/rabbit_amqp1_0_incoming_link.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/src/rabbit_amqp1_0_link_util.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/src/rabbit_amqp1_0_message.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/src/rabbit_amqp1_0_outgoing_link.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/src/rabbit_amqp1_0_reader.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/src/rabbit_amqp1_0_session.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/src/rabbit_amqp1_0_session_process.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/src/rabbit_amqp1_0_session_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/src/rabbit_amqp1_0_session_sup_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/src/rabbit_amqp1_0_util.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/src/rabbit_amqp1_0_writer.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/src/rabbitmq_amqp1_0.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/test/lib-java/junit.jar create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/test/proton/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/test/proton/build.xml create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/test/proton/test/com/rabbitmq/amqp1_0/tests/proton/ProtonTests.java create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/test/src/rabbit_amqp1_0_test.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/test/swiftmq/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/test/swiftmq/build.xml create mode 100755 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/test/swiftmq/run-tests.sh create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-amqp1.0/test/swiftmq/test/com/rabbitmq/amqp1_0/tests/swiftmq/SwiftMQTests.java create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-backend-ldap/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-backend-ldap/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-backend-ldap/README create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-backend-ldap/README-authorisation create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-backend-ldap/README-tests create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-backend-ldap/etc/rabbit-test.config create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-backend-ldap/example/README create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-backend-ldap/example/global.ldif create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-backend-ldap/example/groups.ldif create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-backend-ldap/example/people.ldif create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-backend-ldap/example/rabbit.ldif create mode 100755 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-backend-ldap/example/setup.sh create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-backend-ldap/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-backend-ldap/src/rabbit_auth_backend_ldap.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-backend-ldap/src/rabbit_auth_backend_ldap_app.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-backend-ldap/src/rabbit_auth_backend_ldap_util.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-backend-ldap/src/rabbitmq_auth_backend_ldap.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-backend-ldap/test/src/rabbit_auth_backend_ldap_test.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-mechanism-ssl/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-mechanism-ssl/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-mechanism-ssl/README create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-mechanism-ssl/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-mechanism-ssl/src/rabbit_auth_mechanism_ssl.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-mechanism-ssl/src/rabbit_auth_mechanism_ssl_app.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-auth-mechanism-ssl/src/rabbitmq_auth_mechanism_ssl.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-consistent-hash-exchange/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-consistent-hash-exchange/LICENSE create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-consistent-hash-exchange/LICENSE-MPL-RabbitMQ create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-consistent-hash-exchange/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-consistent-hash-exchange/README.md create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-consistent-hash-exchange/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-consistent-hash-exchange/src/rabbit_exchange_type_consistent_hash.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-consistent-hash-exchange/src/rabbitmq_consistent_hash_exchange.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-consistent-hash-exchange/test/src/rabbit_exchange_type_consistent_hash_test.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/Makefile.in create mode 100755 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/README.in create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/common.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/ebin/amqp_client.app.in create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/include/amqp_client.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/include/amqp_client_internal.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/include/amqp_gen_consumer_spec.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/include/rabbit_routing_prefixes.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/rabbit_common.app.in create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/amqp_auth_mechanisms.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/amqp_channel.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/amqp_channel_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/amqp_channel_sup_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/amqp_channels_manager.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/amqp_client.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/amqp_connection.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/amqp_connection_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/amqp_connection_type_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/amqp_direct_connection.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/amqp_direct_consumer.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/amqp_gen_connection.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/amqp_gen_consumer.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/amqp_main_reader.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/amqp_network_connection.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/amqp_rpc_client.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/amqp_rpc_server.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/amqp_selective_consumer.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/amqp_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/amqp_uri.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/overview.edoc.in create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/rabbit_routing_util.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/src/uri_parser.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/test.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/test/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/test/amqp_client_SUITE.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/test/amqp_dbg.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/test/negative_test_util.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-erlang-client/test/test_util.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation-management/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation-management/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation-management/README create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation-management/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation-management/priv/www/js/federation.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation-management/priv/www/js/tmpl/federation-upstream.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation-management/priv/www/js/tmpl/federation-upstreams.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation-management/priv/www/js/tmpl/federation.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation-management/src/rabbit_federation_mgmt.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation-management/src/rabbitmq_federation_management.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/README create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/README-hacking create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/etc/rabbit-test.sh create mode 100755 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/etc/setup-rabbit-test.sh create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/include/rabbit_federation.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/src/rabbit_federation_app.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/src/rabbit_federation_db.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/src/rabbit_federation_event.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/src/rabbit_federation_exchange.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/src/rabbit_federation_exchange_link.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/src/rabbit_federation_exchange_link_sup_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/src/rabbit_federation_link_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/src/rabbit_federation_link_util.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/src/rabbit_federation_parameters.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/src/rabbit_federation_queue.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/src/rabbit_federation_queue_link.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/src/rabbit_federation_queue_link_sup_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/src/rabbit_federation_status.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/src/rabbit_federation_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/src/rabbit_federation_upstream.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/src/rabbit_federation_upstream_exchange.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/src/rabbit_federation_util.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/src/rabbitmq_federation.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/test/src/rabbit_federation_exchange_test.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/test/src/rabbit_federation_queue_test.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/test/src/rabbit_federation_test_util.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-federation/test/src/rabbit_federation_unit_test.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-agent/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-agent/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-agent/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-agent/src/rabbit_mgmt_agent_app.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-agent/src/rabbit_mgmt_agent_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-agent/src/rabbit_mgmt_db_handler.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-agent/src/rabbit_mgmt_external_stats.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-agent/src/rabbitmq_management_agent.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-visualiser/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-visualiser/LICENSE create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-visualiser/LICENSE-BSD-glMatrix create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-visualiser/LICENSE-MPL-RabbitMQ create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-visualiser/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-visualiser/README create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-visualiser/license_info create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-visualiser/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-visualiser/priv/www/js/visualiser.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-visualiser/priv/www/visualiser/index.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-visualiser/priv/www/visualiser/js/glMatrix-min.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-visualiser/priv/www/visualiser/js/glMatrix.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-visualiser/priv/www/visualiser/js/main.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-visualiser/priv/www/visualiser/js/model.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-visualiser/priv/www/visualiser/js/octtree.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-visualiser/priv/www/visualiser/js/physics.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-visualiser/src/rabbit_mgmt_wm_all.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-visualiser/src/rabbit_visualiser_mgmt.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management-visualiser/src/rabbitmq_management_visualiser.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/LICENSE create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/LICENSE-APACHE2-ExplorerCanvas create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/LICENSE-BSD-base64js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/LICENSE-MIT-EJS10 create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/LICENSE-MIT-Flot create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/LICENSE-MIT-Sammy060 create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/LICENSE-MIT-jQuery164 create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/LICENSE-MPL-RabbitMQ create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/README create mode 100755 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/bin/rabbitmqadmin create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/etc/bunny.config create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/etc/hare.config create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/etc/rabbit-test.config create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/include/rabbit_mgmt.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/include/rabbit_mgmt_test.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/license_info create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/api/index.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/cli/index.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/css/evil.css create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/css/main.css create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/doc/stats.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/favicon.ico create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/img/bg-green-dark.png create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/img/bg-red-dark.png create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/img/bg-red.png create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/img/bg-yellow-dark.png create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/img/collapse.png create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/img/expand.png create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/img/rabbitmqlogo.png create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/index.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/base64.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/charts.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/dispatcher.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/ejs.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/ejs.min.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/excanvas.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/excanvas.min.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/formatters.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/global.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/help.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/jquery-1.6.4.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/jquery-1.6.4.min.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/jquery.flot.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/jquery.flot.min.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/jquery.flot.time.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/jquery.flot.time.min.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/json2.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/main.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/prefs.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/sammy-0.6.0.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/sammy-0.6.0.min.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/404.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/add-binding.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/bindings.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/channel.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/channels-list.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/channels.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/cluster-name.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/connection.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/connections.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/consumers.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/error-popup.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/exchange.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/exchanges.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/import-succeeded.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/layout.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/login.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/memory.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/messages.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/msg-detail-deliveries.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/msg-detail-publishes.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/node.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/overview.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/partition.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/permissions.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/policies.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/policy.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/publish.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/queue.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/queues.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/rate-options.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/registry.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/status.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/user.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/users.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/vhost.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/vhosts.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_app.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_db.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_dispatcher.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_extension.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_format.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_load_definitions.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_stats.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_sup_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_util.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_aliveness_test.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_binding.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_bindings.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_channel.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_channels.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_cluster_name.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_connection.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_connection_channels.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_connections.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_definitions.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_exchange.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_exchange_publish.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_exchanges.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_extensions.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_node.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_nodes.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_overview.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_parameter.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_parameters.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_permission.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_permissions.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_permissions_user.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_permissions_vhost.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_policies.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_policy.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_queue.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_queue_actions.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_queue_get.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_queue_purge.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_queues.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_user.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_users.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_vhost.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_vhosts.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_whoami.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbitmq_management.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/default-config create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_clustering.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_db.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_db_unit.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_http.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_unit.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_util.erl create mode 100755 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbitmqadmin-test.py create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/test-config create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/README.md create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/include/rabbit_mqtt.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/include/rabbit_mqtt_frame.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/lib/junit.jar create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_collector.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_connection_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_frame.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_processor.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_reader.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_util.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbitmq_mqtt.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/test/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/test/src/com/rabbitmq/mqtt/test/MqttTest.java create mode 100755 rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/test/test.sh create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/README create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/etc/rabbit-test.config create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/priv/www/js/shovel.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/priv/www/js/tmpl/dynamic-shovel.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/priv/www/js/tmpl/dynamic-shovels.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/priv/www/js/tmpl/shovels.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/src/rabbit_shovel_mgmt.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/src/rabbitmq_shovel_management.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/test/src/rabbit_shovel_mgmt_test_all.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/test/src/rabbit_shovel_mgmt_test_http.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/README create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/generate_deps create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/include/rabbit_shovel.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_config.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_dyn_worker_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_dyn_worker_sup_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_parameters.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_status.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_worker.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_worker_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbitmq_shovel.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/test/src/rabbit_shovel_test.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/test/src/rabbit_shovel_test_all.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/test/src/rabbit_shovel_test_dyn.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/NOTES create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/README.md create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/deps/stomppy/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/deps/stomppy/rabbit.patch create mode 100755 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_recv.pl create mode 100755 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_rpc_client.pl create mode 100755 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_rpc_service.pl create mode 100755 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_send.pl create mode 100755 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_send_many.pl create mode 100755 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_slow_recv.pl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/cb-receiver.rb create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/cb-sender.rb create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/cb-slow-receiver.rb create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/persistent-receiver.rb create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/persistent-sender.rb create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/topic-broadcast-receiver.rb create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/topic-broadcast-with-unsubscribe.rb create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/topic-sender.rb create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/include/rabbit_stomp.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/include/rabbit_stomp_frame.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/include/rabbit_stomp_headers.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_client_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_frame.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_processor.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_reader.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_util.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbitmq_stomp.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/ack.py create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/base.py create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/connect_options.py create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/destinations.py create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/errors.py create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/lifecycle.py create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/non_ssl.config create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/parsing.py create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_amqqueue_test.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_client.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_publish_test.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_test.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_test_frame.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_test_util.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/reliability.py create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/ssl.config create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/ssl_lifecycle.py create mode 100755 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/test.py create mode 100755 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/test_connect_options.py create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/test_runner.py create mode 100755 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/test_ssl.py create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/test_util.py create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/transactions.py create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/README create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/certs/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/certs/openssl.cnf create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/qpid_config.py create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/qpid_patch create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/rabbit_failing.txt create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/inet_proxy_dist.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/inet_tcp_proxy.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/inet_tcp_proxy_manager.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbit_ha_test_consumer.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbit_ha_test_producer.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbit_test_configs.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbit_test_runner.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbit_test_util.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbitmq_test.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/clustering_management.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/dynamic_ha.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/eager_sync.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/many_node_ha.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/partitions.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/simple_ha.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/sync_detection.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/README create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/priv/www/js/tmpl/traces.ejs create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/priv/www/js/tracing.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_app.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_consumer.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_consumer_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_files.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_mgmt.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_traces.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_wm_file.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_wm_files.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_wm_trace.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_wm_traces.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbitmq_tracing.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/test/src/rabbit_tracing_test.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/LICENSE create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/README.md create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_web_dispatch.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_web_dispatch_app.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_web_dispatch_registry.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_web_dispatch_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_web_dispatch_util.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_webmachine.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbitmq_web_dispatch.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/test/priv/www/index.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/test/src/rabbit_web_dispatch_test.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/test/src/rabbit_web_dispatch_test_unit.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/LICENSE create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/LICENSE-APL2-Stomp-Websocket create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/LICENSE-MPL-RabbitMQ create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/README.md create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/bunny.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/bunny.png create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/echo.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/index.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/main.css create mode 100755 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/pencil.cur create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/stomp.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/temp-queue.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/src/rabbit_web_stomp_examples_app.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/src/rabbitmq_web_stomp_examples.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/LICENSE create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/LICENSE-MPL-RabbitMQ create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/README.md create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/src/rabbit_ws_app.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/src/rabbit_ws_client.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/src/rabbit_ws_client_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/src/rabbit_ws_sockjs.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/src/rabbit_ws_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/src/rabbitmq_web_stomp.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/test/src/rabbit_ws_test_all.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/test/src/rabbit_ws_test_raw_websocket.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/test/src/rabbit_ws_test_sockjs_websocket.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/test/src/rfc6455_client.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/test/src/stomp.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/release.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/0000-remove-spec-patch.diff create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/0001-a2b-b2a.diff create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/0002-parameterised-modules-r16a.diff create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/0003-websocket-subprotocol create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/generate-0000-remove-spec-patch.sh create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/hash.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/.done create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/COPYING create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/Changelog create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/LICENSE-APL2-Rebar create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/LICENSE-MIT-Mochiweb create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/LICENSE-MIT-SockJS create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/README.md create mode 100755 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/cowboy_echo.erl create mode 100755 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/cowboy_test_server.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/echo.html create mode 100755 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/multiplex/cowboy_multiplex.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/multiplex/index.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/multiplex/multiplex.js create mode 100755 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/rebar create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/rebar.config create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/mochijson2_fork.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/mochinum_fork.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/pmod_pt.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_action.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_app.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_cowboy_handler.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_filters.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_handler.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_http.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_internal.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_json.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_multiplex.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_multiplex_channel.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_service.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_session.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_session_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_util.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_ws_handler.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/umbrella.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/.srcdist_done create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/10-remove-crypto-dependency.patch create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/LICENSE-Apache-Basho create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/hash.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/license_info create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/package.mk create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/.done create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/.travis.yml create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/Emakefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/LICENSE create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/README.org create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/THANKS create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/README create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/priv/dispatch.conf create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/rebar.config create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo_app.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo_fs_resource.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo_resource.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo_sup.erl create mode 100755 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/start.sh create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/docs/http-headers-status-v3.png create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/include/webmachine.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/include/webmachine_logger.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/include/wm_reqdata.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/include/wm_reqstate.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/include/wm_resource.hrl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/Makefile create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/README create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/priv/dispatch.conf create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/rebar.config create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/src/wmskel.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/src/wmskel.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/src/wmskel_app.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/src/wmskel_resource.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/src/wmskel_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/start.sh create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/wmskel.template create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/trace/http-headers-status-v3.png create mode 100755 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/trace/wmtrace.css create mode 100755 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/trace/wmtrace.js create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/www/index.html create mode 100755 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/rebar create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/rebar.config create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/rebar.config.script create mode 100755 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/scripts/new_webmachine.sh create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/webmachine.app.src create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/webmachine.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/webmachine_app.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/webmachine_decision_core.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/webmachine_deps.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/webmachine_dispatcher.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/webmachine_error.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/webmachine_error_handler.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/webmachine_log.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/webmachine_log_handler.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/webmachine_logger_watcher.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/webmachine_logger_watcher_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/webmachine_mochiweb.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/webmachine_multipart.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/webmachine_perf_log_handler.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/webmachine_request.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/webmachine_resource.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/webmachine_router.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/webmachine_sup.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/webmachine_util.erl create mode 100755 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/wmtrace_resource.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/src/wrq.erl create mode 100755 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/start-dev.sh create mode 100755 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/start.sh create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/test/etag_test.erl create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/blogs.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/contact.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/css/style-1c.css create mode 100755 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/css/style.css create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/debugging.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/diagram.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/dispatcher.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/docs.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/example_resources.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/favicon.ico create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/images/WM200-crop.png create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/images/basho-landscape.gif create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/images/basic-trace-decision-tab.png create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/images/basic-trace-labeled.png create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/images/basic-trace-request-tab.png create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/images/basic-trace-response-tab.png create mode 100755 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/images/bg.gif create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/images/blankbox.gif create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/images/chash.gif create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/images/easy-ops.gif create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/images/gossip4.gif create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/images/halfblankbox.gif create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/images/http-headers-status-v3.png create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/images/more.gif create mode 100755 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/images/site.gif create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/images/splash250.gif create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/images/vclock.gif create mode 100755 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/index.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/intros.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/mechanics.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/quickstart.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/reftrans.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/reqdata.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/resources.html create mode 100644 rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/www/streambody.html create mode 100755 rabbitmq-server-3.3.5/scripts/rabbitmq-defaults create mode 100755 rabbitmq-server-3.3.5/scripts/rabbitmq-echopid.bat create mode 100755 rabbitmq-server-3.3.5/scripts/rabbitmq-env create mode 100755 rabbitmq-server-3.3.5/scripts/rabbitmq-plugins create mode 100755 rabbitmq-server-3.3.5/scripts/rabbitmq-plugins.bat create mode 100755 rabbitmq-server-3.3.5/scripts/rabbitmq-server create mode 100755 rabbitmq-server-3.3.5/scripts/rabbitmq-server.bat create mode 100755 rabbitmq-server-3.3.5/scripts/rabbitmq-service.bat create mode 100755 rabbitmq-server-3.3.5/scripts/rabbitmqctl create mode 100755 rabbitmq-server-3.3.5/scripts/rabbitmqctl.bat create mode 100644 rabbitmq-server-3.3.5/src/app_utils.erl create mode 100644 rabbitmq-server-3.3.5/src/background_gc.erl create mode 100644 rabbitmq-server-3.3.5/src/credit_flow.erl create mode 100644 rabbitmq-server-3.3.5/src/delegate.erl create mode 100644 rabbitmq-server-3.3.5/src/delegate_sup.erl create mode 100644 rabbitmq-server-3.3.5/src/dtree.erl create mode 100644 rabbitmq-server-3.3.5/src/file_handle_cache.erl create mode 100644 rabbitmq-server-3.3.5/src/gatherer.erl create mode 100644 rabbitmq-server-3.3.5/src/gen_server2.erl create mode 100644 rabbitmq-server-3.3.5/src/gm.erl create mode 100644 rabbitmq-server-3.3.5/src/gm_soak_test.erl create mode 100644 rabbitmq-server-3.3.5/src/gm_speed_test.erl create mode 100644 rabbitmq-server-3.3.5/src/gm_tests.erl create mode 100644 rabbitmq-server-3.3.5/src/lqueue.erl create mode 100644 rabbitmq-server-3.3.5/src/mirrored_supervisor.erl create mode 100644 rabbitmq-server-3.3.5/src/mirrored_supervisor_tests.erl create mode 100644 rabbitmq-server-3.3.5/src/mnesia_sync.erl create mode 100644 rabbitmq-server-3.3.5/src/mochijson2.erl create mode 100644 rabbitmq-server-3.3.5/src/mochinum.erl create mode 100644 rabbitmq-server-3.3.5/src/pg2_fixed.erl create mode 100644 rabbitmq-server-3.3.5/src/pg_local.erl create mode 100644 rabbitmq-server-3.3.5/src/pmon.erl create mode 100644 rabbitmq-server-3.3.5/src/priority_queue.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_access_control.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_alarm.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_amqqueue.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_amqqueue_process.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_amqqueue_sup.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_auth_backend.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_auth_backend_dummy.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_auth_backend_internal.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_auth_mechanism.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_auth_mechanism_amqplain.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_auth_mechanism_cr_demo.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_auth_mechanism_plain.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_autoheal.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_backing_queue.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_backing_queue_qc.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_basic.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_binary_generator.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_binary_parser.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_binding.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_channel.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_channel_interceptor.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_channel_sup.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_channel_sup_sup.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_client_sup.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_command_assembler.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_connection_helper_sup.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_connection_sup.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_control_main.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_dead_letter.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_diagnostics.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_direct.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_disk_monitor.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_error_logger.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_error_logger_file_h.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_event.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_exchange.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_exchange_decorator.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_exchange_type.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_exchange_type_direct.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_exchange_type_fanout.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_exchange_type_headers.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_exchange_type_invalid.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_exchange_type_topic.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_file.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_framing.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_guid.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_heartbeat.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_limiter.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_log.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_memory_monitor.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_mirror_queue_coordinator.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_mirror_queue_master.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_mirror_queue_misc.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_mirror_queue_mode.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_mirror_queue_mode_all.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_mirror_queue_mode_exactly.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_mirror_queue_mode_nodes.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_mirror_queue_slave.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_mirror_queue_slave_sup.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_mirror_queue_sync.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_misc.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_mnesia.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_msg_file.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_msg_store.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_msg_store_ets_index.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_msg_store_gc.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_msg_store_index.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_net.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_networking.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_node_monitor.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_nodes.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_parameter_validation.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_plugins.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_plugins_main.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_policies.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_policy.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_policy_validator.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_prelaunch.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_queue_collector.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_queue_consumers.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_queue_decorator.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_queue_index.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_reader.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_recovery_terms.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_registry.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_restartable_sup.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_router.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_runtime_parameter.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_runtime_parameters.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_runtime_parameters_test.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_sasl_report_file_h.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_ssl.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_sup.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_table.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_tests.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_tests_event_receiver.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_trace.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_types.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_upgrade.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_upgrade_functions.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_variable_queue.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_version.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_vhost.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_vm.erl create mode 100644 rabbitmq-server-3.3.5/src/rabbit_writer.erl create mode 100644 rabbitmq-server-3.3.5/src/supervised_lifecycle.erl create mode 100644 rabbitmq-server-3.3.5/src/supervisor2.erl create mode 100644 rabbitmq-server-3.3.5/src/supervisor2_tests.erl create mode 100644 rabbitmq-server-3.3.5/src/tcp_acceptor.erl create mode 100644 rabbitmq-server-3.3.5/src/tcp_acceptor_sup.erl create mode 100644 rabbitmq-server-3.3.5/src/tcp_listener.erl create mode 100644 rabbitmq-server-3.3.5/src/tcp_listener_sup.erl create mode 100644 rabbitmq-server-3.3.5/src/test_sup.erl create mode 100644 rabbitmq-server-3.3.5/src/truncate.erl create mode 100644 rabbitmq-server-3.3.5/src/vm_memory_monitor.erl create mode 100644 rabbitmq-server-3.3.5/src/vm_memory_monitor_tests.erl create mode 100644 rabbitmq-server-3.3.5/src/worker_pool.erl create mode 100644 rabbitmq-server-3.3.5/src/worker_pool_sup.erl create mode 100644 rabbitmq-server-3.3.5/src/worker_pool_worker.erl create mode 100644 rabbitmq-server-3.3.5/version.mk diff --git a/debian/LICENSE.head b/debian/LICENSE.head new file mode 100644 index 0000000..2b5a17e --- /dev/null +++ b/debian/LICENSE.head @@ -0,0 +1,5 @@ +This package, the RabbitMQ server is licensed under the MPL. + +If you have any questions regarding licensing, please contact us at +info@rabbitmq.com. + diff --git a/debian/LICENSE.tail b/debian/LICENSE.tail new file mode 100644 index 0000000..7858a04 --- /dev/null +++ b/debian/LICENSE.tail @@ -0,0 +1,516 @@ + +The MIT license is as follows: + + "Permission is hereby granted, free of charge, to any person + obtaining a copy of this file (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." + + +The BSD 2-Clause license is as follows: + + "Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + + +The rest of this package is licensed under the Mozilla Public License 1.1 +Authors and Copyright are as described below: + + The Initial Developer of the Original Code is GoPivotal, Inc. + Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. + + + MOZILLA PUBLIC LICENSE + Version 1.1 + + --------------- + +1. Definitions. + + 1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. + + 1.1. "Contributor" means each entity that creates or contributes to + the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications + made by that particular Contributor. + + 1.3. "Covered Code" means the Original Code or Modifications or the + combination of the Original Code and Modifications, in each case + including portions thereof. + + 1.4. "Electronic Distribution Mechanism" means a mechanism generally + accepted in the software development community for the electronic + transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source + Code. + + 1.6. "Initial Developer" means the individual or entity identified + as the Initial Developer in the Source Code notice required by Exhibit + A. + + 1.7. "Larger Work" means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + + 1.8. "License" means this document. + + 1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + A. Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. + + B. Any new file that contains any part of the Original Code or + previous Modifications. + + 1.10. "Original Code" means Source Code of computer software code + which is described in the Source Code notice required by Exhibit A as + Original Code, and which, at the time of its release under this + License is not already Covered Code governed by this License. + + 1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, + and apparatus claims, in any patent Licensable by grantor. + + 1.11. "Source Code" means the preferred form of the Covered Code for + making modifications to it, including all modules it contains, plus + any associated interface definition files, scripts used to control + compilation and installation of an Executable, or source code + differential comparisons against either the Original Code or another + well known, available Covered Code of the Contributor's choice. The + Source Code can be in a compressed or archival form, provided the + appropriate decompression or de-archiving software is widely available + for no charge. + + 1.12. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License or a future version of this License issued under Section 6.1. + For legal entities, "You" includes any entity which controls, is + controlled by, or is under common control with You. For purposes of + this definition, "control" means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty percent + (50%) of the outstanding shares or beneficial ownership of such + entity. + +2. Source Code License. + + 2.1. The Initial Developer Grant. + The Initial Developer hereby grants You a world-wide, royalty-free, + non-exclusive license, subject to third party intellectual property + claims: + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original + Code (or portions thereof) with or without Modifications, and/or + as part of a Larger Work; and + + (b) under Patents Claims infringed by the making, using or + selling of Original Code, to make, have made, use, practice, + sell, and offer for sale, and/or otherwise dispose of the + Original Code (or portions thereof). + + (c) the licenses granted in this Section 2.1(a) and (b) are + effective on the date Initial Developer first distributes + Original Code under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) + separate from the Original Code; or 3) for infringements caused + by: i) the modification of the Original Code or ii) the + combination of the Original Code with other software or devices. + + 2.2. Contributor Grant. + Subject to third party intellectual property claims, each Contributor + hereby grants You a world-wide, royalty-free, non-exclusive license + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor, to use, reproduce, modify, + display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an + unmodified basis, with other Modifications, as Covered Code + and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either alone + and/or in combination with its Contributor Version (or portions + of such combination), to make, use, sell, offer for sale, have + made, and/or otherwise dispose of: 1) Modifications made by that + Contributor (or portions thereof); and 2) the combination of + Modifications made by that Contributor with its Contributor + Version (or portions of such combination). + + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of + the Covered Code. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: 1) for any code that Contributor has deleted from the + Contributor Version; 2) separate from the Contributor Version; + 3) for infringements caused by: i) third party modifications of + Contributor Version or ii) the combination of Modifications made + by that Contributor with other software (except as part of the + Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by + that Contributor. + +3. Distribution Obligations. + + 3.1. Application of License. + The Modifications which You create or to which You contribute are + governed by the terms of this License, including without limitation + Section 2.2. The Source Code version of Covered Code may be + distributed only under the terms of this License or a future version + of this License released under Section 6.1, and You must include a + copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code + version that alters or restricts the applicable version of this + License or the recipients' rights hereunder. However, You may include + an additional document offering the additional rights described in + Section 3.5. + + 3.2. Availability of Source Code. + Any Modification which You create or to which You contribute must be + made available in Source Code form under the terms of this License + either on the same media as an Executable version or via an accepted + Electronic Distribution Mechanism to anyone to whom you made an + Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) + months after the date it initially became available, or at least six + (6) months after a subsequent version of that particular Modification + has been made available to such recipients. You are responsible for + ensuring that the Source Code version remains available even if the + Electronic Distribution Mechanism is maintained by a third party. + + 3.3. Description of Modifications. + You must cause all Covered Code to which You contribute to contain a + file documenting the changes You made to create that Covered Code and + the date of any change. You must include a prominent statement that + the Modification is derived, directly or indirectly, from Original + Code provided by the Initial Developer and including the name of the + Initial Developer in (a) the Source Code, and (b) in any notice in an + Executable version or related documentation in which You describe the + origin or ownership of the Covered Code. + + 3.4. Intellectual Property Matters + (a) Third Party Claims. + If Contributor has knowledge that a license under a third party's + intellectual property rights is required to exercise the rights + granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code + distribution titled "LEGAL" which describes the claim and the + party making the claim in sufficient detail that a recipient will + know whom to contact. If Contributor obtains such knowledge after + the Modification is made available as described in Section 3.2, + Contributor shall promptly modify the LEGAL file in all copies + Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) + reasonably calculated to inform those who received the Covered + Code that new knowledge has been obtained. + + (b) Contributor APIs. + If Contributor's Modifications include an application programming + interface and Contributor has knowledge of patent licenses which + are reasonably necessary to implement that API, Contributor must + also include this information in the LEGAL file. + + (c) Representations. + Contributor represents that, except as disclosed pursuant to + Section 3.4(a) above, Contributor believes that Contributor's + Modifications are Contributor's original creation(s) and/or + Contributor has sufficient rights to grant the rights conveyed by + this License. + + 3.5. Required Notices. + You must duplicate the notice in Exhibit A in each file of the Source + Code. If it is not possible to put such notice in a particular Source + Code file due to its structure, then You must include such notice in a + location (such as a relevant directory) where a user would be likely + to look for such a notice. If You created one or more Modification(s) + You may add your name as a Contributor to the notice described in + Exhibit A. You must also duplicate this License in any documentation + for the Source Code where You describe recipients' rights or ownership + rights relating to Covered Code. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear than + any such warranty, support, indemnity or liability obligation is + offered by You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of warranty, + support, indemnity or liability terms You offer. + + 3.6. Distribution of Executable Versions. + You may distribute Covered Code in Executable form only if the + requirements of Section 3.1-3.5 have been met for that Covered Code, + and if You include a notice stating that the Source Code version of + the Covered Code is available under the terms of this License, + including a description of how and where You have fulfilled the + obligations of Section 3.2. The notice must be conspicuously included + in any notice in an Executable version, related documentation or + collateral in which You describe recipients' rights relating to the + Covered Code. You may distribute the Executable version of Covered + Code or ownership rights under a license of Your choice, which may + contain terms different from this License, provided that You are in + compliance with the terms of this License and that the license for the + Executable version does not attempt to limit or alter the recipient's + rights in the Source Code version from the rights set forth in this + License. If You distribute the Executable version under a different + license You must make it absolutely clear that any terms which differ + from this License are offered by You alone, not by the Initial + Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such + terms You offer. + + 3.7. Larger Works. + You may create a Larger Work by combining Covered Code with other code + not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Code. + +4. Inability to Comply Due to Statute or Regulation. + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description + must be included in the LEGAL file described in Section 3.4 and must + be included with all distributions of the Source Code. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Application of this License. + + This License applies to code to which the Initial Developer has + attached the notice in Exhibit A and to related Covered Code. + +6. Versions of the License. + + 6.1. New Versions. + Netscape Communications Corporation ("Netscape") may publish revised + and/or new versions of the License from time to time. Each version + will be given a distinguishing version number. + + 6.2. Effect of New Versions. + Once Covered Code has been published under a particular version of the + License, You may always continue to use it under the terms of that + version. You may also choose to use such Covered Code under the terms + of any subsequent version of the License published by Netscape. No one + other than Netscape has the right to modify the terms applicable to + Covered Code created under this License. + + 6.3. Derivative Works. + If You create or use a modified version of this License (which you may + only do in order to apply it to code which is not already Covered Code + governed by this License), You must (a) rename Your license so that + the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", + "MPL", "NPL" or any confusingly similar phrase do not appear in your + license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license + contains terms which differ from the Mozilla Public License and + Netscape Public License. (Filling in the name of the Initial + Developer, Original Code or Contributor in the notice described in + Exhibit A shall not of themselves be deemed to be modifications of + this License.) + +7. DISCLAIMER OF WARRANTY. + + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE + IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, + YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE + COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER + OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +8. TERMINATION. + + 8.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall + survive any termination of this License. Provisions which, by their + nature, must remain in effect beyond the termination of this License + shall survive. + + 8.2. If You initiate litigation by asserting a patent infringement + claim (excluding declatory judgment actions) against Initial Developer + or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + + (a) such Participant's Contributor Version directly or indirectly + infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License + shall, upon 60 days notice from Participant terminate prospectively, + unless if within 60 days after receipt of notice You either: (i) + agree in writing to pay Participant a mutually agreeable reasonable + royalty for Your past and future use of Modifications made by such + Participant, or (ii) withdraw Your litigation claim with respect to + the Contributor Version against such Participant. If within 60 days + of notice, a reasonable royalty and payment arrangement are not + mutually agreed upon in writing by the parties or the litigation claim + is not withdrawn, the rights granted by Participant to You under + Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + + (b) any software, hardware, or device, other than such Participant's + Contributor Version, directly or indirectly infringes any patent, then + any rights granted to You by such Participant under Sections 2.1(b) + and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that + Participant. + + 8.3. If You assert a patent infringement claim against Participant + alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as + by license or settlement) prior to the initiation of patent + infringement litigation, then the reasonable value of the licenses + granted by such Participant under Sections 2.1 or 2.2 shall be taken + into account in determining the amount or value of any payment or + license. + + 8.4. In the event of termination under Sections 8.1 or 8.2 above, + all end user license agreements (excluding distributors and resellers) + which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination. + +9. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, + OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR + ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY + CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, + WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE + EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO + THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +10. U.S. GOVERNMENT END USERS. + + The Covered Code is a "commercial item," as that term is defined in + 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer + software" and "commercial computer software documentation," as such + terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 + C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), + all U.S. Government End Users acquire Covered Code with only those + rights set forth herein. + +11. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed by + California law provisions (except to the extent applicable law, if + any, provides otherwise), excluding its conflict-of-law provisions. + With respect to disputes in which at least one party is a citizen of, + or an entity chartered or registered to do business in the United + States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern + District of California, with venue lying in Santa Clara County, + California, with the losing party responsible for costs, including + without limitation, court costs and reasonable attorneys' fees and + expenses. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. + Any law or regulation which provides that the language of a contract + shall be construed against the drafter shall not apply to this + License. + +12. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, + out of its utilization of rights under this License and You agree to + work with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + +13. MULTIPLE-LICENSED CODE. + + Initial Developer may designate portions of the Covered Code as + "Multiple-Licensed". "Multiple-Licensed" means that the Initial + Developer permits you to utilize portions of the Covered Code under + Your choice of the NPL or the alternative licenses, if any, specified + by the Initial Developer in the file described in Exhibit A. + +EXHIBIT A -Mozilla Public License. + + ``The contents of this file are subject to the Mozilla Public License + Version 1.1 (the "License"); you may not use this file except in + compliance with the License. You may obtain a copy of the License at + http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + License for the specific language governing rights and limitations + under the License. + + The Original Code is RabbitMQ. + + The Initial Developer of the Original Code is GoPivotal, Inc. + Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved.'' + + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.] diff --git a/debian/README b/debian/README new file mode 100644 index 0000000..0a29ee2 --- /dev/null +++ b/debian/README @@ -0,0 +1,20 @@ +This is rabbitmq-server, a message broker implementing AMQP, STOMP and MQTT. + +Most of the documentation for RabbitMQ is provided on the RabbitMQ web +site. You can see documentation for the current version at: + +http://www.rabbitmq.com/documentation.html + +and for previous versions at: + +http://www.rabbitmq.com/previous.html + +Man pages are installed with this package. Of particular interest are +rabbitmqctl(1), to interact with a running RabbitMQ server, and +rabbitmq-plugins(1), to enable and disable plugins. These should be +run as the superuser. + +An example configuration file is provided in the same directory as +this README. Copy it to /etc/rabbitmq/rabbitmq.config to use it. The +RabbitMQ server must be restarted after changing the configuration +file or enabling or disabling plugins. diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..c660575 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,325 @@ +rabbitmq-server (3.3.5-1~mos6.1) trusty; urgency=low + + * Pick the source from packages/precise/rabbitmq-server, commit + 55fbf9f2223244c363790e84913232adf9fed990. + * Bump the version for MOS 6.1 + + -- Alexei Sheplyakov Wed, 28 Jan 2015 17:48:06 +0300 + +rabbitmq-server (3.3.5-1) unstable; urgency=low + + * New Upstream Release + * Changed Uploaders from Emile Joubert to Blair Hester + + -- Simon MacMullen Mon, 11 Aug 2014 12:23:31 +0100 + +rabbitmq-server (3.3.4-1) unstable; urgency=low + + * New Upstream Release + + -- Simon MacMullen Tue, 24 Jun 2014 12:50:29 +0100 + +rabbitmq-server (3.3.3-1) unstable; urgency=low + + * New Upstream Release + + -- Simon MacMullen Mon, 16 Jun 2014 13:00:00 +0100 + +rabbitmq-server (3.3.2-1) unstable; urgency=low + + * New Upstream Release + + -- Simon MacMullen Mon, 09 Jun 2014 10:25:22 +0100 + +rabbitmq-server (3.3.1-1) unstable; urgency=low + + * New Upstream Release + + -- Simon MacMullen Tue, 29 Apr 2014 11:49:23 +0100 + +rabbitmq-server (3.3.0-1) unstable; urgency=low + + * New Upstream Release + + -- Simon MacMullen Wed, 02 Apr 2014 14:23:14 +0100 + +rabbitmq-server (3.2.4-1) unstable; urgency=low + + * New Upstream Release + + -- Simon MacMullen Mon, 03 Mar 2014 14:50:18 +0000 + +rabbitmq-server (3.2.3-1+mira.1) precise; urgency=low + + * Disable rabbitmq-server autostart + + -- Vladimir Kuklin Thu, 10 Jul 2014 14:01:02 +0400 + +rabbitmq-server (3.2.3-1) unstable; urgency=low + + [ Emile Joubert ] + * New Upstream Release + + [ Vladimir Kuklin ] + * +mira + + -- Vladimir Kuklin Thu, 10 Jul 2014 14:00:41 +0400 + +rabbitmq-server (3.2.2-1) unstable; urgency=low + + * New Upstream Release + + -- Emile Joubert Tue, 10 Dec 2013 16:08:08 +0000 + +rabbitmq-server (3.2.0-1) unstable; urgency=low + + * New Upstream Release + + -- Emile Joubert Wed, 23 Oct 2013 12:44:10 +0100 + +rabbitmq-server (3.1.5-1) unstable; urgency=low + + * New Upstream Release + + -- Simon MacMullen Thu, 15 Aug 2013 11:03:13 +0100 + +rabbitmq-server (3.1.3-1) unstable; urgency=low + + * New Upstream Release + + -- Tim Watson Tue, 25 Jun 2013 15:01:12 +0100 + +rabbitmq-server (3.1.2-1) unstable; urgency=low + + * New Upstream Release + + -- Tim Watson Mon, 24 Jun 2013 11:16:41 +0100 + +rabbitmq-server (3.1.1-1) unstable; urgency=low + + * Test release + + -- Tim Watson Mon, 20 May 2013 16:21:20 +0100 + +rabbitmq-server (3.1.0-1) unstable; urgency=low + + * New Upstream Release + + -- Simon MacMullen Wed, 01 May 2013 11:57:58 +0100 + +rabbitmq-server (3.0.1-1) unstable; urgency=low + + * New Upstream Release + + -- Simon MacMullen Tue, 11 Dec 2012 11:29:55 +0000 + +rabbitmq-server (3.0.0-1) unstable; urgency=low + + * New Upstream Release + + -- Simon MacMullen Fri, 16 Nov 2012 14:15:29 +0000 + +rabbitmq-server (2.7.1-1) natty; urgency=low + + * New Upstream Release + + -- Steve Powell Fri, 16 Dec 2011 12:12:36 +0000 + +rabbitmq-server (2.7.0-1) natty; urgency=low + + * New Upstream Release + + -- Steve Powell Tue, 08 Nov 2011 16:47:50 +0000 + +rabbitmq-server (2.6.1-1) natty; urgency=low + + * New Upstream Release + + -- Tim Fri, 09 Sep 2011 14:38:45 +0100 + +rabbitmq-server (2.6.0-1) natty; urgency=low + + * New Upstream Release + + -- Tim Fri, 26 Aug 2011 16:29:40 +0100 + +rabbitmq-server (2.5.1-1) lucid; urgency=low + + * New Upstream Release + + -- Simon MacMullen Mon, 27 Jun 2011 11:21:49 +0100 + +rabbitmq-server (2.5.0-1) lucid; urgency=low + + * New Upstream Release + + -- Thu, 09 Jun 2011 07:20:29 -0700 + +rabbitmq-server (2.4.1-1) lucid; urgency=low + + * New Upstream Release + + -- Alexandru Scvortov Thu, 07 Apr 2011 16:49:22 +0100 + +rabbitmq-server (2.4.0-1) lucid; urgency=low + + * New Upstream Release + + -- Alexandru Scvortov Tue, 22 Mar 2011 17:34:31 +0000 + +rabbitmq-server (2.3.1-1) lucid; urgency=low + + * New Upstream Release + + -- Simon MacMullen Thu, 03 Feb 2011 12:43:56 +0000 + +rabbitmq-server (2.3.0-1) lucid; urgency=low + + * New Upstream Release + + -- Simon MacMullen Tue, 01 Feb 2011 12:52:16 +0000 + +rabbitmq-server (2.2.0-1) lucid; urgency=low + + * New Upstream Release + + -- Rob Harrop Mon, 29 Nov 2010 12:24:48 +0000 + +rabbitmq-server (2.1.1-1) lucid; urgency=low + + * New Upstream Release + + -- Vlad Alexandru Ionescu Tue, 19 Oct 2010 17:20:10 +0100 + +rabbitmq-server (2.1.0-1) lucid; urgency=low + + * New Upstream Release + + -- Marek Majkowski Tue, 14 Sep 2010 14:20:17 +0100 + +rabbitmq-server (2.0.0-1) karmic; urgency=low + + * New Upstream Release + + -- Michael Bridgen Mon, 23 Aug 2010 14:55:39 +0100 + +rabbitmq-server (1.8.1-1) lucid; urgency=low + + * New Upstream Release + + -- Emile Joubert Wed, 14 Jul 2010 15:05:24 +0100 + +rabbitmq-server (1.8.0-1) intrepid; urgency=low + + * New Upstream Release + + -- Matthew Sackman Tue, 15 Jun 2010 12:48:48 +0100 + +rabbitmq-server (1.7.2-1) intrepid; urgency=low + + * New Upstream Release + + -- Matthew Sackman Mon, 15 Feb 2010 15:54:47 +0000 + +rabbitmq-server (1.7.1-1) intrepid; urgency=low + + * New Upstream Release + + -- Matthew Sackman Fri, 22 Jan 2010 14:14:29 +0000 + +rabbitmq-server (1.7.0-1) intrepid; urgency=low + + * New Upstream Release + + -- David Wragg Mon, 05 Oct 2009 13:44:41 +0100 + +rabbitmq-server (1.6.0-1) hardy; urgency=low + + * New Upstream Release + + -- Matthias Radestock Tue, 16 Jun 2009 15:02:58 +0100 + +rabbitmq-server (1.5.5-1) hardy; urgency=low + + * New Upstream Release + + -- Matthias Radestock Tue, 19 May 2009 09:57:54 +0100 + +rabbitmq-server (1.5.4-1) hardy; urgency=low + + * New Upstream Release + + -- Matthias Radestock Mon, 06 Apr 2009 09:19:32 +0100 + +rabbitmq-server (1.5.3-1) hardy; urgency=low + + * New Upstream Release + + -- Tony Garnock-Jones Tue, 24 Feb 2009 18:23:33 +0000 + +rabbitmq-server (1.5.2-1) hardy; urgency=low + + * New Upstream Release + + -- Tony Garnock-Jones Mon, 23 Feb 2009 16:03:38 +0000 + +rabbitmq-server (1.5.1-1) hardy; urgency=low + + * New Upstream Release + + -- Simon MacMullen Mon, 19 Jan 2009 15:46:13 +0000 + +rabbitmq-server (1.5.0-1) testing; urgency=low + + * New Upstream Release + + -- Matthias Radestock Wed, 17 Dec 2008 18:23:47 +0000 + +rabbitmq-server (1.4.0-1) testing; urgency=low + + * New Upstream Release + + -- Tony Garnock-Jones Thu, 24 Jul 2008 13:21:48 +0100 + +rabbitmq-server (1.3.0-1) testing; urgency=low + + * New Upstream Release + + -- Adrien Pierard Mon, 03 Mar 2008 15:34:38 +0000 + +rabbitmq-server (1.2.0-2) testing; urgency=low + + * Fixed rabbitmqctl wrapper script + + -- Simon MacMullen Fri, 05 Oct 2007 11:55:00 +0100 + +rabbitmq-server (1.2.0-1) testing; urgency=low + + * New upstream release + + -- Simon MacMullen Wed, 26 Sep 2007 11:49:26 +0100 + +rabbitmq-server (1.1.1-1) testing; urgency=low + + * New upstream release + + -- Simon MacMullen Wed, 29 Aug 2007 12:03:15 +0100 + +rabbitmq-server (1.1.0-alpha-2) testing; urgency=low + + * Fixed erlang-nox dependency + + -- Simon MacMullen Thu, 02 Aug 2007 11:27:13 +0100 + +rabbitmq-server (1.1.0-alpha-1) testing; urgency=low + + * New upstream release + + -- Simon MacMullen Fri, 20 Jul 2007 18:17:33 +0100 + +rabbitmq-server (1.0.0-alpha-1) unstable; urgency=low + + * Initial release + + -- Tony Garnock-Jones Wed, 31 Jan 2007 19:06:33 +0000 + diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..45a4fb7 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +8 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..02d2354 --- /dev/null +++ b/debian/control @@ -0,0 +1,17 @@ +Source: rabbitmq-server +Section: net +Priority: extra +Maintainer: RabbitMQ Team +Uploaders: Emile Joubert +DM-Upload-Allowed: yes +Build-Depends: cdbs, debhelper (>= 5), erlang-dev, python-simplejson, xmlto, xsltproc, erlang-nox (>= 1:13.b.3), erlang-src (>= 1:13.b.3), unzip, zip +Standards-Version: 3.9.2 + +Package: rabbitmq-server +Architecture: all +Depends: erlang-nox (>= 1:13.b.3) | esl-erlang, adduser, logrotate, ${misc:Depends} +Description: AMQP server written in Erlang + RabbitMQ is an implementation of AMQP, the emerging standard for high + performance enterprise messaging. The RabbitMQ server is a robust and + scalable implementation of an AMQP broker. +Homepage: http://www.rabbitmq.com/ diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..e384a7c --- /dev/null +++ b/debian/copyright @@ -0,0 +1,567 @@ +This package was debianized by Tony Garnock-Jones on +Wed, 3 Jan 2007 15:43:44 +0000. + +It was downloaded from http://www.rabbitmq.com/ + + +This package, the RabbitMQ server is licensed under the MPL. + +If you have any questions regarding licensing, please contact us at +info@rabbitmq.com. + +The files amqp-rabbitmq-0.8.json and amqp-rabbitmq-0.9.1.json are +"Copyright (C) 2008-2013 GoPivotal", Inc. and are covered by the MIT +license. + +jQuery is "Copyright (c) 2010 John Resig" and is covered by the MIT +license. It was downloaded from http://jquery.com/ + +EJS is "Copyright (c) 2007 Edward Benson" and is covered by the MIT +license. It was downloaded from http://embeddedjs.com/ + +Sammy is "Copyright (c) 2008 Aaron Quint, Quirkey NYC, LLC" and is +covered by the MIT license. It was downloaded from +http://code.quirkey.com/sammy/ + +ExplorerCanvas is "Copyright 2006 Google Inc" and is covered by the +Apache License version 2.0. It was downloaded from +http://code.google.com/p/explorercanvas/ + +Flot is "Copyright (c) 2007-2013 IOLA and Ole Laursen" and is covered +by the MIT license. It was downloaded from +http://www.flotcharts.org/ +Webmachine is Copyright (c) Basho Technologies and is covered by the +Apache License 2.0. It was downloaded from http://webmachine.basho.com/ + +Eldap is "Copyright (c) 2010, Torbjorn Tornkvist" and is covered by +the MIT license. It was downloaded from https://github.com/etnt/eldap + +Mochiweb is "Copyright (c) 2007 Mochi Media, Inc." and is covered by +the MIT license. It was downloaded from +http://github.com/mochi/mochiweb/ + +glMatrix is "Copyright (c) 2011, Brandon Jones" and is covered by the +BSD 2-Clause license. It was downloaded from +http://code.google.com/p/glmatrix/ + + +The MIT license is as follows: + + "Permission is hereby granted, free of charge, to any person + obtaining a copy of this file (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." + + +The BSD 2-Clause license is as follows: + + "Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + + +The rest of this package is licensed under the Mozilla Public License 1.1 +Authors and Copyright are as described below: + + The Initial Developer of the Original Code is GoPivotal, Inc. + Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. + + + MOZILLA PUBLIC LICENSE + Version 1.1 + + --------------- + +1. Definitions. + + 1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. + + 1.1. "Contributor" means each entity that creates or contributes to + the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications + made by that particular Contributor. + + 1.3. "Covered Code" means the Original Code or Modifications or the + combination of the Original Code and Modifications, in each case + including portions thereof. + + 1.4. "Electronic Distribution Mechanism" means a mechanism generally + accepted in the software development community for the electronic + transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source + Code. + + 1.6. "Initial Developer" means the individual or entity identified + as the Initial Developer in the Source Code notice required by Exhibit + A. + + 1.7. "Larger Work" means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + + 1.8. "License" means this document. + + 1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + A. Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. + + B. Any new file that contains any part of the Original Code or + previous Modifications. + + 1.10. "Original Code" means Source Code of computer software code + which is described in the Source Code notice required by Exhibit A as + Original Code, and which, at the time of its release under this + License is not already Covered Code governed by this License. + + 1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, + and apparatus claims, in any patent Licensable by grantor. + + 1.11. "Source Code" means the preferred form of the Covered Code for + making modifications to it, including all modules it contains, plus + any associated interface definition files, scripts used to control + compilation and installation of an Executable, or source code + differential comparisons against either the Original Code or another + well known, available Covered Code of the Contributor's choice. The + Source Code can be in a compressed or archival form, provided the + appropriate decompression or de-archiving software is widely available + for no charge. + + 1.12. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License or a future version of this License issued under Section 6.1. + For legal entities, "You" includes any entity which controls, is + controlled by, or is under common control with You. For purposes of + this definition, "control" means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty percent + (50%) of the outstanding shares or beneficial ownership of such + entity. + +2. Source Code License. + + 2.1. The Initial Developer Grant. + The Initial Developer hereby grants You a world-wide, royalty-free, + non-exclusive license, subject to third party intellectual property + claims: + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original + Code (or portions thereof) with or without Modifications, and/or + as part of a Larger Work; and + + (b) under Patents Claims infringed by the making, using or + selling of Original Code, to make, have made, use, practice, + sell, and offer for sale, and/or otherwise dispose of the + Original Code (or portions thereof). + + (c) the licenses granted in this Section 2.1(a) and (b) are + effective on the date Initial Developer first distributes + Original Code under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) + separate from the Original Code; or 3) for infringements caused + by: i) the modification of the Original Code or ii) the + combination of the Original Code with other software or devices. + + 2.2. Contributor Grant. + Subject to third party intellectual property claims, each Contributor + hereby grants You a world-wide, royalty-free, non-exclusive license + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor, to use, reproduce, modify, + display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an + unmodified basis, with other Modifications, as Covered Code + and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either alone + and/or in combination with its Contributor Version (or portions + of such combination), to make, use, sell, offer for sale, have + made, and/or otherwise dispose of: 1) Modifications made by that + Contributor (or portions thereof); and 2) the combination of + Modifications made by that Contributor with its Contributor + Version (or portions of such combination). + + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of + the Covered Code. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: 1) for any code that Contributor has deleted from the + Contributor Version; 2) separate from the Contributor Version; + 3) for infringements caused by: i) third party modifications of + Contributor Version or ii) the combination of Modifications made + by that Contributor with other software (except as part of the + Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by + that Contributor. + +3. Distribution Obligations. + + 3.1. Application of License. + The Modifications which You create or to which You contribute are + governed by the terms of this License, including without limitation + Section 2.2. The Source Code version of Covered Code may be + distributed only under the terms of this License or a future version + of this License released under Section 6.1, and You must include a + copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code + version that alters or restricts the applicable version of this + License or the recipients' rights hereunder. However, You may include + an additional document offering the additional rights described in + Section 3.5. + + 3.2. Availability of Source Code. + Any Modification which You create or to which You contribute must be + made available in Source Code form under the terms of this License + either on the same media as an Executable version or via an accepted + Electronic Distribution Mechanism to anyone to whom you made an + Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) + months after the date it initially became available, or at least six + (6) months after a subsequent version of that particular Modification + has been made available to such recipients. You are responsible for + ensuring that the Source Code version remains available even if the + Electronic Distribution Mechanism is maintained by a third party. + + 3.3. Description of Modifications. + You must cause all Covered Code to which You contribute to contain a + file documenting the changes You made to create that Covered Code and + the date of any change. You must include a prominent statement that + the Modification is derived, directly or indirectly, from Original + Code provided by the Initial Developer and including the name of the + Initial Developer in (a) the Source Code, and (b) in any notice in an + Executable version or related documentation in which You describe the + origin or ownership of the Covered Code. + + 3.4. Intellectual Property Matters + (a) Third Party Claims. + If Contributor has knowledge that a license under a third party's + intellectual property rights is required to exercise the rights + granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code + distribution titled "LEGAL" which describes the claim and the + party making the claim in sufficient detail that a recipient will + know whom to contact. If Contributor obtains such knowledge after + the Modification is made available as described in Section 3.2, + Contributor shall promptly modify the LEGAL file in all copies + Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) + reasonably calculated to inform those who received the Covered + Code that new knowledge has been obtained. + + (b) Contributor APIs. + If Contributor's Modifications include an application programming + interface and Contributor has knowledge of patent licenses which + are reasonably necessary to implement that API, Contributor must + also include this information in the LEGAL file. + + (c) Representations. + Contributor represents that, except as disclosed pursuant to + Section 3.4(a) above, Contributor believes that Contributor's + Modifications are Contributor's original creation(s) and/or + Contributor has sufficient rights to grant the rights conveyed by + this License. + + 3.5. Required Notices. + You must duplicate the notice in Exhibit A in each file of the Source + Code. If it is not possible to put such notice in a particular Source + Code file due to its structure, then You must include such notice in a + location (such as a relevant directory) where a user would be likely + to look for such a notice. If You created one or more Modification(s) + You may add your name as a Contributor to the notice described in + Exhibit A. You must also duplicate this License in any documentation + for the Source Code where You describe recipients' rights or ownership + rights relating to Covered Code. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear than + any such warranty, support, indemnity or liability obligation is + offered by You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of warranty, + support, indemnity or liability terms You offer. + + 3.6. Distribution of Executable Versions. + You may distribute Covered Code in Executable form only if the + requirements of Section 3.1-3.5 have been met for that Covered Code, + and if You include a notice stating that the Source Code version of + the Covered Code is available under the terms of this License, + including a description of how and where You have fulfilled the + obligations of Section 3.2. The notice must be conspicuously included + in any notice in an Executable version, related documentation or + collateral in which You describe recipients' rights relating to the + Covered Code. You may distribute the Executable version of Covered + Code or ownership rights under a license of Your choice, which may + contain terms different from this License, provided that You are in + compliance with the terms of this License and that the license for the + Executable version does not attempt to limit or alter the recipient's + rights in the Source Code version from the rights set forth in this + License. If You distribute the Executable version under a different + license You must make it absolutely clear that any terms which differ + from this License are offered by You alone, not by the Initial + Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such + terms You offer. + + 3.7. Larger Works. + You may create a Larger Work by combining Covered Code with other code + not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Code. + +4. Inability to Comply Due to Statute or Regulation. + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description + must be included in the LEGAL file described in Section 3.4 and must + be included with all distributions of the Source Code. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Application of this License. + + This License applies to code to which the Initial Developer has + attached the notice in Exhibit A and to related Covered Code. + +6. Versions of the License. + + 6.1. New Versions. + Netscape Communications Corporation ("Netscape") may publish revised + and/or new versions of the License from time to time. Each version + will be given a distinguishing version number. + + 6.2. Effect of New Versions. + Once Covered Code has been published under a particular version of the + License, You may always continue to use it under the terms of that + version. You may also choose to use such Covered Code under the terms + of any subsequent version of the License published by Netscape. No one + other than Netscape has the right to modify the terms applicable to + Covered Code created under this License. + + 6.3. Derivative Works. + If You create or use a modified version of this License (which you may + only do in order to apply it to code which is not already Covered Code + governed by this License), You must (a) rename Your license so that + the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", + "MPL", "NPL" or any confusingly similar phrase do not appear in your + license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license + contains terms which differ from the Mozilla Public License and + Netscape Public License. (Filling in the name of the Initial + Developer, Original Code or Contributor in the notice described in + Exhibit A shall not of themselves be deemed to be modifications of + this License.) + +7. DISCLAIMER OF WARRANTY. + + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE + IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, + YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE + COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER + OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +8. TERMINATION. + + 8.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall + survive any termination of this License. Provisions which, by their + nature, must remain in effect beyond the termination of this License + shall survive. + + 8.2. If You initiate litigation by asserting a patent infringement + claim (excluding declatory judgment actions) against Initial Developer + or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + + (a) such Participant's Contributor Version directly or indirectly + infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License + shall, upon 60 days notice from Participant terminate prospectively, + unless if within 60 days after receipt of notice You either: (i) + agree in writing to pay Participant a mutually agreeable reasonable + royalty for Your past and future use of Modifications made by such + Participant, or (ii) withdraw Your litigation claim with respect to + the Contributor Version against such Participant. If within 60 days + of notice, a reasonable royalty and payment arrangement are not + mutually agreed upon in writing by the parties or the litigation claim + is not withdrawn, the rights granted by Participant to You under + Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + + (b) any software, hardware, or device, other than such Participant's + Contributor Version, directly or indirectly infringes any patent, then + any rights granted to You by such Participant under Sections 2.1(b) + and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that + Participant. + + 8.3. If You assert a patent infringement claim against Participant + alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as + by license or settlement) prior to the initiation of patent + infringement litigation, then the reasonable value of the licenses + granted by such Participant under Sections 2.1 or 2.2 shall be taken + into account in determining the amount or value of any payment or + license. + + 8.4. In the event of termination under Sections 8.1 or 8.2 above, + all end user license agreements (excluding distributors and resellers) + which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination. + +9. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, + OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR + ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY + CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, + WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE + EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO + THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +10. U.S. GOVERNMENT END USERS. + + The Covered Code is a "commercial item," as that term is defined in + 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer + software" and "commercial computer software documentation," as such + terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 + C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), + all U.S. Government End Users acquire Covered Code with only those + rights set forth herein. + +11. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed by + California law provisions (except to the extent applicable law, if + any, provides otherwise), excluding its conflict-of-law provisions. + With respect to disputes in which at least one party is a citizen of, + or an entity chartered or registered to do business in the United + States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern + District of California, with venue lying in Santa Clara County, + California, with the losing party responsible for costs, including + without limitation, court costs and reasonable attorneys' fees and + expenses. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. + Any law or regulation which provides that the language of a contract + shall be construed against the drafter shall not apply to this + License. + +12. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, + out of its utilization of rights under this License and You agree to + work with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + +13. MULTIPLE-LICENSED CODE. + + Initial Developer may designate portions of the Covered Code as + "Multiple-Licensed". "Multiple-Licensed" means that the Initial + Developer permits you to utilize portions of the Covered Code under + Your choice of the NPL or the alternative licenses, if any, specified + by the Initial Developer in the file described in Exhibit A. + +EXHIBIT A -Mozilla Public License. + + ``The contents of this file are subject to the Mozilla Public License + Version 1.1 (the "License"); you may not use this file except in + compliance with the License. You may obtain a copy of the License at + http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + License for the specific language governing rights and limitations + under the License. + + The Original Code is RabbitMQ. + + The Initial Developer of the Original Code is GoPivotal, Inc. + Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved.'' + + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.] + + +The Debian packaging is (C) 2007-2013, GoPivotal, Inc. and is licensed +under the MPL 1.1, see above. + diff --git a/debian/dirs b/debian/dirs new file mode 100644 index 0000000..625b7d4 --- /dev/null +++ b/debian/dirs @@ -0,0 +1,9 @@ +usr/lib/rabbitmq/bin +usr/lib/erlang/lib +usr/sbin +usr/share/man +var/lib/rabbitmq/mnesia +var/log/rabbitmq +etc/logrotate.d +etc/rabbitmq + diff --git a/debian/postinst b/debian/postinst new file mode 100644 index 0000000..b11340e --- /dev/null +++ b/debian/postinst @@ -0,0 +1,60 @@ +#!/bin/sh +# postinst script for rabbitmq +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-remove' +# * `abort-deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +# create rabbitmq group +if ! getent group rabbitmq >/dev/null; then + addgroup --system rabbitmq +fi + +# create rabbitmq user +if ! getent passwd rabbitmq >/dev/null; then + adduser --system --ingroup rabbitmq --home /var/lib/rabbitmq \ + --no-create-home --gecos "RabbitMQ messaging server" \ + --disabled-login rabbitmq +fi + +chown -R rabbitmq:rabbitmq /var/lib/rabbitmq +chown -R rabbitmq:rabbitmq /var/log/rabbitmq + +case "$1" in + configure) + if [ -f /etc/rabbitmq/rabbitmq.conf ] && \ + [ ! -f /etc/rabbitmq/rabbitmq-env.conf ]; then + mv /etc/rabbitmq/rabbitmq.conf /etc/rabbitmq/rabbitmq-env.conf + fi + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 + + diff --git a/debian/postrm.in b/debian/postrm.in new file mode 100644 index 0000000..c2e9bbf --- /dev/null +++ b/debian/postrm.in @@ -0,0 +1,65 @@ +#!/bin/sh +# postrm script for rabbitmq +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `remove' +# * `purge' +# * `upgrade' +# * `failed-upgrade' +# * `abort-install' +# * `abort-install' +# * `abort-upgrade' +# * `disappear' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +remove_plugin_traces() { + # Remove traces of plugins + rm -rf /var/lib/rabbitmq/plugins-scratch +} + +case "$1" in + purge) + rm -f /etc/default/rabbitmq + if [ -d /var/lib/rabbitmq ]; then + rm -r /var/lib/rabbitmq + fi + if [ -d /var/log/rabbitmq ]; then + rm -r /var/log/rabbitmq + fi + if [ -d /etc/rabbitmq ]; then + rm -r /etc/rabbitmq + fi + remove_plugin_traces + if getent passwd rabbitmq >/dev/null; then + # Stop epmd if run by the rabbitmq user + pkill -u rabbitmq epmd || : + fi + ;; + + remove|upgrade) + remove_plugin_traces + ;; + + failed-upgrade|abort-install|abort-upgrade|disappear) + ;; + + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 + + diff --git a/debian/rabbitmq-script-wrapper b/debian/rabbitmq-script-wrapper new file mode 100644 index 0000000..4fecc2e --- /dev/null +++ b/debian/rabbitmq-script-wrapper @@ -0,0 +1,44 @@ +#!/bin/sh +## The contents of this file are subject to the Mozilla Public License +## Version 1.1 (the "License"); you may not use this file except in +## compliance with the License. You may obtain a copy of the License +## at http://www.mozilla.org/MPL/ +## +## Software distributed under the License is distributed on an "AS IS" +## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +## the License for the specific language governing rights and +## limitations under the License. +## +## The Original Code is RabbitMQ. +## +## The Initial Developer of the Original Code is GoPivotal, Inc. +## Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +## + +# Escape spaces and quotes, because shell is revolting. +for arg in "$@" ; do + # Escape quotes in parameters, so that they're passed through cleanly. + arg=$(sed -e 's/"/\\"/g' <<-END + $arg + END + ) + CMDLINE="${CMDLINE} \"${arg}\"" +done + +cd /var/lib/rabbitmq + +SCRIPT=`basename $0` + +if [ `id -u` = `id -u rabbitmq` -a "$SCRIPT" = "rabbitmq-server" ] ; then + /usr/lib/rabbitmq/bin/rabbitmq-server "$@" > "/var/log/rabbitmq/startup_log" 2> "/var/log/rabbitmq/startup_err" +elif [ `id -u` = `id -u rabbitmq` -o "$SCRIPT" = "rabbitmq-plugins" ] ; then + /usr/lib/rabbitmq/bin/${SCRIPT} "$@" +elif [ `id -u` = 0 ] ; then + su rabbitmq -s /bin/sh -c "/usr/lib/rabbitmq/bin/${SCRIPT} ${CMDLINE}" +else + /usr/lib/rabbitmq/bin/${SCRIPT} + echo + echo "Only root or rabbitmq should run ${SCRIPT}" + echo + exit 1 +fi diff --git a/debian/rabbitmq-server.default b/debian/rabbitmq-server.default new file mode 100644 index 0000000..1efb356 --- /dev/null +++ b/debian/rabbitmq-server.default @@ -0,0 +1,11 @@ +# This file is sourced by /etc/init.d/rabbitmq-server. Its primary +# reason for existing is to allow adjustment of system limits for the +# rabbitmq-server process. +# +# Maximum number of open file handles. This will need to be increased +# to handle many simultaneous connections. Refer to the system +# documentation for ulimit (in man bash) for more information. +# +ulimit -H -n 105472 +ulimit -S -n 102400 + diff --git a/debian/rabbitmq-server.init b/debian/rabbitmq-server.init new file mode 100644 index 0000000..b2d3f86 --- /dev/null +++ b/debian/rabbitmq-server.init @@ -0,0 +1,187 @@ +#!/bin/sh +# +# rabbitmq-server RabbitMQ broker +# +# chkconfig: - 80 05 +# description: Enable AMQP service provided by RabbitMQ +# + +### BEGIN INIT INFO +# Provides: rabbitmq-server +# Required-Start: $remote_fs $network +# Required-Stop: $remote_fs $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Description: RabbitMQ broker +# Short-Description: Enable AMQP service provided by RabbitMQ broker +### END INIT INFO + +PATH=/sbin:/usr/sbin:/bin:/usr/bin +NAME=rabbitmq-server +DAEMON=/usr/sbin/${NAME} +CONTROL=/usr/sbin/rabbitmqctl +DESC="message broker" +USER=rabbitmq +ROTATE_SUFFIX= +INIT_LOG_DIR=/var/log/rabbitmq +PID_FILE=/var/run/rabbitmq/pid + + +test -x $DAEMON || exit 0 +test -x $CONTROL || exit 0 + +RETVAL=0 +set -e + +[ -f /etc/default/${NAME} ] && . /etc/default/${NAME} + +. /lib/lsb/init-functions +. /lib/init/vars.sh + +ensure_pid_dir () { + PID_DIR=`dirname ${PID_FILE}` + if [ ! -d ${PID_DIR} ] ; then + mkdir -p ${PID_DIR} + chown -R ${USER}:${USER} ${PID_DIR} + chmod 755 ${PID_DIR} + fi +} + +remove_pid () { + rm -f ${PID_FILE} + rmdir `dirname ${PID_FILE}` || : +} + +start_rabbitmq () { + status_rabbitmq quiet + if [ $RETVAL != 0 ] ; then + RETVAL=0 + ensure_pid_dir + set +e + RABBITMQ_PID_FILE=$PID_FILE start-stop-daemon --quiet \ + --chuid rabbitmq --start --exec $DAEMON \ + --pidfile "$RABBITMQ_PID_FILE" --background + $CONTROL wait $PID_FILE >/dev/null 2>&1 + RETVAL=$? + set -e + if [ $RETVAL != 0 ] ; then + remove_pid + fi + else + RETVAL=3 + fi +} + +stop_rabbitmq () { + status_rabbitmq quiet + if [ $RETVAL = 0 ] ; then + set +e + $CONTROL stop ${PID_FILE} > ${INIT_LOG_DIR}/shutdown_log 2> ${INIT_LOG_DIR}/shutdown_err + RETVAL=$? + set -e + if [ $RETVAL = 0 ] ; then + remove_pid + fi + else + RETVAL=3 + fi +} + +status_rabbitmq() { + set +e + if [ "$1" != "quiet" ] ; then + $CONTROL status 2>&1 + else + $CONTROL status > /dev/null 2>&1 + fi + if [ $? != 0 ] ; then + RETVAL=3 + fi + set -e +} + +rotate_logs_rabbitmq() { + set +e + $CONTROL -q rotate_logs ${ROTATE_SUFFIX} + if [ $? != 0 ] ; then + RETVAL=1 + fi + set -e +} + +restart_running_rabbitmq () { + status_rabbitmq quiet + if [ $RETVAL = 0 ] ; then + restart_rabbitmq + else + log_warning_msg "${DESC} not running" + fi +} + +restart_rabbitmq() { + stop_rabbitmq + start_rabbitmq +} + +restart_end() { + if [ $RETVAL = 0 ] ; then + log_end_msg 0 + else + log_end_msg 1 + fi +} + +start_stop_end() { + case "$RETVAL" in + 0) + [ -x /sbin/initctl ] && /sbin/initctl emit --no-wait "${NAME}-${1}" + log_end_msg 0 + ;; + 3) + log_warning_msg "${DESC} already ${1}" + log_end_msg 0 + RETVAL=0 + ;; + *) + log_warning_msg "FAILED - check ${INIT_LOG_DIR}/startup_\{log, _err\}" + log_end_msg 1 + ;; + esac +} + +case "$1" in + start) + log_daemon_msg "Starting ${DESC}" $NAME + start_rabbitmq + start_stop_end "running" + ;; + stop) + log_daemon_msg "Stopping ${DESC}" $NAME + stop_rabbitmq + start_stop_end "stopped" + ;; + status) + status_rabbitmq + ;; + rotate-logs) + log_action_begin_msg "Rotating log files for ${DESC}: ${NAME}" + rotate_logs_rabbitmq + log_action_end_msg $RETVAL + ;; + force-reload|reload|restart) + log_daemon_msg "Restarting ${DESC}" $NAME + restart_rabbitmq + restart_end + ;; + try-restart) + log_daemon_msg "Restarting ${DESC}" $NAME + restart_running_rabbitmq + restart_end + ;; + *) + echo "Usage: $0 {start|stop|status|rotate-logs|restart|condrestart|try-restart|reload|force-reload}" >&2 + RETVAL=1 + ;; +esac + +exit $RETVAL diff --git a/debian/rabbitmq-server.logrotate b/debian/rabbitmq-server.logrotate new file mode 100644 index 0000000..c786df7 --- /dev/null +++ b/debian/rabbitmq-server.logrotate @@ -0,0 +1,12 @@ +/var/log/rabbitmq/*.log { + weekly + missingok + rotate 20 + compress + delaycompress + notifempty + sharedscripts + postrotate + /etc/init.d/rabbitmq-server rotate-logs > /dev/null + endscript +} diff --git a/debian/rabbitmq-server.ocf b/debian/rabbitmq-server.ocf new file mode 100644 index 0000000..c927971 --- /dev/null +++ b/debian/rabbitmq-server.ocf @@ -0,0 +1,371 @@ +#!/bin/sh +## The contents of this file are subject to the Mozilla Public License +## Version 1.1 (the "License"); you may not use this file except in +## compliance with the License. You may obtain a copy of the License +## at http://www.mozilla.org/MPL/ +## +## Software distributed under the License is distributed on an "AS IS" +## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +## the License for the specific language governing rights and +## limitations under the License. +## +## The Original Code is RabbitMQ. +## +## The Initial Developer of the Original Code is GoPivotal, Inc. +## Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +## + +## +## OCF Resource Agent compliant rabbitmq-server resource script. +## + +## OCF instance parameters +## OCF_RESKEY_server +## OCF_RESKEY_ctl +## OCF_RESKEY_nodename +## OCF_RESKEY_ip +## OCF_RESKEY_port +## OCF_RESKEY_config_file +## OCF_RESKEY_log_base +## OCF_RESKEY_mnesia_base +## OCF_RESKEY_server_start_args +## OCF_RESKEY_pid_file + +####################################################################### +# Initialization: + +: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/resource.d/heartbeat} +. ${OCF_FUNCTIONS_DIR}/.ocf-shellfuncs + +####################################################################### + +OCF_RESKEY_server_default="/usr/sbin/rabbitmq-server" +OCF_RESKEY_ctl_default="/usr/sbin/rabbitmqctl" +OCF_RESKEY_nodename_default="rabbit@localhost" +OCF_RESKEY_log_base_default="/var/log/rabbitmq" +OCF_RESKEY_pid_file_default="/var/run/rabbitmq/pid" +: ${OCF_RESKEY_server=${OCF_RESKEY_server_default}} +: ${OCF_RESKEY_ctl=${OCF_RESKEY_ctl_default}} +: ${OCF_RESKEY_nodename=${OCF_RESKEY_nodename_default}} +: ${OCF_RESKEY_log_base=${OCF_RESKEY_log_base_default}} +: ${OCF_RESKEY_pid_file=${OCF_RESKEY_pid_file_default}} + +meta_data() { + cat < + + +1.0 + + +Resource agent for RabbitMQ-server + + +Resource agent for RabbitMQ-server + + + + +The path to the rabbitmq-server script + +Path to rabbitmq-server + + + + + +The path to the rabbitmqctl script + +Path to rabbitmqctl + + + + + +The node name for rabbitmq-server + +Node name + + + + + +The IP address for rabbitmq-server to listen on + +IP Address + + + + + +The IP Port for rabbitmq-server to listen on + +IP Port + + + + + +Location of the config file (without the .config suffix) + +Config file path (without the .config suffix) + + + + + +Location of the directory under which logs will be created + +Log base path + + + + + +Location of the directory under which mnesia will store data + +Mnesia base path + + + + + +Additional arguments provided to the server on startup + +Server start arguments + + + + + +Location of the file in which the pid will be stored + +Pid file path + + + + + + + + + + + + + + +END +} + +rabbit_usage() { + cat < /dev/null 2> /dev/null + rc=$? + case "$rc" in + 0) + ocf_log debug "RabbitMQ server is running normally" + return $OCF_SUCCESS + ;; + 2) + ocf_log debug "RabbitMQ server is not running" + return $OCF_NOT_RUNNING + ;; + *) + ocf_log err "Unexpected return from rabbitmqctl $NODENAME_ARG $action: $rc" + exit $OCF_ERR_GENERIC + esac +} + +rabbit_start() { + local rc + + if rabbit_status; then + ocf_log info "Resource already running." + return $OCF_SUCCESS + fi + + export_vars + + setsid sh -c "$RABBITMQ_SERVER > ${RABBITMQ_LOG_BASE}/startup_log 2> ${RABBITMQ_LOG_BASE}/startup_err" & + + # Wait for the server to come up. + # Let the CRM/LRM time us out if required + rabbit_wait $RABBITMQ_PID_FILE + rc=$? + if [ "$rc" != $OCF_SUCCESS ]; then + remove_pid + ocf_log info "rabbitmq-server start failed: $rc" + exit $OCF_ERR_GENERIC + fi + + return $OCF_SUCCESS +} + +rabbit_stop() { + local rc + + if ! rabbit_status; then + ocf_log info "Resource not running." + return $OCF_SUCCESS + fi + + $RABBITMQ_CTL stop + rc=$? + + if [ "$rc" != 0 ]; then + ocf_log err "rabbitmq-server stop command failed: $RABBITMQ_CTL stop, $rc" + return $rc + fi + + # Spin waiting for the server to shut down. + # Let the CRM/LRM time us out if required + stop_wait=1 + while [ $stop_wait = 1 ]; do + rabbit_status + rc=$? + if [ "$rc" = $OCF_NOT_RUNNING ]; then + remove_pid + stop_wait=0 + break + elif [ "$rc" != $OCF_SUCCESS ]; then + ocf_log info "rabbitmq-server stop failed: $rc" + exit $OCF_ERR_GENERIC + fi + sleep 1 + done + + return $OCF_SUCCESS +} + +rabbit_monitor() { + rabbit_status + return $? +} + +case $__OCF_ACTION in + meta-data) + meta_data + exit $OCF_SUCCESS + ;; + usage|help) + rabbit_usage + exit $OCF_SUCCESS + ;; +esac + +if ocf_is_probe; then + rabbit_validate_partial +else + rabbit_validate_full +fi + +case $__OCF_ACTION in + start) + rabbit_start + ;; + stop) + rabbit_stop + ;; + status|monitor) + rabbit_monitor + ;; + validate-all) + exit $OCF_SUCCESS + ;; + *) + rabbit_usage + exit $OCF_ERR_UNIMPLEMENTED + ;; +esac + +exit $? diff --git a/debian/rules b/debian/rules new file mode 100644 index 0000000..cac29c8 --- /dev/null +++ b/debian/rules @@ -0,0 +1,27 @@ +#!/usr/bin/make -f + +include /usr/share/cdbs/1/rules/debhelper.mk +include /usr/share/cdbs/1/class/makefile.mk + +RABBIT_LIB=$(DEB_DESTDIR)usr/lib/rabbitmq/lib/rabbitmq_server-$(DEB_UPSTREAM_VERSION)/ +RABBIT_BIN=$(DEB_DESTDIR)usr/lib/rabbitmq/bin/ + +DOCDIR=$(DEB_DESTDIR)usr/share/doc/rabbitmq-server/ +DEB_MAKE_INSTALL_TARGET := install TARGET_DIR=$(RABBIT_LIB) SBIN_DIR=$(RABBIT_BIN) DOC_INSTALL_DIR=$(DOCDIR) MAN_DIR=$(DEB_DESTDIR)usr/share/man/ +DEB_MAKE_CLEAN_TARGET:= distclean +DEB_INSTALL_DOCS_ALL=debian/README + +DEB_DH_INSTALLINIT_ARGS="--no-start" + +install/rabbitmq-server:: + mkdir -p $(DOCDIR) + rm $(RABBIT_LIB)LICENSE* $(RABBIT_LIB)INSTALL* + for script in rabbitmqctl rabbitmq-server rabbitmq-plugins; do \ + install -p -D -m 0755 debian/rabbitmq-script-wrapper $(DEB_DESTDIR)usr/sbin/$$script; \ + done + sed -e 's|@RABBIT_LIB@|/usr/lib/rabbitmq/lib/rabbitmq_server-$(DEB_UPSTREAM_VERSION)|g' debian/postrm + install -p -D -m 0755 debian/rabbitmq-server.ocf $(DEB_DESTDIR)usr/lib/ocf/resource.d/rabbitmq/rabbitmq-server + install -p -D -m 0644 debian/rabbitmq-server.default $(DEB_DESTDIR)etc/default/rabbitmq-server + +clean:: + rm -f plugins-src/rabbitmq-server debian/postrm plugins/README diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/debian/watch b/debian/watch new file mode 100644 index 0000000..b41aff9 --- /dev/null +++ b/debian/watch @@ -0,0 +1,4 @@ +version=3 + +http://www.rabbitmq.com/releases/rabbitmq-server/v(.*)/rabbitmq-server-(\d.*)\.tar\.gz \ + debian uupdate diff --git a/rabbitmq-server-3.3.5/INSTALL b/rabbitmq-server-3.3.5/INSTALL new file mode 100644 index 0000000..be34498 --- /dev/null +++ b/rabbitmq-server-3.3.5/INSTALL @@ -0,0 +1,2 @@ +Please see http://www.rabbitmq.com/download.html for links to guides +to installing RabbitMQ. diff --git a/rabbitmq-server-3.3.5/LICENSE b/rabbitmq-server-3.3.5/LICENSE new file mode 100644 index 0000000..58c43e7 --- /dev/null +++ b/rabbitmq-server-3.3.5/LICENSE @@ -0,0 +1,556 @@ +This package, the RabbitMQ server is licensed under the MPL. + +If you have any questions regarding licensing, please contact us at +info@rabbitmq.com. + +The files amqp-rabbitmq-0.8.json and amqp-rabbitmq-0.9.1.json are +"Copyright (C) 2008-2013 GoPivotal", Inc. and are covered by the MIT +license. + +jQuery is "Copyright (c) 2010 John Resig" and is covered by the MIT +license. It was downloaded from http://jquery.com/ + +EJS is "Copyright (c) 2007 Edward Benson" and is covered by the MIT +license. It was downloaded from http://embeddedjs.com/ + +Sammy is "Copyright (c) 2008 Aaron Quint, Quirkey NYC, LLC" and is +covered by the MIT license. It was downloaded from +http://code.quirkey.com/sammy/ + +ExplorerCanvas is "Copyright 2006 Google Inc" and is covered by the +Apache License version 2.0. It was downloaded from +http://code.google.com/p/explorercanvas/ + +Flot is "Copyright (c) 2007-2013 IOLA and Ole Laursen" and is covered +by the MIT license. It was downloaded from +http://www.flotcharts.org/ +Webmachine is Copyright (c) Basho Technologies and is covered by the +Apache License 2.0. It was downloaded from http://webmachine.basho.com/ + +Eldap is "Copyright (c) 2010, Torbjorn Tornkvist" and is covered by +the MIT license. It was downloaded from https://github.com/etnt/eldap + +Mochiweb is "Copyright (c) 2007 Mochi Media, Inc." and is covered by +the MIT license. It was downloaded from +http://github.com/mochi/mochiweb/ + +glMatrix is "Copyright (c) 2011, Brandon Jones" and is covered by the +BSD 2-Clause license. It was downloaded from +http://code.google.com/p/glmatrix/ + + +The MIT license is as follows: + + "Permission is hereby granted, free of charge, to any person + obtaining a copy of this file (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." + + +The BSD 2-Clause license is as follows: + + "Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the + following conditions are met: + + 1. Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + + +The rest of this package is licensed under the Mozilla Public License 1.1 +Authors and Copyright are as described below: + + The Initial Developer of the Original Code is GoPivotal, Inc. + Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. + + + MOZILLA PUBLIC LICENSE + Version 1.1 + + --------------- + +1. Definitions. + + 1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. + + 1.1. "Contributor" means each entity that creates or contributes to + the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications + made by that particular Contributor. + + 1.3. "Covered Code" means the Original Code or Modifications or the + combination of the Original Code and Modifications, in each case + including portions thereof. + + 1.4. "Electronic Distribution Mechanism" means a mechanism generally + accepted in the software development community for the electronic + transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source + Code. + + 1.6. "Initial Developer" means the individual or entity identified + as the Initial Developer in the Source Code notice required by Exhibit + A. + + 1.7. "Larger Work" means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + + 1.8. "License" means this document. + + 1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + A. Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. + + B. Any new file that contains any part of the Original Code or + previous Modifications. + + 1.10. "Original Code" means Source Code of computer software code + which is described in the Source Code notice required by Exhibit A as + Original Code, and which, at the time of its release under this + License is not already Covered Code governed by this License. + + 1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, + and apparatus claims, in any patent Licensable by grantor. + + 1.11. "Source Code" means the preferred form of the Covered Code for + making modifications to it, including all modules it contains, plus + any associated interface definition files, scripts used to control + compilation and installation of an Executable, or source code + differential comparisons against either the Original Code or another + well known, available Covered Code of the Contributor's choice. The + Source Code can be in a compressed or archival form, provided the + appropriate decompression or de-archiving software is widely available + for no charge. + + 1.12. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License or a future version of this License issued under Section 6.1. + For legal entities, "You" includes any entity which controls, is + controlled by, or is under common control with You. For purposes of + this definition, "control" means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty percent + (50%) of the outstanding shares or beneficial ownership of such + entity. + +2. Source Code License. + + 2.1. The Initial Developer Grant. + The Initial Developer hereby grants You a world-wide, royalty-free, + non-exclusive license, subject to third party intellectual property + claims: + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original + Code (or portions thereof) with or without Modifications, and/or + as part of a Larger Work; and + + (b) under Patents Claims infringed by the making, using or + selling of Original Code, to make, have made, use, practice, + sell, and offer for sale, and/or otherwise dispose of the + Original Code (or portions thereof). + + (c) the licenses granted in this Section 2.1(a) and (b) are + effective on the date Initial Developer first distributes + Original Code under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) + separate from the Original Code; or 3) for infringements caused + by: i) the modification of the Original Code or ii) the + combination of the Original Code with other software or devices. + + 2.2. Contributor Grant. + Subject to third party intellectual property claims, each Contributor + hereby grants You a world-wide, royalty-free, non-exclusive license + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor, to use, reproduce, modify, + display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an + unmodified basis, with other Modifications, as Covered Code + and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either alone + and/or in combination with its Contributor Version (or portions + of such combination), to make, use, sell, offer for sale, have + made, and/or otherwise dispose of: 1) Modifications made by that + Contributor (or portions thereof); and 2) the combination of + Modifications made by that Contributor with its Contributor + Version (or portions of such combination). + + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of + the Covered Code. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: 1) for any code that Contributor has deleted from the + Contributor Version; 2) separate from the Contributor Version; + 3) for infringements caused by: i) third party modifications of + Contributor Version or ii) the combination of Modifications made + by that Contributor with other software (except as part of the + Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by + that Contributor. + +3. Distribution Obligations. + + 3.1. Application of License. + The Modifications which You create or to which You contribute are + governed by the terms of this License, including without limitation + Section 2.2. The Source Code version of Covered Code may be + distributed only under the terms of this License or a future version + of this License released under Section 6.1, and You must include a + copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code + version that alters or restricts the applicable version of this + License or the recipients' rights hereunder. However, You may include + an additional document offering the additional rights described in + Section 3.5. + + 3.2. Availability of Source Code. + Any Modification which You create or to which You contribute must be + made available in Source Code form under the terms of this License + either on the same media as an Executable version or via an accepted + Electronic Distribution Mechanism to anyone to whom you made an + Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) + months after the date it initially became available, or at least six + (6) months after a subsequent version of that particular Modification + has been made available to such recipients. You are responsible for + ensuring that the Source Code version remains available even if the + Electronic Distribution Mechanism is maintained by a third party. + + 3.3. Description of Modifications. + You must cause all Covered Code to which You contribute to contain a + file documenting the changes You made to create that Covered Code and + the date of any change. You must include a prominent statement that + the Modification is derived, directly or indirectly, from Original + Code provided by the Initial Developer and including the name of the + Initial Developer in (a) the Source Code, and (b) in any notice in an + Executable version or related documentation in which You describe the + origin or ownership of the Covered Code. + + 3.4. Intellectual Property Matters + (a) Third Party Claims. + If Contributor has knowledge that a license under a third party's + intellectual property rights is required to exercise the rights + granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code + distribution titled "LEGAL" which describes the claim and the + party making the claim in sufficient detail that a recipient will + know whom to contact. If Contributor obtains such knowledge after + the Modification is made available as described in Section 3.2, + Contributor shall promptly modify the LEGAL file in all copies + Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) + reasonably calculated to inform those who received the Covered + Code that new knowledge has been obtained. + + (b) Contributor APIs. + If Contributor's Modifications include an application programming + interface and Contributor has knowledge of patent licenses which + are reasonably necessary to implement that API, Contributor must + also include this information in the LEGAL file. + + (c) Representations. + Contributor represents that, except as disclosed pursuant to + Section 3.4(a) above, Contributor believes that Contributor's + Modifications are Contributor's original creation(s) and/or + Contributor has sufficient rights to grant the rights conveyed by + this License. + + 3.5. Required Notices. + You must duplicate the notice in Exhibit A in each file of the Source + Code. If it is not possible to put such notice in a particular Source + Code file due to its structure, then You must include such notice in a + location (such as a relevant directory) where a user would be likely + to look for such a notice. If You created one or more Modification(s) + You may add your name as a Contributor to the notice described in + Exhibit A. You must also duplicate this License in any documentation + for the Source Code where You describe recipients' rights or ownership + rights relating to Covered Code. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear than + any such warranty, support, indemnity or liability obligation is + offered by You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of warranty, + support, indemnity or liability terms You offer. + + 3.6. Distribution of Executable Versions. + You may distribute Covered Code in Executable form only if the + requirements of Section 3.1-3.5 have been met for that Covered Code, + and if You include a notice stating that the Source Code version of + the Covered Code is available under the terms of this License, + including a description of how and where You have fulfilled the + obligations of Section 3.2. The notice must be conspicuously included + in any notice in an Executable version, related documentation or + collateral in which You describe recipients' rights relating to the + Covered Code. You may distribute the Executable version of Covered + Code or ownership rights under a license of Your choice, which may + contain terms different from this License, provided that You are in + compliance with the terms of this License and that the license for the + Executable version does not attempt to limit or alter the recipient's + rights in the Source Code version from the rights set forth in this + License. If You distribute the Executable version under a different + license You must make it absolutely clear that any terms which differ + from this License are offered by You alone, not by the Initial + Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such + terms You offer. + + 3.7. Larger Works. + You may create a Larger Work by combining Covered Code with other code + not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Code. + +4. Inability to Comply Due to Statute or Regulation. + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description + must be included in the LEGAL file described in Section 3.4 and must + be included with all distributions of the Source Code. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Application of this License. + + This License applies to code to which the Initial Developer has + attached the notice in Exhibit A and to related Covered Code. + +6. Versions of the License. + + 6.1. New Versions. + Netscape Communications Corporation ("Netscape") may publish revised + and/or new versions of the License from time to time. Each version + will be given a distinguishing version number. + + 6.2. Effect of New Versions. + Once Covered Code has been published under a particular version of the + License, You may always continue to use it under the terms of that + version. You may also choose to use such Covered Code under the terms + of any subsequent version of the License published by Netscape. No one + other than Netscape has the right to modify the terms applicable to + Covered Code created under this License. + + 6.3. Derivative Works. + If You create or use a modified version of this License (which you may + only do in order to apply it to code which is not already Covered Code + governed by this License), You must (a) rename Your license so that + the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", + "MPL", "NPL" or any confusingly similar phrase do not appear in your + license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license + contains terms which differ from the Mozilla Public License and + Netscape Public License. (Filling in the name of the Initial + Developer, Original Code or Contributor in the notice described in + Exhibit A shall not of themselves be deemed to be modifications of + this License.) + +7. DISCLAIMER OF WARRANTY. + + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE + IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, + YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE + COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER + OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +8. TERMINATION. + + 8.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall + survive any termination of this License. Provisions which, by their + nature, must remain in effect beyond the termination of this License + shall survive. + + 8.2. If You initiate litigation by asserting a patent infringement + claim (excluding declatory judgment actions) against Initial Developer + or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + + (a) such Participant's Contributor Version directly or indirectly + infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License + shall, upon 60 days notice from Participant terminate prospectively, + unless if within 60 days after receipt of notice You either: (i) + agree in writing to pay Participant a mutually agreeable reasonable + royalty for Your past and future use of Modifications made by such + Participant, or (ii) withdraw Your litigation claim with respect to + the Contributor Version against such Participant. If within 60 days + of notice, a reasonable royalty and payment arrangement are not + mutually agreed upon in writing by the parties or the litigation claim + is not withdrawn, the rights granted by Participant to You under + Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + + (b) any software, hardware, or device, other than such Participant's + Contributor Version, directly or indirectly infringes any patent, then + any rights granted to You by such Participant under Sections 2.1(b) + and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that + Participant. + + 8.3. If You assert a patent infringement claim against Participant + alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as + by license or settlement) prior to the initiation of patent + infringement litigation, then the reasonable value of the licenses + granted by such Participant under Sections 2.1 or 2.2 shall be taken + into account in determining the amount or value of any payment or + license. + + 8.4. In the event of termination under Sections 8.1 or 8.2 above, + all end user license agreements (excluding distributors and resellers) + which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination. + +9. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, + OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR + ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY + CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, + WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE + EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO + THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +10. U.S. GOVERNMENT END USERS. + + The Covered Code is a "commercial item," as that term is defined in + 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer + software" and "commercial computer software documentation," as such + terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 + C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), + all U.S. Government End Users acquire Covered Code with only those + rights set forth herein. + +11. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed by + California law provisions (except to the extent applicable law, if + any, provides otherwise), excluding its conflict-of-law provisions. + With respect to disputes in which at least one party is a citizen of, + or an entity chartered or registered to do business in the United + States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern + District of California, with venue lying in Santa Clara County, + California, with the losing party responsible for costs, including + without limitation, court costs and reasonable attorneys' fees and + expenses. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. + Any law or regulation which provides that the language of a contract + shall be construed against the drafter shall not apply to this + License. + +12. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, + out of its utilization of rights under this License and You agree to + work with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + +13. MULTIPLE-LICENSED CODE. + + Initial Developer may designate portions of the Covered Code as + "Multiple-Licensed". "Multiple-Licensed" means that the Initial + Developer permits you to utilize portions of the Covered Code under + Your choice of the NPL or the alternative licenses, if any, specified + by the Initial Developer in the file described in Exhibit A. + +EXHIBIT A -Mozilla Public License. + + ``The contents of this file are subject to the Mozilla Public License + Version 1.1 (the "License"); you may not use this file except in + compliance with the License. You may obtain a copy of the License at + http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + License for the specific language governing rights and limitations + under the License. + + The Original Code is RabbitMQ. + + The Initial Developer of the Original Code is GoPivotal, Inc. + Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved.'' + + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.] diff --git a/rabbitmq-server-3.3.5/LICENSE-APACHE2-ExplorerCanvas b/rabbitmq-server-3.3.5/LICENSE-APACHE2-ExplorerCanvas new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/rabbitmq-server-3.3.5/LICENSE-APACHE2-ExplorerCanvas @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/rabbitmq-server-3.3.5/LICENSE-APL2-Stomp-Websocket b/rabbitmq-server-3.3.5/LICENSE-APL2-Stomp-Websocket new file mode 100644 index 0000000..ef51da2 --- /dev/null +++ b/rabbitmq-server-3.3.5/LICENSE-APL2-Stomp-Websocket @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/rabbitmq-server-3.3.5/LICENSE-Apache-Basho b/rabbitmq-server-3.3.5/LICENSE-Apache-Basho new file mode 100644 index 0000000..e454a52 --- /dev/null +++ b/rabbitmq-server-3.3.5/LICENSE-Apache-Basho @@ -0,0 +1,178 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/rabbitmq-server-3.3.5/LICENSE-BSD-base64js b/rabbitmq-server-3.3.5/LICENSE-BSD-base64js new file mode 100644 index 0000000..7073704 --- /dev/null +++ b/rabbitmq-server-3.3.5/LICENSE-BSD-base64js @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2010 Nick Galbreath + * http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript + * + * 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/rabbitmq-server-3.3.5/LICENSE-BSD-glMatrix b/rabbitmq-server-3.3.5/LICENSE-BSD-glMatrix new file mode 100644 index 0000000..660571e --- /dev/null +++ b/rabbitmq-server-3.3.5/LICENSE-BSD-glMatrix @@ -0,0 +1,26 @@ +Copyright (c) 2011, Brandon Jones +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/rabbitmq-server-3.3.5/LICENSE-MIT-EJS10 b/rabbitmq-server-3.3.5/LICENSE-MIT-EJS10 new file mode 100644 index 0000000..f3bdcd8 --- /dev/null +++ b/rabbitmq-server-3.3.5/LICENSE-MIT-EJS10 @@ -0,0 +1,23 @@ +EJS - Embedded JavaScript + +Copyright (c) 2007 Edward Benson + +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/rabbitmq-server-3.3.5/LICENSE-MIT-Flot b/rabbitmq-server-3.3.5/LICENSE-MIT-Flot new file mode 100644 index 0000000..67f4625 --- /dev/null +++ b/rabbitmq-server-3.3.5/LICENSE-MIT-Flot @@ -0,0 +1,22 @@ +Copyright (c) 2007-2013 IOLA and Ole Laursen + +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/rabbitmq-server-3.3.5/LICENSE-MIT-Mochi b/rabbitmq-server-3.3.5/LICENSE-MIT-Mochi new file mode 100644 index 0000000..c85b65a --- /dev/null +++ b/rabbitmq-server-3.3.5/LICENSE-MIT-Mochi @@ -0,0 +1,9 @@ +This is the MIT license. + +Copyright (c) 2007 Mochi Media, Inc. + +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/rabbitmq-server-3.3.5/LICENSE-MIT-Sammy060 b/rabbitmq-server-3.3.5/LICENSE-MIT-Sammy060 new file mode 100644 index 0000000..3debf5a --- /dev/null +++ b/rabbitmq-server-3.3.5/LICENSE-MIT-Sammy060 @@ -0,0 +1,25 @@ +Copyright (c) 2008 Aaron Quint, Quirkey NYC, LLC + +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/rabbitmq-server-3.3.5/LICENSE-MIT-eldap b/rabbitmq-server-3.3.5/LICENSE-MIT-eldap new file mode 100644 index 0000000..1f62009 --- /dev/null +++ b/rabbitmq-server-3.3.5/LICENSE-MIT-eldap @@ -0,0 +1,21 @@ + +Copyright (c) 2010, Torbjorn Tornkvist + +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/rabbitmq-server-3.3.5/LICENSE-MIT-jQuery164 b/rabbitmq-server-3.3.5/LICENSE-MIT-jQuery164 new file mode 100644 index 0000000..5a87162 --- /dev/null +++ b/rabbitmq-server-3.3.5/LICENSE-MIT-jQuery164 @@ -0,0 +1,21 @@ +Copyright (c) 2011 John Resig, http://jquery.com/ + +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/rabbitmq-server-3.3.5/LICENSE-MPL-RabbitMQ b/rabbitmq-server-3.3.5/LICENSE-MPL-RabbitMQ new file mode 100644 index 0000000..c87c1a3 --- /dev/null +++ b/rabbitmq-server-3.3.5/LICENSE-MPL-RabbitMQ @@ -0,0 +1,455 @@ + MOZILLA PUBLIC LICENSE + Version 1.1 + + --------------- + +1. Definitions. + + 1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. + + 1.1. "Contributor" means each entity that creates or contributes to + the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications + made by that particular Contributor. + + 1.3. "Covered Code" means the Original Code or Modifications or the + combination of the Original Code and Modifications, in each case + including portions thereof. + + 1.4. "Electronic Distribution Mechanism" means a mechanism generally + accepted in the software development community for the electronic + transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source + Code. + + 1.6. "Initial Developer" means the individual or entity identified + as the Initial Developer in the Source Code notice required by Exhibit + A. + + 1.7. "Larger Work" means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + + 1.8. "License" means this document. + + 1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + A. Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. + + B. Any new file that contains any part of the Original Code or + previous Modifications. + + 1.10. "Original Code" means Source Code of computer software code + which is described in the Source Code notice required by Exhibit A as + Original Code, and which, at the time of its release under this + License is not already Covered Code governed by this License. + + 1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, + and apparatus claims, in any patent Licensable by grantor. + + 1.11. "Source Code" means the preferred form of the Covered Code for + making modifications to it, including all modules it contains, plus + any associated interface definition files, scripts used to control + compilation and installation of an Executable, or source code + differential comparisons against either the Original Code or another + well known, available Covered Code of the Contributor's choice. The + Source Code can be in a compressed or archival form, provided the + appropriate decompression or de-archiving software is widely available + for no charge. + + 1.12. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License or a future version of this License issued under Section 6.1. + For legal entities, "You" includes any entity which controls, is + controlled by, or is under common control with You. For purposes of + this definition, "control" means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty percent + (50%) of the outstanding shares or beneficial ownership of such + entity. + +2. Source Code License. + + 2.1. The Initial Developer Grant. + The Initial Developer hereby grants You a world-wide, royalty-free, + non-exclusive license, subject to third party intellectual property + claims: + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original + Code (or portions thereof) with or without Modifications, and/or + as part of a Larger Work; and + + (b) under Patents Claims infringed by the making, using or + selling of Original Code, to make, have made, use, practice, + sell, and offer for sale, and/or otherwise dispose of the + Original Code (or portions thereof). + + (c) the licenses granted in this Section 2.1(a) and (b) are + effective on the date Initial Developer first distributes + Original Code under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) + separate from the Original Code; or 3) for infringements caused + by: i) the modification of the Original Code or ii) the + combination of the Original Code with other software or devices. + + 2.2. Contributor Grant. + Subject to third party intellectual property claims, each Contributor + hereby grants You a world-wide, royalty-free, non-exclusive license + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor, to use, reproduce, modify, + display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an + unmodified basis, with other Modifications, as Covered Code + and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either alone + and/or in combination with its Contributor Version (or portions + of such combination), to make, use, sell, offer for sale, have + made, and/or otherwise dispose of: 1) Modifications made by that + Contributor (or portions thereof); and 2) the combination of + Modifications made by that Contributor with its Contributor + Version (or portions of such combination). + + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of + the Covered Code. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: 1) for any code that Contributor has deleted from the + Contributor Version; 2) separate from the Contributor Version; + 3) for infringements caused by: i) third party modifications of + Contributor Version or ii) the combination of Modifications made + by that Contributor with other software (except as part of the + Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by + that Contributor. + +3. Distribution Obligations. + + 3.1. Application of License. + The Modifications which You create or to which You contribute are + governed by the terms of this License, including without limitation + Section 2.2. The Source Code version of Covered Code may be + distributed only under the terms of this License or a future version + of this License released under Section 6.1, and You must include a + copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code + version that alters or restricts the applicable version of this + License or the recipients' rights hereunder. However, You may include + an additional document offering the additional rights described in + Section 3.5. + + 3.2. Availability of Source Code. + Any Modification which You create or to which You contribute must be + made available in Source Code form under the terms of this License + either on the same media as an Executable version or via an accepted + Electronic Distribution Mechanism to anyone to whom you made an + Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) + months after the date it initially became available, or at least six + (6) months after a subsequent version of that particular Modification + has been made available to such recipients. You are responsible for + ensuring that the Source Code version remains available even if the + Electronic Distribution Mechanism is maintained by a third party. + + 3.3. Description of Modifications. + You must cause all Covered Code to which You contribute to contain a + file documenting the changes You made to create that Covered Code and + the date of any change. You must include a prominent statement that + the Modification is derived, directly or indirectly, from Original + Code provided by the Initial Developer and including the name of the + Initial Developer in (a) the Source Code, and (b) in any notice in an + Executable version or related documentation in which You describe the + origin or ownership of the Covered Code. + + 3.4. Intellectual Property Matters + (a) Third Party Claims. + If Contributor has knowledge that a license under a third party's + intellectual property rights is required to exercise the rights + granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code + distribution titled "LEGAL" which describes the claim and the + party making the claim in sufficient detail that a recipient will + know whom to contact. If Contributor obtains such knowledge after + the Modification is made available as described in Section 3.2, + Contributor shall promptly modify the LEGAL file in all copies + Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) + reasonably calculated to inform those who received the Covered + Code that new knowledge has been obtained. + + (b) Contributor APIs. + If Contributor's Modifications include an application programming + interface and Contributor has knowledge of patent licenses which + are reasonably necessary to implement that API, Contributor must + also include this information in the LEGAL file. + + (c) Representations. + Contributor represents that, except as disclosed pursuant to + Section 3.4(a) above, Contributor believes that Contributor's + Modifications are Contributor's original creation(s) and/or + Contributor has sufficient rights to grant the rights conveyed by + this License. + + 3.5. Required Notices. + You must duplicate the notice in Exhibit A in each file of the Source + Code. If it is not possible to put such notice in a particular Source + Code file due to its structure, then You must include such notice in a + location (such as a relevant directory) where a user would be likely + to look for such a notice. If You created one or more Modification(s) + You may add your name as a Contributor to the notice described in + Exhibit A. You must also duplicate this License in any documentation + for the Source Code where You describe recipients' rights or ownership + rights relating to Covered Code. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear than + any such warranty, support, indemnity or liability obligation is + offered by You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of warranty, + support, indemnity or liability terms You offer. + + 3.6. Distribution of Executable Versions. + You may distribute Covered Code in Executable form only if the + requirements of Section 3.1-3.5 have been met for that Covered Code, + and if You include a notice stating that the Source Code version of + the Covered Code is available under the terms of this License, + including a description of how and where You have fulfilled the + obligations of Section 3.2. The notice must be conspicuously included + in any notice in an Executable version, related documentation or + collateral in which You describe recipients' rights relating to the + Covered Code. You may distribute the Executable version of Covered + Code or ownership rights under a license of Your choice, which may + contain terms different from this License, provided that You are in + compliance with the terms of this License and that the license for the + Executable version does not attempt to limit or alter the recipient's + rights in the Source Code version from the rights set forth in this + License. If You distribute the Executable version under a different + license You must make it absolutely clear that any terms which differ + from this License are offered by You alone, not by the Initial + Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such + terms You offer. + + 3.7. Larger Works. + You may create a Larger Work by combining Covered Code with other code + not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Code. + +4. Inability to Comply Due to Statute or Regulation. + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description + must be included in the LEGAL file described in Section 3.4 and must + be included with all distributions of the Source Code. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Application of this License. + + This License applies to code to which the Initial Developer has + attached the notice in Exhibit A and to related Covered Code. + +6. Versions of the License. + + 6.1. New Versions. + Netscape Communications Corporation ("Netscape") may publish revised + and/or new versions of the License from time to time. Each version + will be given a distinguishing version number. + + 6.2. Effect of New Versions. + Once Covered Code has been published under a particular version of the + License, You may always continue to use it under the terms of that + version. You may also choose to use such Covered Code under the terms + of any subsequent version of the License published by Netscape. No one + other than Netscape has the right to modify the terms applicable to + Covered Code created under this License. + + 6.3. Derivative Works. + If You create or use a modified version of this License (which you may + only do in order to apply it to code which is not already Covered Code + governed by this License), You must (a) rename Your license so that + the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", + "MPL", "NPL" or any confusingly similar phrase do not appear in your + license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license + contains terms which differ from the Mozilla Public License and + Netscape Public License. (Filling in the name of the Initial + Developer, Original Code or Contributor in the notice described in + Exhibit A shall not of themselves be deemed to be modifications of + this License.) + +7. DISCLAIMER OF WARRANTY. + + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE + IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, + YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE + COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER + OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +8. TERMINATION. + + 8.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall + survive any termination of this License. Provisions which, by their + nature, must remain in effect beyond the termination of this License + shall survive. + + 8.2. If You initiate litigation by asserting a patent infringement + claim (excluding declatory judgment actions) against Initial Developer + or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + + (a) such Participant's Contributor Version directly or indirectly + infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License + shall, upon 60 days notice from Participant terminate prospectively, + unless if within 60 days after receipt of notice You either: (i) + agree in writing to pay Participant a mutually agreeable reasonable + royalty for Your past and future use of Modifications made by such + Participant, or (ii) withdraw Your litigation claim with respect to + the Contributor Version against such Participant. If within 60 days + of notice, a reasonable royalty and payment arrangement are not + mutually agreed upon in writing by the parties or the litigation claim + is not withdrawn, the rights granted by Participant to You under + Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + + (b) any software, hardware, or device, other than such Participant's + Contributor Version, directly or indirectly infringes any patent, then + any rights granted to You by such Participant under Sections 2.1(b) + and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that + Participant. + + 8.3. If You assert a patent infringement claim against Participant + alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as + by license or settlement) prior to the initiation of patent + infringement litigation, then the reasonable value of the licenses + granted by such Participant under Sections 2.1 or 2.2 shall be taken + into account in determining the amount or value of any payment or + license. + + 8.4. In the event of termination under Sections 8.1 or 8.2 above, + all end user license agreements (excluding distributors and resellers) + which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination. + +9. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, + OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR + ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY + CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, + WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE + EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO + THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +10. U.S. GOVERNMENT END USERS. + + The Covered Code is a "commercial item," as that term is defined in + 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer + software" and "commercial computer software documentation," as such + terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 + C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), + all U.S. Government End Users acquire Covered Code with only those + rights set forth herein. + +11. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed by + California law provisions (except to the extent applicable law, if + any, provides otherwise), excluding its conflict-of-law provisions. + With respect to disputes in which at least one party is a citizen of, + or an entity chartered or registered to do business in the United + States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern + District of California, with venue lying in Santa Clara County, + California, with the losing party responsible for costs, including + without limitation, court costs and reasonable attorneys' fees and + expenses. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. + Any law or regulation which provides that the language of a contract + shall be construed against the drafter shall not apply to this + License. + +12. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, + out of its utilization of rights under this License and You agree to + work with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + +13. MULTIPLE-LICENSED CODE. + + Initial Developer may designate portions of the Covered Code as + "Multiple-Licensed". "Multiple-Licensed" means that the Initial + Developer permits you to utilize portions of the Covered Code under + Your choice of the NPL or the alternative licenses, if any, specified + by the Initial Developer in the file described in Exhibit A. + +EXHIBIT A -Mozilla Public License. + + ``The contents of this file are subject to the Mozilla Public License + Version 1.1 (the "License"); you may not use this file except in + compliance with the License. You may obtain a copy of the License at + http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + License for the specific language governing rights and limitations + under the License. + + The Original Code is RabbitMQ. + + The Initial Developer of the Original Code is GoPivotal, Inc. + Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved.'' + + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.] diff --git a/rabbitmq-server-3.3.5/Makefile b/rabbitmq-server-3.3.5/Makefile new file mode 100644 index 0000000..c54b44e --- /dev/null +++ b/rabbitmq-server-3.3.5/Makefile @@ -0,0 +1,383 @@ +TMPDIR ?= /tmp + +RABBITMQ_NODENAME ?= rabbit +RABBITMQ_SERVER_START_ARGS ?= +RABBITMQ_MNESIA_DIR ?= $(TMPDIR)/rabbitmq-$(RABBITMQ_NODENAME)-mnesia +RABBITMQ_PLUGINS_EXPAND_DIR ?= $(TMPDIR)/rabbitmq-$(RABBITMQ_NODENAME)-plugins-scratch +RABBITMQ_LOG_BASE ?= $(TMPDIR) + +DEPS_FILE=deps.mk +SOURCE_DIR=src +EBIN_DIR=ebin +INCLUDE_DIR=include +DOCS_DIR=docs +INCLUDES=$(wildcard $(INCLUDE_DIR)/*.hrl) $(INCLUDE_DIR)/rabbit_framing.hrl +SOURCES=$(wildcard $(SOURCE_DIR)/*.erl) $(SOURCE_DIR)/rabbit_framing_amqp_0_9_1.erl $(SOURCE_DIR)/rabbit_framing_amqp_0_8.erl $(USAGES_ERL) +BEAM_TARGETS=$(patsubst $(SOURCE_DIR)/%.erl, $(EBIN_DIR)/%.beam, $(SOURCES)) +TARGETS=$(EBIN_DIR)/rabbit.app $(INCLUDE_DIR)/rabbit_framing.hrl $(BEAM_TARGETS) plugins +WEB_URL=http://www.rabbitmq.com/ +MANPAGES=$(patsubst %.xml, %.gz, $(wildcard $(DOCS_DIR)/*.[0-9].xml)) +WEB_MANPAGES=$(patsubst %.xml, %.man.xml, $(wildcard $(DOCS_DIR)/*.[0-9].xml) $(DOCS_DIR)/rabbitmq-service.xml $(DOCS_DIR)/rabbitmq-echopid.xml) +USAGES_XML=$(DOCS_DIR)/rabbitmqctl.1.xml $(DOCS_DIR)/rabbitmq-plugins.1.xml +USAGES_ERL=$(foreach XML, $(USAGES_XML), $(call usage_xml_to_erl, $(XML))) +QC_MODULES := rabbit_backing_queue_qc +QC_TRIALS ?= 100 + +ifeq ($(shell python -c 'import simplejson' 2>/dev/null && echo yes),yes) +PYTHON=python +else +ifeq ($(shell python2.6 -c 'import simplejson' 2>/dev/null && echo yes),yes) +PYTHON=python2.6 +else +ifeq ($(shell python2.5 -c 'import simplejson' 2>/dev/null && echo yes),yes) +PYTHON=python2.5 +else +# Hmm. Missing simplejson? +PYTHON=python +endif +endif +endif + +BASIC_PLT=basic.plt +RABBIT_PLT=rabbit.plt + +ifndef USE_SPECS +# our type specs rely on callback specs, which are available in R15B +# upwards. +USE_SPECS:=$(shell erl -noshell -eval 'io:format([list_to_integer(X) || X <- string:tokens(erlang:system_info(version), ".")] >= [5,9]), halt().') +endif + +ifndef USE_PROPER_QC +# PropEr needs to be installed for property checking +# http://proper.softlab.ntua.gr/ +USE_PROPER_QC:=$(shell erl -noshell -eval 'io:format({module, proper} =:= code:ensure_loaded(proper)), halt().') +endif + +#other args: +native +"{hipe,[o3,verbose]}" -Ddebug=true +debug_info +no_strict_record_tests +ERLC_OPTS=-I $(INCLUDE_DIR) -o $(EBIN_DIR) -Wall -v +debug_info $(call boolean_macro,$(USE_SPECS),use_specs) $(call boolean_macro,$(USE_PROPER_QC),use_proper_qc) + +include version.mk + +PLUGINS_SRC_DIR?=$(shell [ -d "plugins-src" ] && echo "plugins-src" || echo ) +PLUGINS_DIR=plugins +TARBALL_NAME=rabbitmq-server-$(VERSION) +TARGET_SRC_DIR=dist/$(TARBALL_NAME) + +SIBLING_CODEGEN_DIR=../rabbitmq-codegen/ +AMQP_CODEGEN_DIR=$(shell [ -d $(SIBLING_CODEGEN_DIR) ] && echo $(SIBLING_CODEGEN_DIR) || echo codegen) +AMQP_SPEC_JSON_FILES_0_9_1=$(AMQP_CODEGEN_DIR)/amqp-rabbitmq-0.9.1.json $(AMQP_CODEGEN_DIR)/credit_extension.json +AMQP_SPEC_JSON_FILES_0_8=$(AMQP_CODEGEN_DIR)/amqp-rabbitmq-0.8.json + +ERL_CALL=erl_call -sname $(RABBITMQ_NODENAME) -e + +ERL_EBIN=erl -noinput -pa $(EBIN_DIR) + +define usage_xml_to_erl + $(subst __,_,$(patsubst $(DOCS_DIR)/rabbitmq%.1.xml, $(SOURCE_DIR)/rabbit_%_usage.erl, $(subst -,_,$(1)))) +endef + +define usage_dep + $(call usage_xml_to_erl, $(1)): $(1) $(DOCS_DIR)/usage.xsl +endef + +define boolean_macro +$(if $(filter true,$(1)),-D$(2)) +endef + +ifneq "$(SBIN_DIR)" "" +ifneq "$(TARGET_DIR)" "" +SCRIPTS_REL_PATH=$(shell ./calculate-relative $(TARGET_DIR)/sbin $(SBIN_DIR)) +endif +endif + +# Versions prior to this are not supported +NEED_MAKE := 3.80 +ifneq "$(NEED_MAKE)" "$(firstword $(sort $(NEED_MAKE) $(MAKE_VERSION)))" +$(error Versions of make prior to $(NEED_MAKE) are not supported) +endif + +# .DEFAULT_GOAL introduced in 3.81 +DEFAULT_GOAL_MAKE := 3.81 +ifneq "$(DEFAULT_GOAL_MAKE)" "$(firstword $(sort $(DEFAULT_GOAL_MAKE) $(MAKE_VERSION)))" +.DEFAULT_GOAL=all +endif + +all: $(TARGETS) + +.PHONY: plugins check-xref +ifneq "$(PLUGINS_SRC_DIR)" "" +plugins: + [ -d "$(PLUGINS_SRC_DIR)/rabbitmq-server" ] || ln -s "$(CURDIR)" "$(PLUGINS_SRC_DIR)/rabbitmq-server" + mkdir -p $(PLUGINS_DIR) + PLUGINS_SRC_DIR="" $(MAKE) -C "$(PLUGINS_SRC_DIR)" plugins-dist PLUGINS_DIST_DIR="$(CURDIR)/$(PLUGINS_DIR)" VERSION=$(VERSION) + echo "Put your EZs here and use rabbitmq-plugins to enable them." > $(PLUGINS_DIR)/README + rm -f $(PLUGINS_DIR)/rabbit_common*.ez + +# add -q to remove printout of warnings.... +check-xref: $(BEAM_TARGETS) $(PLUGINS_DIR) + rm -rf lib + ./check_xref $(PLUGINS_DIR) -q + +else +plugins: +# Not building plugins + +check-xref: + $(info xref checks are disabled) + +endif + +$(DEPS_FILE): $(SOURCES) $(INCLUDES) + rm -f $@ + echo $(subst : ,:,$(foreach FILE,$^,$(FILE):)) | escript generate_deps $@ $(EBIN_DIR) + +$(EBIN_DIR)/rabbit.app: $(EBIN_DIR)/rabbit_app.in $(SOURCES) generate_app + escript generate_app $< $@ $(SOURCE_DIR) + +$(EBIN_DIR)/%.beam: $(SOURCE_DIR)/%.erl | $(DEPS_FILE) + erlc $(ERLC_OPTS) -pa $(EBIN_DIR) $< + +$(INCLUDE_DIR)/rabbit_framing.hrl: codegen.py $(AMQP_CODEGEN_DIR)/amqp_codegen.py $(AMQP_SPEC_JSON_FILES_0_9_1) $(AMQP_SPEC_JSON_FILES_0_8) + $(PYTHON) codegen.py --ignore-conflicts header $(AMQP_SPEC_JSON_FILES_0_9_1) $(AMQP_SPEC_JSON_FILES_0_8) $@ + +$(SOURCE_DIR)/rabbit_framing_amqp_0_9_1.erl: codegen.py $(AMQP_CODEGEN_DIR)/amqp_codegen.py $(AMQP_SPEC_JSON_FILES_0_9_1) + $(PYTHON) codegen.py body $(AMQP_SPEC_JSON_FILES_0_9_1) $@ + +$(SOURCE_DIR)/rabbit_framing_amqp_0_8.erl: codegen.py $(AMQP_CODEGEN_DIR)/amqp_codegen.py $(AMQP_SPEC_JSON_FILES_0_8) + $(PYTHON) codegen.py body $(AMQP_SPEC_JSON_FILES_0_8) $@ + +dialyze: $(BEAM_TARGETS) $(BASIC_PLT) + dialyzer --plt $(BASIC_PLT) --no_native --fullpath \ + $(BEAM_TARGETS) + +# rabbit.plt is used by rabbitmq-erlang-client's dialyze make target +create-plt: $(RABBIT_PLT) + +$(RABBIT_PLT): $(BEAM_TARGETS) $(BASIC_PLT) + dialyzer --plt $(BASIC_PLT) --output_plt $@ --no_native \ + --add_to_plt $(BEAM_TARGETS) + +$(BASIC_PLT): $(BEAM_TARGETS) + if [ -f $@ ]; then \ + touch $@; \ + else \ + dialyzer --output_plt $@ --build_plt \ + --apps erts kernel stdlib compiler sasl os_mon mnesia tools \ + public_key crypto ssl xmerl; \ + fi + +clean: + rm -f $(EBIN_DIR)/*.beam + rm -f $(EBIN_DIR)/rabbit.app $(EBIN_DIR)/rabbit.boot $(EBIN_DIR)/rabbit.script $(EBIN_DIR)/rabbit.rel + rm -f $(PLUGINS_DIR)/*.ez + [ -d "$(PLUGINS_SRC_DIR)" ] && PLUGINS_SRC_DIR="" PRESERVE_CLONE_DIR=1 make -C $(PLUGINS_SRC_DIR) clean || true + rm -f $(INCLUDE_DIR)/rabbit_framing.hrl $(SOURCE_DIR)/rabbit_framing_amqp_*.erl codegen.pyc + rm -f $(DOCS_DIR)/*.[0-9].gz $(DOCS_DIR)/*.man.xml $(DOCS_DIR)/*.erl $(USAGES_ERL) + rm -f $(RABBIT_PLT) + rm -f $(DEPS_FILE) + +cleandb: + rm -rf $(RABBITMQ_MNESIA_DIR)/* + +############ various tasks to interact with RabbitMQ ################### + +BASIC_SCRIPT_ENVIRONMENT_SETTINGS=\ + RABBITMQ_NODE_IP_ADDRESS="$(RABBITMQ_NODE_IP_ADDRESS)" \ + RABBITMQ_NODE_PORT="$(RABBITMQ_NODE_PORT)" \ + RABBITMQ_LOG_BASE="$(RABBITMQ_LOG_BASE)" \ + RABBITMQ_MNESIA_DIR="$(RABBITMQ_MNESIA_DIR)" \ + RABBITMQ_PLUGINS_EXPAND_DIR="$(RABBITMQ_PLUGINS_EXPAND_DIR)" + +run: all + $(BASIC_SCRIPT_ENVIRONMENT_SETTINGS) \ + RABBITMQ_ALLOW_INPUT=true \ + RABBITMQ_SERVER_START_ARGS="$(RABBITMQ_SERVER_START_ARGS)" \ + ./scripts/rabbitmq-server + +run-background: all + $(BASIC_SCRIPT_ENVIRONMENT_SETTINGS) \ + RABBITMQ_SERVER_START_ARGS="$(RABBITMQ_SERVER_START_ARGS)" \ + ./scripts/rabbitmq-server -detached + +run-node: all + $(BASIC_SCRIPT_ENVIRONMENT_SETTINGS) \ + RABBITMQ_NODE_ONLY=true \ + RABBITMQ_ALLOW_INPUT=true \ + RABBITMQ_SERVER_START_ARGS="$(RABBITMQ_SERVER_START_ARGS)" \ + ./scripts/rabbitmq-server + +run-background-node: all + $(BASIC_SCRIPT_ENVIRONMENT_SETTINGS) \ + RABBITMQ_NODE_ONLY=true \ + RABBITMQ_SERVER_START_ARGS="$(RABBITMQ_SERVER_START_ARGS)" \ + ./scripts/rabbitmq-server + +run-tests: all + OUT=$$(echo "rabbit_tests:all_tests()." | $(ERL_CALL)) ; \ + echo $$OUT ; echo $$OUT | grep '^{ok, passed}$$' > /dev/null + +run-qc: all + $(foreach MOD,$(QC_MODULES),./quickcheck $(RABBITMQ_NODENAME) $(MOD) $(QC_TRIALS)) + +start-background-node: all + -rm -f $(RABBITMQ_MNESIA_DIR).pid + mkdir -p $(RABBITMQ_MNESIA_DIR) + nohup sh -c "$(MAKE) run-background-node > $(RABBITMQ_MNESIA_DIR)/startup_log 2> $(RABBITMQ_MNESIA_DIR)/startup_err" > /dev/null & + ./scripts/rabbitmqctl -n $(RABBITMQ_NODENAME) wait $(RABBITMQ_MNESIA_DIR).pid kernel + +start-rabbit-on-node: all + echo "rabbit:start()." | $(ERL_CALL) + ./scripts/rabbitmqctl -n $(RABBITMQ_NODENAME) wait $(RABBITMQ_MNESIA_DIR).pid + +stop-rabbit-on-node: all + echo "rabbit:stop()." | $(ERL_CALL) + +set-resource-alarm: all + echo "rabbit_alarm:set_alarm({{resource_limit, $(SOURCE), node()}, []})." | \ + $(ERL_CALL) + +clear-resource-alarm: all + echo "rabbit_alarm:clear_alarm({resource_limit, $(SOURCE), node()})." | \ + $(ERL_CALL) + +stop-node: + -$(ERL_CALL) -q + +# code coverage will be created for subdirectory "ebin" of COVER_DIR +COVER_DIR=. + +start-cover: all + echo "rabbit_misc:start_cover([\"rabbit\", \"hare\"])." | $(ERL_CALL) + echo "rabbit_misc:enable_cover([\"$(COVER_DIR)\"])." | $(ERL_CALL) + +stop-cover: all + echo "rabbit_misc:report_cover(), cover:stop()." | $(ERL_CALL) + cat cover/summary.txt + +######################################################################## + +srcdist: distclean + mkdir -p $(TARGET_SRC_DIR)/codegen + cp -r ebin src include LICENSE LICENSE-MPL-RabbitMQ INSTALL README $(TARGET_SRC_DIR) + sed 's/%%VSN%%/$(VERSION)/' $(TARGET_SRC_DIR)/ebin/rabbit_app.in > $(TARGET_SRC_DIR)/ebin/rabbit_app.in.tmp && \ + mv $(TARGET_SRC_DIR)/ebin/rabbit_app.in.tmp $(TARGET_SRC_DIR)/ebin/rabbit_app.in + + cp -r $(AMQP_CODEGEN_DIR)/* $(TARGET_SRC_DIR)/codegen/ + cp codegen.py Makefile generate_app generate_deps calculate-relative $(TARGET_SRC_DIR) + + echo "VERSION?=${VERSION}" > $(TARGET_SRC_DIR)/version.mk + + cp -r scripts $(TARGET_SRC_DIR) + cp -r $(DOCS_DIR) $(TARGET_SRC_DIR) + chmod 0755 $(TARGET_SRC_DIR)/scripts/* + +ifneq "$(PLUGINS_SRC_DIR)" "" + cp -r $(PLUGINS_SRC_DIR) $(TARGET_SRC_DIR)/plugins-src + rm $(TARGET_SRC_DIR)/LICENSE + cat packaging/common/LICENSE.head >> $(TARGET_SRC_DIR)/LICENSE + cat $(AMQP_CODEGEN_DIR)/license_info >> $(TARGET_SRC_DIR)/LICENSE + find $(PLUGINS_SRC_DIR)/licensing -name "license_info_*" -exec cat '{}' >> $(TARGET_SRC_DIR)/LICENSE \; + cat packaging/common/LICENSE.tail >> $(TARGET_SRC_DIR)/LICENSE + find $(PLUGINS_SRC_DIR)/licensing -name "LICENSE-*" -exec cp '{}' $(TARGET_SRC_DIR) \; + rm -rf $(TARGET_SRC_DIR)/licensing +else + @echo No plugins source distribution found +endif + + (cd dist; tar -zchf $(TARBALL_NAME).tar.gz $(TARBALL_NAME)) + (cd dist; zip -q -r $(TARBALL_NAME).zip $(TARBALL_NAME)) + rm -rf $(TARGET_SRC_DIR) + +distclean: clean + $(MAKE) -C $(AMQP_CODEGEN_DIR) distclean + rm -rf dist + find . -regex '.*\(~\|#\|\.swp\|\.dump\)' -exec rm {} \; + +# xmlto can not read from standard input, so we mess with a tmp file. +%.gz: %.xml $(DOCS_DIR)/examples-to-end.xsl + xmlto --version | grep -E '^xmlto version 0\.0\.([0-9]|1[1-8])$$' >/dev/null || opt='--stringparam man.indent.verbatims=0' ; \ + xsltproc --novalid $(DOCS_DIR)/examples-to-end.xsl $< > $<.tmp && \ + xmlto -o $(DOCS_DIR) $$opt man $<.tmp && \ + gzip -f $(DOCS_DIR)/`basename $< .xml` + rm -f $<.tmp + +# Use tmp files rather than a pipeline so that we get meaningful errors +# Do not fold the cp into previous line, it's there to stop the file being +# generated but empty if we fail +$(SOURCE_DIR)/%_usage.erl: + xsltproc --novalid --stringparam modulename "`basename $@ .erl`" \ + $(DOCS_DIR)/usage.xsl $< > $@.tmp + sed -e 's/"/\\"/g' -e 's/%QUOTE%/"/g' $@.tmp > $@.tmp2 + fold -s $@.tmp2 > $@.tmp3 + mv $@.tmp3 $@ + rm $@.tmp $@.tmp2 + +# We rename the file before xmlto sees it since xmlto will use the name of +# the file to make internal links. +%.man.xml: %.xml $(DOCS_DIR)/html-to-website-xml.xsl + cp $< `basename $< .xml`.xml && \ + xmlto xhtml-nochunks `basename $< .xml`.xml ; rm `basename $< .xml`.xml + cat `basename $< .xml`.html | \ + xsltproc --novalid $(DOCS_DIR)/remove-namespaces.xsl - | \ + xsltproc --novalid --stringparam original `basename $<` $(DOCS_DIR)/html-to-website-xml.xsl - | \ + xmllint --format - > $@ + rm `basename $< .xml`.html + +docs_all: $(MANPAGES) $(WEB_MANPAGES) + +install: install_bin install_docs + +install_bin: all install_dirs + cp -r ebin include LICENSE* INSTALL $(TARGET_DIR) + + chmod 0755 scripts/* + for script in rabbitmq-env rabbitmq-server rabbitmqctl rabbitmq-plugins rabbitmq-defaults; do \ + cp scripts/$$script $(TARGET_DIR)/sbin; \ + [ -e $(SBIN_DIR)/$$script ] || ln -s $(SCRIPTS_REL_PATH)/$$script $(SBIN_DIR)/$$script; \ + done + + mkdir -p $(TARGET_DIR)/$(PLUGINS_DIR) + [ -d "$(PLUGINS_DIR)" ] && cp $(PLUGINS_DIR)/*.ez $(PLUGINS_DIR)/README $(TARGET_DIR)/$(PLUGINS_DIR) || true + +install_docs: docs_all install_dirs + for section in 1 5; do \ + mkdir -p $(MAN_DIR)/man$$section; \ + for manpage in $(DOCS_DIR)/*.$$section.gz; do \ + cp $$manpage $(MAN_DIR)/man$$section; \ + done; \ + done + cp $(DOCS_DIR)/rabbitmq.config.example $(DOC_INSTALL_DIR)/rabbitmq.config.example + +install_dirs: + @ OK=true && \ + { [ -n "$(TARGET_DIR)" ] || { echo "Please set TARGET_DIR."; OK=false; }; } && \ + { [ -n "$(SBIN_DIR)" ] || { echo "Please set SBIN_DIR."; OK=false; }; } && \ + { [ -n "$(MAN_DIR)" ] || { echo "Please set MAN_DIR."; OK=false; }; } && \ + { [ -n "$(DOC_INSTALL_DIR)" ] || { echo "Please set DOC_INSTALL_DIR."; OK=false; }; } && $$OK + + mkdir -p $(TARGET_DIR)/sbin + mkdir -p $(SBIN_DIR) + mkdir -p $(MAN_DIR) + mkdir -p $(DOC_INSTALL_DIR) + +$(foreach XML,$(USAGES_XML),$(eval $(call usage_dep, $(XML)))) + +# Note that all targets which depend on clean must have clean in their +# name. Also any target that doesn't depend on clean should not have +# clean in its name, unless you know that you don't need any of the +# automatic dependency generation for that target (e.g. cleandb). + +# We want to load the dep file if *any* target *doesn't* contain +# "clean" - i.e. if removing all clean-like targets leaves something. + +ifeq "$(MAKECMDGOALS)" "" +TESTABLEGOALS:=$(.DEFAULT_GOAL) +else +TESTABLEGOALS:=$(MAKECMDGOALS) +endif + +ifneq "$(strip $(patsubst clean%,,$(patsubst %clean,,$(TESTABLEGOALS))))" "" +include $(DEPS_FILE) +endif + +.PHONY: run-qc diff --git a/rabbitmq-server-3.3.5/README b/rabbitmq-server-3.3.5/README new file mode 100644 index 0000000..90e99e6 --- /dev/null +++ b/rabbitmq-server-3.3.5/README @@ -0,0 +1 @@ +Please see http://www.rabbitmq.com/build-server.html for build instructions. \ No newline at end of file diff --git a/rabbitmq-server-3.3.5/calculate-relative b/rabbitmq-server-3.3.5/calculate-relative new file mode 100755 index 0000000..3af18e8 --- /dev/null +++ b/rabbitmq-server-3.3.5/calculate-relative @@ -0,0 +1,45 @@ +#!/usr/bin/env python +# +# relpath.py +# R.Barran 30/08/2004 +# Retrieved from http://code.activestate.com/recipes/302594/ + +import os +import sys + +def relpath(target, base=os.curdir): + """ + Return a relative path to the target from either the current dir or an optional base dir. + Base can be a directory specified either as absolute or relative to current dir. + """ + + if not os.path.exists(target): + raise OSError, 'Target does not exist: '+target + + if not os.path.isdir(base): + raise OSError, 'Base is not a directory or does not exist: '+base + + base_list = (os.path.abspath(base)).split(os.sep) + target_list = (os.path.abspath(target)).split(os.sep) + + # On the windows platform the target may be on a completely different drive from the base. + if os.name in ['nt','dos','os2'] and base_list[0] <> target_list[0]: + raise OSError, 'Target is on a different drive to base. Target: '+target_list[0].upper()+', base: '+base_list[0].upper() + + # Starting from the filepath root, work out how much of the filepath is + # shared by base and target. + for i in range(min(len(base_list), len(target_list))): + if base_list[i] <> target_list[i]: break + else: + # If we broke out of the loop, i is pointing to the first differing path elements. + # If we didn't break out of the loop, i is pointing to identical path elements. + # Increment i so that in all cases it points to the first differing path elements. + i+=1 + + rel_list = [os.pardir] * (len(base_list)-i) + target_list[i:] + if (len(rel_list) == 0): + return "." + return os.path.join(*rel_list) + +if __name__ == "__main__": + print(relpath(sys.argv[1], sys.argv[2])) diff --git a/rabbitmq-server-3.3.5/codegen.py b/rabbitmq-server-3.3.5/codegen.py new file mode 100644 index 0000000..b2356bb --- /dev/null +++ b/rabbitmq-server-3.3.5/codegen.py @@ -0,0 +1,595 @@ +## The contents of this file are subject to the Mozilla Public License +## Version 1.1 (the "License"); you may not use this file except in +## compliance with the License. You may obtain a copy of the License +## at http://www.mozilla.org/MPL/ +## +## Software distributed under the License is distributed on an "AS IS" +## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +## the License for the specific language governing rights and +## limitations under the License. +## +## The Original Code is RabbitMQ. +## +## The Initial Developer of the Original Code is GoPivotal, Inc. +## Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +## + +from __future__ import nested_scopes + +import sys +sys.path.append("../rabbitmq-codegen") # in case we're next to an experimental revision +sys.path.append("codegen") # in case we're building from a distribution package + +from amqp_codegen import * +import string +import re + +# Coming up with a proper encoding of AMQP tables in JSON is too much +# hassle at this stage. Given that the only default value we are +# interested in is for the empty table, we only support that. +def convertTable(d): + if len(d) == 0: + return "[]" + else: + raise Exception('Non-empty table defaults not supported ' + d) + +erlangDefaultValueTypeConvMap = { + bool : lambda x: str(x).lower(), + str : lambda x: "<<\"" + x + "\">>", + int : lambda x: str(x), + float : lambda x: str(x), + dict: convertTable, + unicode: lambda x: "<<\"" + x.encode("utf-8") + "\">>" +} + +def erlangize(s): + s = s.replace('-', '_') + s = s.replace(' ', '_') + return s + +AmqpMethod.erlangName = lambda m: "'" + erlangize(m.klass.name) + '.' + erlangize(m.name) + "'" + +AmqpClass.erlangName = lambda c: "'" + erlangize(c.name) + "'" + +def erlangConstantName(s): + return '_'.join(re.split('[- ]', s.upper())) + +class PackedMethodBitField: + def __init__(self, index): + self.index = index + self.domain = 'bit' + self.contents = [] + + def extend(self, f): + self.contents.append(f) + + def count(self): + return len(self.contents) + + def full(self): + return self.count() == 8 + +def multiLineFormat(things, prologue, separator, lineSeparator, epilogue, thingsPerLine = 4): + r = [prologue] + i = 0 + for t in things: + if i != 0: + if i % thingsPerLine == 0: + r += [lineSeparator] + else: + r += [separator] + r += [t] + i += 1 + r += [epilogue] + return "".join(r) + +def prettyType(typeName, subTypes, typesPerLine = 4): + """Pretty print a type signature made up of many alternative subtypes""" + sTs = multiLineFormat(subTypes, + "( ", " | ", "\n | ", " )", + thingsPerLine = typesPerLine) + return "-type(%s ::\n %s)." % (typeName, sTs) + +def printFileHeader(): + print """%% Autogenerated code. Do not edit. +%% +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%%""" + +def genErl(spec): + def erlType(domain): + return erlangize(spec.resolveDomain(domain)) + + def fieldTypeList(fields): + return '[' + ', '.join([erlType(f.domain) for f in fields]) + ']' + + def fieldNameList(fields): + return '[' + ', '.join([erlangize(f.name) for f in fields]) + ']' + + def fieldTempList(fields): + return '[' + ', '.join(['F' + str(f.index) for f in fields]) + ']' + + def fieldMapList(fields): + return ', '.join([erlangize(f.name) + " = F" + str(f.index) for f in fields]) + + def genLookupMethodName(m): + print "lookup_method_name({%d, %d}) -> %s;" % (m.klass.index, m.index, m.erlangName()) + + def genLookupClassName(c): + print "lookup_class_name(%d) -> %s;" % (c.index, c.erlangName()) + + def genMethodId(m): + print "method_id(%s) -> {%d, %d};" % (m.erlangName(), m.klass.index, m.index) + + def genMethodHasContent(m): + print "method_has_content(%s) -> %s;" % (m.erlangName(), str(m.hasContent).lower()) + + def genMethodIsSynchronous(m): + hasNoWait = "nowait" in fieldNameList(m.arguments) + if m.isSynchronous and hasNoWait: + print "is_method_synchronous(#%s{nowait = NoWait}) -> not(NoWait);" % (m.erlangName()) + else: + print "is_method_synchronous(#%s{}) -> %s;" % (m.erlangName(), str(m.isSynchronous).lower()) + + def genMethodFieldTypes(m): + """Not currently used - may be useful in future?""" + print "method_fieldtypes(%s) -> %s;" % (m.erlangName(), fieldTypeList(m.arguments)) + + def genMethodFieldNames(m): + print "method_fieldnames(%s) -> %s;" % (m.erlangName(), fieldNameList(m.arguments)) + + def packMethodFields(fields): + packed = [] + bitfield = None + for f in fields: + if erlType(f.domain) == 'bit': + if not(bitfield) or bitfield.full(): + bitfield = PackedMethodBitField(f.index) + packed.append(bitfield) + bitfield.extend(f) + else: + bitfield = None + packed.append(f) + return packed + + def methodFieldFragment(f): + type = erlType(f.domain) + p = 'F' + str(f.index) + if type == 'shortstr': + return p+'Len:8/unsigned, '+p+':'+p+'Len/binary' + elif type == 'longstr': + return p+'Len:32/unsigned, '+p+':'+p+'Len/binary' + elif type == 'octet': + return p+':8/unsigned' + elif type == 'short': + return p+':16/unsigned' + elif type == 'long': + return p+':32/unsigned' + elif type == 'longlong': + return p+':64/unsigned' + elif type == 'timestamp': + return p+':64/unsigned' + elif type == 'bit': + return p+'Bits:8' + elif type == 'table': + return p+'Len:32/unsigned, '+p+'Tab:'+p+'Len/binary' + + def genFieldPostprocessing(packed, hasContent): + for f in packed: + type = erlType(f.domain) + if type == 'bit': + for index in range(f.count()): + print " F%d = ((F%dBits band %d) /= 0)," % \ + (f.index + index, + f.index, + 1 << index) + elif type == 'table': + print " F%d = rabbit_binary_parser:parse_table(F%dTab)," % \ + (f.index, f.index) + # We skip the check on content-bearing methods for + # speed. This is a sanity check, not a security thing. + elif type == 'shortstr' and not hasContent: + print " rabbit_binary_parser:assert_utf8(F%d)," % (f.index) + else: + pass + + def genMethodRecord(m): + print "method_record(%s) -> #%s{};" % (m.erlangName(), m.erlangName()) + + def genDecodeMethodFields(m): + packedFields = packMethodFields(m.arguments) + binaryPattern = ', '.join([methodFieldFragment(f) for f in packedFields]) + if binaryPattern: + restSeparator = ', ' + else: + restSeparator = '' + recordConstructorExpr = '#%s{%s}' % (m.erlangName(), fieldMapList(m.arguments)) + print "decode_method_fields(%s, <<%s>>) ->" % (m.erlangName(), binaryPattern) + genFieldPostprocessing(packedFields, m.hasContent) + print " %s;" % (recordConstructorExpr,) + + def genDecodeProperties(c): + def presentBin(fields): + ps = ', '.join(['P' + str(f.index) + ':1' for f in fields]) + return '<<' + ps + ', _:%d, R0/binary>>' % (16 - len(fields),) + def writePropFieldLine(field): + i = str(field.index) + if field.domain == 'bit': + print " {F%s, R%s} = {P%s =/= 0, R%s}," % \ + (i, str(field.index + 1), i, i) + else: + print " {F%s, R%s} = if P%s =:= 0 -> {undefined, R%s}; true -> ?%s_VAL(R%s, L%s, V%s, X%s) end," % \ + (i, str(field.index + 1), i, i, erlType(field.domain).upper(), i, i, i, i) + + if len(c.fields) == 0: + print "decode_properties(%d, <<>>) ->" % (c.index,) + else: + print ("decode_properties(%d, %s) ->" % + (c.index, presentBin(c.fields))) + for field in c.fields: + writePropFieldLine(field) + print " <<>> = %s," % ('R' + str(len(c.fields))) + print " #'P_%s'{%s};" % (erlangize(c.name), fieldMapList(c.fields)) + + def genFieldPreprocessing(packed): + for f in packed: + type = erlType(f.domain) + if type == 'bit': + print " F%dBits = (%s)," % \ + (f.index, + ' bor '.join(['(bitvalue(F%d) bsl %d)' % (x.index, x.index - f.index) + for x in f.contents])) + elif type == 'table': + print " F%dTab = rabbit_binary_generator:generate_table(F%d)," % (f.index, f.index) + print " F%dLen = size(F%dTab)," % (f.index, f.index) + elif type == 'shortstr': + print " F%dLen = shortstr_size(F%d)," % (f.index, f.index) + elif type == 'longstr': + print " F%dLen = size(F%d)," % (f.index, f.index) + else: + pass + + def genEncodeMethodFields(m): + packedFields = packMethodFields(m.arguments) + print "encode_method_fields(#%s{%s}) ->" % (m.erlangName(), fieldMapList(m.arguments)) + genFieldPreprocessing(packedFields) + print " <<%s>>;" % (', '.join([methodFieldFragment(f) for f in packedFields])) + + def genEncodeProperties(c): + def presentBin(fields): + ps = ', '.join(['P' + str(f.index) + ':1' for f in fields]) + return '<<' + ps + ', 0:%d>>' % (16 - len(fields),) + def writePropFieldLine(field): + i = str(field.index) + if field.domain == 'bit': + print " {P%s, R%s} = {F%s =:= 1, R%s}," % \ + (i, str(field.index + 1), i, i) + else: + print " {P%s, R%s} = if F%s =:= undefined -> {0, R%s}; true -> {1, [?%s_PROP(F%s, L%s) | R%s]} end," % \ + (i, str(field.index + 1), i, i, erlType(field.domain).upper(), i, i, i) + + print "encode_properties(#'P_%s'{%s}) ->" % (erlangize(c.name), fieldMapList(c.fields)) + if len(c.fields) == 0: + print " <<>>;" + else: + print " R0 = [<<>>]," + for field in c.fields: + writePropFieldLine(field) + print " list_to_binary([%s | lists:reverse(R%s)]);" % \ + (presentBin(c.fields), str(len(c.fields))) + + def messageConstantClass(cls): + # We do this because 0.8 uses "soft error" and 8.1 uses "soft-error". + return erlangConstantName(cls) + + def genLookupException(c,v,cls): + mCls = messageConstantClass(cls) + if mCls == 'SOFT_ERROR': genLookupException1(c,'false') + elif mCls == 'HARD_ERROR': genLookupException1(c, 'true') + elif mCls == '': pass + else: raise Exception('Unknown constant class' + cls) + + def genLookupException1(c,hardErrorBoolStr): + n = erlangConstantName(c) + print 'lookup_amqp_exception(%s) -> {%s, ?%s, <<"%s">>};' % \ + (n.lower(), hardErrorBoolStr, n, n) + + def genAmqpException(c,v,cls): + n = erlangConstantName(c) + print 'amqp_exception(?%s) -> %s;' % \ + (n, n.lower()) + + methods = spec.allMethods() + + printFileHeader() + module = "rabbit_framing_amqp_%d_%d" % (spec.major, spec.minor) + if spec.revision != 0: + module = "%s_%d" % (module, spec.revision) + if module == "rabbit_framing_amqp_8_0": + module = "rabbit_framing_amqp_0_8" + print "-module(%s)." % module + print """-include("rabbit_framing.hrl"). + +-export([version/0]). +-export([lookup_method_name/1]). +-export([lookup_class_name/1]). + +-export([method_id/1]). +-export([method_has_content/1]). +-export([is_method_synchronous/1]). +-export([method_record/1]). +-export([method_fieldnames/1]). +-export([decode_method_fields/2]). +-export([decode_properties/2]). +-export([encode_method_fields/1]). +-export([encode_properties/1]). +-export([lookup_amqp_exception/1]). +-export([amqp_exception/1]). + +""" + print "%% Various types" + print "-ifdef(use_specs)." + + print """-export_type([amqp_field_type/0, amqp_property_type/0, + amqp_table/0, amqp_array/0, amqp_value/0, + amqp_method_name/0, amqp_method/0, amqp_method_record/0, + amqp_method_field_name/0, amqp_property_record/0, + amqp_exception/0, amqp_exception_code/0, amqp_class_id/0]). + +-type(amqp_field_type() :: + 'longstr' | 'signedint' | 'decimal' | 'timestamp' | + 'table' | 'byte' | 'double' | 'float' | 'long' | + 'short' | 'bool' | 'binary' | 'void' | 'array'). +-type(amqp_property_type() :: + 'shortstr' | 'longstr' | 'octet' | 'short' | 'long' | + 'longlong' | 'timestamp' | 'bit' | 'table'). + +-type(amqp_table() :: [{binary(), amqp_field_type(), amqp_value()}]). +-type(amqp_array() :: [{amqp_field_type(), amqp_value()}]). +-type(amqp_value() :: binary() | % longstr + integer() | % signedint + {non_neg_integer(), non_neg_integer()} | % decimal + amqp_table() | + amqp_array() | + byte() | % byte + float() | % double + integer() | % long + integer() | % short + boolean() | % bool + binary() | % binary + 'undefined' | % void + non_neg_integer() % timestamp + ). +""" + + print prettyType("amqp_method_name()", + [m.erlangName() for m in methods]) + print prettyType("amqp_method()", + ["{%s, %s}" % (m.klass.index, m.index) for m in methods], + 6) + print prettyType("amqp_method_record()", + ["#%s{}" % (m.erlangName()) for m in methods]) + fieldNames = set() + for m in methods: + fieldNames.update(m.arguments) + fieldNames = [erlangize(f.name) for f in fieldNames] + print prettyType("amqp_method_field_name()", + fieldNames) + print prettyType("amqp_property_record()", + ["#'P_%s'{}" % erlangize(c.name) for c in spec.allClasses()]) + print prettyType("amqp_exception()", + ["'%s'" % erlangConstantName(c).lower() for (c, v, cls) in spec.constants]) + print prettyType("amqp_exception_code()", + ["%i" % v for (c, v, cls) in spec.constants]) + classIds = set() + for m in spec.allMethods(): + classIds.add(m.klass.index) + print prettyType("amqp_class_id()", + ["%i" % ci for ci in classIds]) + print prettyType("amqp_class_name()", + ["%s" % c.erlangName() for c in spec.allClasses()]) + print "-endif. % use_specs" + + print """ +%% Method signatures +-ifdef(use_specs). +-spec(version/0 :: () -> {non_neg_integer(), non_neg_integer(), non_neg_integer()}). +-spec(lookup_method_name/1 :: (amqp_method()) -> amqp_method_name()). +-spec(lookup_class_name/1 :: (amqp_class_id()) -> amqp_class_name()). +-spec(method_id/1 :: (amqp_method_name()) -> amqp_method()). +-spec(method_has_content/1 :: (amqp_method_name()) -> boolean()). +-spec(is_method_synchronous/1 :: (amqp_method_record()) -> boolean()). +-spec(method_record/1 :: (amqp_method_name()) -> amqp_method_record()). +-spec(method_fieldnames/1 :: (amqp_method_name()) -> [amqp_method_field_name()]). +-spec(decode_method_fields/2 :: + (amqp_method_name(), binary()) -> amqp_method_record() | rabbit_types:connection_exit()). +-spec(decode_properties/2 :: (non_neg_integer(), binary()) -> amqp_property_record()). +-spec(encode_method_fields/1 :: (amqp_method_record()) -> binary()). +-spec(encode_properties/1 :: (amqp_property_record()) -> binary()). +-spec(lookup_amqp_exception/1 :: (amqp_exception()) -> {boolean(), amqp_exception_code(), binary()}). +-spec(amqp_exception/1 :: (amqp_exception_code()) -> amqp_exception()). +-endif. % use_specs + +bitvalue(true) -> 1; +bitvalue(false) -> 0; +bitvalue(undefined) -> 0. + +shortstr_size(S) -> + case size(S) of + Len when Len =< 255 -> Len; + _ -> exit(method_field_shortstr_overflow) + end. + +-define(SHORTSTR_VAL(R, L, V, X), + begin + <> = R, + {V, X} + end). + +-define(LONGSTR_VAL(R, L, V, X), + begin + <> = R, + {V, X} + end). + +-define(SHORT_VAL(R, L, V, X), + begin + <> = R, + {V, X} + end). + +-define(LONG_VAL(R, L, V, X), + begin + <> = R, + {V, X} + end). + +-define(LONGLONG_VAL(R, L, V, X), + begin + <> = R, + {V, X} + end). + +-define(OCTET_VAL(R, L, V, X), + begin + <> = R, + {V, X} + end). + +-define(TABLE_VAL(R, L, V, X), + begin + <> = R, + {rabbit_binary_parser:parse_table(V), X} + end). + +-define(TIMESTAMP_VAL(R, L, V, X), + begin + <> = R, + {V, X} + end). + +-define(SHORTSTR_PROP(X, L), + begin + L = size(X), + if L < 256 -> <>; + true -> exit(content_properties_shortstr_overflow) + end + end). + +-define(LONGSTR_PROP(X, L), + begin + L = size(X), + <> + end). + +-define(OCTET_PROP(X, L), <>). +-define(SHORT_PROP(X, L), <>). +-define(LONG_PROP(X, L), <>). +-define(LONGLONG_PROP(X, L), <>). +-define(TIMESTAMP_PROP(X, L), <>). + +-define(TABLE_PROP(X, T), + begin + T = rabbit_binary_generator:generate_table(X), + <<(size(T)):32, T/binary>> + end). +""" + version = "{%d, %d, %d}" % (spec.major, spec.minor, spec.revision) + if version == '{8, 0, 0}': version = '{0, 8, 0}' + print "version() -> %s." % (version) + + for m in methods: genLookupMethodName(m) + print "lookup_method_name({_ClassId, _MethodId} = Id) -> exit({unknown_method_id, Id})." + + for c in spec.allClasses(): genLookupClassName(c) + print "lookup_class_name(ClassId) -> exit({unknown_class_id, ClassId})." + + for m in methods: genMethodId(m) + print "method_id(Name) -> exit({unknown_method_name, Name})." + + for m in methods: genMethodHasContent(m) + print "method_has_content(Name) -> exit({unknown_method_name, Name})." + + for m in methods: genMethodIsSynchronous(m) + print "is_method_synchronous(Name) -> exit({unknown_method_name, Name})." + + for m in methods: genMethodRecord(m) + print "method_record(Name) -> exit({unknown_method_name, Name})." + + for m in methods: genMethodFieldNames(m) + print "method_fieldnames(Name) -> exit({unknown_method_name, Name})." + + for m in methods: genDecodeMethodFields(m) + print "decode_method_fields(Name, BinaryFields) ->" + print " rabbit_misc:frame_error(Name, BinaryFields)." + + for c in spec.allClasses(): genDecodeProperties(c) + print "decode_properties(ClassId, _BinaryFields) -> exit({unknown_class_id, ClassId})." + + for m in methods: genEncodeMethodFields(m) + print "encode_method_fields(Record) -> exit({unknown_method_name, element(1, Record)})." + + for c in spec.allClasses(): genEncodeProperties(c) + print "encode_properties(Record) -> exit({unknown_properties_record, Record})." + + for (c,v,cls) in spec.constants: genLookupException(c,v,cls) + print "lookup_amqp_exception(Code) ->" + print " rabbit_log:warning(\"Unknown AMQP error code '~p'~n\", [Code])," + print " {true, ?INTERNAL_ERROR, <<\"INTERNAL_ERROR\">>}." + + for(c,v,cls) in spec.constants: genAmqpException(c,v,cls) + print "amqp_exception(_Code) -> undefined." + +def genHrl(spec): + def fieldNameList(fields): + return ', '.join([erlangize(f.name) for f in fields]) + + def fieldNameListDefaults(fields): + def fillField(field): + result = erlangize(f.name) + if field.defaultvalue != None: + conv_fn = erlangDefaultValueTypeConvMap[type(field.defaultvalue)] + result += ' = ' + conv_fn(field.defaultvalue) + return result + return ', '.join([fillField(f) for f in fields]) + + methods = spec.allMethods() + + printFileHeader() + print "-define(PROTOCOL_PORT, %d)." % (spec.port) + + for (c,v,cls) in spec.constants: + print "-define(%s, %s)." % (erlangConstantName(c), v) + + print "%% Method field records." + for m in methods: + print "-record(%s, {%s})." % (m.erlangName(), fieldNameListDefaults(m.arguments)) + + print "%% Class property records." + for c in spec.allClasses(): + print "-record('P_%s', {%s})." % (erlangize(c.name), fieldNameList(c.fields)) + + +def generateErl(specPath): + genErl(AmqpSpec(specPath)) + +def generateHrl(specPath): + genHrl(AmqpSpec(specPath)) + +if __name__ == "__main__": + do_main_dict({"header": generateHrl, + "body": generateErl}) + diff --git a/rabbitmq-server-3.3.5/codegen/LICENSE b/rabbitmq-server-3.3.5/codegen/LICENSE new file mode 100644 index 0000000..c092f14 --- /dev/null +++ b/rabbitmq-server-3.3.5/codegen/LICENSE @@ -0,0 +1,6 @@ +This package, the RabbitMQ code generation library and associated +files, is licensed under the MPL. For the MPL, please see +LICENSE-MPL-RabbitMQ. + +If you have any questions regarding licensing, please contact us at +info@rabbitmq.com. diff --git a/rabbitmq-server-3.3.5/codegen/LICENSE-MPL-RabbitMQ b/rabbitmq-server-3.3.5/codegen/LICENSE-MPL-RabbitMQ new file mode 100644 index 0000000..c87c1a3 --- /dev/null +++ b/rabbitmq-server-3.3.5/codegen/LICENSE-MPL-RabbitMQ @@ -0,0 +1,455 @@ + MOZILLA PUBLIC LICENSE + Version 1.1 + + --------------- + +1. Definitions. + + 1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. + + 1.1. "Contributor" means each entity that creates or contributes to + the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications + made by that particular Contributor. + + 1.3. "Covered Code" means the Original Code or Modifications or the + combination of the Original Code and Modifications, in each case + including portions thereof. + + 1.4. "Electronic Distribution Mechanism" means a mechanism generally + accepted in the software development community for the electronic + transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source + Code. + + 1.6. "Initial Developer" means the individual or entity identified + as the Initial Developer in the Source Code notice required by Exhibit + A. + + 1.7. "Larger Work" means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + + 1.8. "License" means this document. + + 1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + A. Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. + + B. Any new file that contains any part of the Original Code or + previous Modifications. + + 1.10. "Original Code" means Source Code of computer software code + which is described in the Source Code notice required by Exhibit A as + Original Code, and which, at the time of its release under this + License is not already Covered Code governed by this License. + + 1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, + and apparatus claims, in any patent Licensable by grantor. + + 1.11. "Source Code" means the preferred form of the Covered Code for + making modifications to it, including all modules it contains, plus + any associated interface definition files, scripts used to control + compilation and installation of an Executable, or source code + differential comparisons against either the Original Code or another + well known, available Covered Code of the Contributor's choice. The + Source Code can be in a compressed or archival form, provided the + appropriate decompression or de-archiving software is widely available + for no charge. + + 1.12. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License or a future version of this License issued under Section 6.1. + For legal entities, "You" includes any entity which controls, is + controlled by, or is under common control with You. For purposes of + this definition, "control" means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty percent + (50%) of the outstanding shares or beneficial ownership of such + entity. + +2. Source Code License. + + 2.1. The Initial Developer Grant. + The Initial Developer hereby grants You a world-wide, royalty-free, + non-exclusive license, subject to third party intellectual property + claims: + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original + Code (or portions thereof) with or without Modifications, and/or + as part of a Larger Work; and + + (b) under Patents Claims infringed by the making, using or + selling of Original Code, to make, have made, use, practice, + sell, and offer for sale, and/or otherwise dispose of the + Original Code (or portions thereof). + + (c) the licenses granted in this Section 2.1(a) and (b) are + effective on the date Initial Developer first distributes + Original Code under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) + separate from the Original Code; or 3) for infringements caused + by: i) the modification of the Original Code or ii) the + combination of the Original Code with other software or devices. + + 2.2. Contributor Grant. + Subject to third party intellectual property claims, each Contributor + hereby grants You a world-wide, royalty-free, non-exclusive license + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor, to use, reproduce, modify, + display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an + unmodified basis, with other Modifications, as Covered Code + and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either alone + and/or in combination with its Contributor Version (or portions + of such combination), to make, use, sell, offer for sale, have + made, and/or otherwise dispose of: 1) Modifications made by that + Contributor (or portions thereof); and 2) the combination of + Modifications made by that Contributor with its Contributor + Version (or portions of such combination). + + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of + the Covered Code. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: 1) for any code that Contributor has deleted from the + Contributor Version; 2) separate from the Contributor Version; + 3) for infringements caused by: i) third party modifications of + Contributor Version or ii) the combination of Modifications made + by that Contributor with other software (except as part of the + Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by + that Contributor. + +3. Distribution Obligations. + + 3.1. Application of License. + The Modifications which You create or to which You contribute are + governed by the terms of this License, including without limitation + Section 2.2. The Source Code version of Covered Code may be + distributed only under the terms of this License or a future version + of this License released under Section 6.1, and You must include a + copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code + version that alters or restricts the applicable version of this + License or the recipients' rights hereunder. However, You may include + an additional document offering the additional rights described in + Section 3.5. + + 3.2. Availability of Source Code. + Any Modification which You create or to which You contribute must be + made available in Source Code form under the terms of this License + either on the same media as an Executable version or via an accepted + Electronic Distribution Mechanism to anyone to whom you made an + Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) + months after the date it initially became available, or at least six + (6) months after a subsequent version of that particular Modification + has been made available to such recipients. You are responsible for + ensuring that the Source Code version remains available even if the + Electronic Distribution Mechanism is maintained by a third party. + + 3.3. Description of Modifications. + You must cause all Covered Code to which You contribute to contain a + file documenting the changes You made to create that Covered Code and + the date of any change. You must include a prominent statement that + the Modification is derived, directly or indirectly, from Original + Code provided by the Initial Developer and including the name of the + Initial Developer in (a) the Source Code, and (b) in any notice in an + Executable version or related documentation in which You describe the + origin or ownership of the Covered Code. + + 3.4. Intellectual Property Matters + (a) Third Party Claims. + If Contributor has knowledge that a license under a third party's + intellectual property rights is required to exercise the rights + granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code + distribution titled "LEGAL" which describes the claim and the + party making the claim in sufficient detail that a recipient will + know whom to contact. If Contributor obtains such knowledge after + the Modification is made available as described in Section 3.2, + Contributor shall promptly modify the LEGAL file in all copies + Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) + reasonably calculated to inform those who received the Covered + Code that new knowledge has been obtained. + + (b) Contributor APIs. + If Contributor's Modifications include an application programming + interface and Contributor has knowledge of patent licenses which + are reasonably necessary to implement that API, Contributor must + also include this information in the LEGAL file. + + (c) Representations. + Contributor represents that, except as disclosed pursuant to + Section 3.4(a) above, Contributor believes that Contributor's + Modifications are Contributor's original creation(s) and/or + Contributor has sufficient rights to grant the rights conveyed by + this License. + + 3.5. Required Notices. + You must duplicate the notice in Exhibit A in each file of the Source + Code. If it is not possible to put such notice in a particular Source + Code file due to its structure, then You must include such notice in a + location (such as a relevant directory) where a user would be likely + to look for such a notice. If You created one or more Modification(s) + You may add your name as a Contributor to the notice described in + Exhibit A. You must also duplicate this License in any documentation + for the Source Code where You describe recipients' rights or ownership + rights relating to Covered Code. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear than + any such warranty, support, indemnity or liability obligation is + offered by You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of warranty, + support, indemnity or liability terms You offer. + + 3.6. Distribution of Executable Versions. + You may distribute Covered Code in Executable form only if the + requirements of Section 3.1-3.5 have been met for that Covered Code, + and if You include a notice stating that the Source Code version of + the Covered Code is available under the terms of this License, + including a description of how and where You have fulfilled the + obligations of Section 3.2. The notice must be conspicuously included + in any notice in an Executable version, related documentation or + collateral in which You describe recipients' rights relating to the + Covered Code. You may distribute the Executable version of Covered + Code or ownership rights under a license of Your choice, which may + contain terms different from this License, provided that You are in + compliance with the terms of this License and that the license for the + Executable version does not attempt to limit or alter the recipient's + rights in the Source Code version from the rights set forth in this + License. If You distribute the Executable version under a different + license You must make it absolutely clear that any terms which differ + from this License are offered by You alone, not by the Initial + Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such + terms You offer. + + 3.7. Larger Works. + You may create a Larger Work by combining Covered Code with other code + not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Code. + +4. Inability to Comply Due to Statute or Regulation. + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description + must be included in the LEGAL file described in Section 3.4 and must + be included with all distributions of the Source Code. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Application of this License. + + This License applies to code to which the Initial Developer has + attached the notice in Exhibit A and to related Covered Code. + +6. Versions of the License. + + 6.1. New Versions. + Netscape Communications Corporation ("Netscape") may publish revised + and/or new versions of the License from time to time. Each version + will be given a distinguishing version number. + + 6.2. Effect of New Versions. + Once Covered Code has been published under a particular version of the + License, You may always continue to use it under the terms of that + version. You may also choose to use such Covered Code under the terms + of any subsequent version of the License published by Netscape. No one + other than Netscape has the right to modify the terms applicable to + Covered Code created under this License. + + 6.3. Derivative Works. + If You create or use a modified version of this License (which you may + only do in order to apply it to code which is not already Covered Code + governed by this License), You must (a) rename Your license so that + the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", + "MPL", "NPL" or any confusingly similar phrase do not appear in your + license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license + contains terms which differ from the Mozilla Public License and + Netscape Public License. (Filling in the name of the Initial + Developer, Original Code or Contributor in the notice described in + Exhibit A shall not of themselves be deemed to be modifications of + this License.) + +7. DISCLAIMER OF WARRANTY. + + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE + IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, + YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE + COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER + OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +8. TERMINATION. + + 8.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall + survive any termination of this License. Provisions which, by their + nature, must remain in effect beyond the termination of this License + shall survive. + + 8.2. If You initiate litigation by asserting a patent infringement + claim (excluding declatory judgment actions) against Initial Developer + or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + + (a) such Participant's Contributor Version directly or indirectly + infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License + shall, upon 60 days notice from Participant terminate prospectively, + unless if within 60 days after receipt of notice You either: (i) + agree in writing to pay Participant a mutually agreeable reasonable + royalty for Your past and future use of Modifications made by such + Participant, or (ii) withdraw Your litigation claim with respect to + the Contributor Version against such Participant. If within 60 days + of notice, a reasonable royalty and payment arrangement are not + mutually agreed upon in writing by the parties or the litigation claim + is not withdrawn, the rights granted by Participant to You under + Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + + (b) any software, hardware, or device, other than such Participant's + Contributor Version, directly or indirectly infringes any patent, then + any rights granted to You by such Participant under Sections 2.1(b) + and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that + Participant. + + 8.3. If You assert a patent infringement claim against Participant + alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as + by license or settlement) prior to the initiation of patent + infringement litigation, then the reasonable value of the licenses + granted by such Participant under Sections 2.1 or 2.2 shall be taken + into account in determining the amount or value of any payment or + license. + + 8.4. In the event of termination under Sections 8.1 or 8.2 above, + all end user license agreements (excluding distributors and resellers) + which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination. + +9. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, + OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR + ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY + CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, + WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE + EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO + THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +10. U.S. GOVERNMENT END USERS. + + The Covered Code is a "commercial item," as that term is defined in + 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer + software" and "commercial computer software documentation," as such + terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 + C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), + all U.S. Government End Users acquire Covered Code with only those + rights set forth herein. + +11. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed by + California law provisions (except to the extent applicable law, if + any, provides otherwise), excluding its conflict-of-law provisions. + With respect to disputes in which at least one party is a citizen of, + or an entity chartered or registered to do business in the United + States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern + District of California, with venue lying in Santa Clara County, + California, with the losing party responsible for costs, including + without limitation, court costs and reasonable attorneys' fees and + expenses. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. + Any law or regulation which provides that the language of a contract + shall be construed against the drafter shall not apply to this + License. + +12. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, + out of its utilization of rights under this License and You agree to + work with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + +13. MULTIPLE-LICENSED CODE. + + Initial Developer may designate portions of the Covered Code as + "Multiple-Licensed". "Multiple-Licensed" means that the Initial + Developer permits you to utilize portions of the Covered Code under + Your choice of the NPL or the alternative licenses, if any, specified + by the Initial Developer in the file described in Exhibit A. + +EXHIBIT A -Mozilla Public License. + + ``The contents of this file are subject to the Mozilla Public License + Version 1.1 (the "License"); you may not use this file except in + compliance with the License. You may obtain a copy of the License at + http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + License for the specific language governing rights and limitations + under the License. + + The Original Code is RabbitMQ. + + The Initial Developer of the Original Code is GoPivotal, Inc. + Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved.'' + + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.] diff --git a/rabbitmq-server-3.3.5/codegen/Makefile b/rabbitmq-server-3.3.5/codegen/Makefile new file mode 100644 index 0000000..8c3bfa9 --- /dev/null +++ b/rabbitmq-server-3.3.5/codegen/Makefile @@ -0,0 +1,8 @@ +all: + echo "Please select a target from the Makefile." + +clean: + rm -f *.pyc + +distclean: clean + find . -regex '.*\(~\|#\|\.swp\)' -exec rm {} \; diff --git a/rabbitmq-server-3.3.5/codegen/README.extensions.md b/rabbitmq-server-3.3.5/codegen/README.extensions.md new file mode 100644 index 0000000..74b24fc --- /dev/null +++ b/rabbitmq-server-3.3.5/codegen/README.extensions.md @@ -0,0 +1,189 @@ +# Protocol extensions + +The `amqp_codegen.py` AMQP specification compiler has recently been +enhanced to take more than a single specification file, which allows +AMQP library authors to include extensions to the core protocol +without needing to modify the core AMQP specification file as +distributed. + +The compiler is invoked with the path to a single "main" specification +document and zero or more paths to "extension" documents. + +The order of the extensions matters: any later class property +definitions, for instance, are added to the list of definitions in +order of appearance. In general, composition of extensions with a core +specification document is therefore non-commutative. + +## The main document + +Written in the style of a +[json-shapes](http://github.com/tonyg/json-shapes) schema: + + DomainDefinition = _and(array_of(string()), array_length_equals(2)); + + ConstantDefinition = { + "name": string(), + "value": number(), + "class": optional(_or("soft-error", "hard-error")) + }; + + FieldDefinition = { + "name": string(), + "type": string(), + "default-value": optional(anything()) + }; + + MethodDefinition = { + "name": string(), + "id": number(), + "arguments": array_of(FieldDefinition), + "synchronous": optional(boolean()), + "content": optional(boolean()) + }; + + ClassDefinition = { + "name": string(), + "id": number(), + "methods": array_of(MethodDefinition), + "properties": optional(array_of(FieldDefinition)) + }; + + MainDocument = { + "major-version": number(), + "minor-version": number(), + "revision": optional(number()), + "port": number(), + "domains": array_of(DomainDefinition), + "constants": array_of(ConstantDefinition), + "classes": array_of(ClassDefinition), + } + +Within a `FieldDefinition`, the keyword `domain` can be used instead +of `type`, but `type` is preferred and `domain` is deprecated. + +Type names can either be a defined `domain` name or a built-in name +from the following list: + + - octet + - shortstr + - longstr + - short + - long + - longlong + - bit + - table + - timestamp + +Method and class IDs must be integers between 0 and 65535, +inclusive. Note that there is no specific subset of the space reserved +for experimental or site-local extensions, so be careful not to +conflict with IDs used by the AMQP core specification. + +If the `synchronous` field of a `MethodDefinition` is missing, it is +assumed to be `false`; the same applies to the `content` field. + +A `ConstantDefinition` with a `class` attribute is considered to be an +error-code definition; otherwise, it is considered to be a +straightforward numeric constant. + +## Extensions + +Written in the style of a +[json-shapes](http://github.com/tonyg/json-shapes) schema, and +referencing some of the type definitions given above: + + ExtensionDocument = { + "extension": anything(), + "domains": array_of(DomainDefinition), + "constants": array_of(ConstantDefinition), + "classes": array_of(ClassDefinition) + }; + +The `extension` keyword is used to describe the extension informally +for human readers. Typically it will be a dictionary, with members +such as: + + { + "name": "The name of the extension", + "version": "1.0", + "copyright": "Copyright (C) 1234 Yoyodyne, Inc." + } + +## Merge behaviour + +In the case of conflicts between values specified in the main document +and in any extension documents, type-specific merge operators are +invoked. + + - Any doubly-defined domain names are regarded as true + conflicts. Otherwise, all the domain definitions from all the main + and extension documents supplied to the compiler are merged into a + single dictionary. + + - Constant definitions are treated as per domain names above, + *mutatis mutandis*. + + - Classes and their methods are a little trickier: if an extension + defines a class with the same name as one previously defined, then + only the `methods` and `properties` fields of the extension's class + definition are attended to. + + - Any doubly-defined method names or property names within a class + are treated as true conflicts. + + - Properties defined in an extension are added to the end of the + extant property list for the class. + + (Extensions are of course permitted to define brand new classes as + well as to extend existing ones.) + + - Any other kind of conflict leads to a raised + `AmqpSpecFileMergeConflict` exception. + +## Invoking the spec compiler + +Your code generation code should invoke `amqp_codegen.do_main_dict` +with a dictionary of functions as the sole argument. Each will be +used for generationg a separate file. The `do_main_dict` function +will parse the command-line arguments supplied when python was +invoked. + +The command-line will be parsed as: + + python your_codegen.py [ ...] + +where `` is a key into the dictionary supplied to +`do_main_dict` and is used to select which generation function is +called. The `` and `` arguments are file names of +specification documents containing expressions in the syntax given +above. The *final* argument on the command line, ``, is the +name of the source-code file to generate. + +Here's a tiny example of the layout of a code generation module that +uses `amqp_codegen`: + + import amqp_codegen + + def generateHeader(specPath): + spec = amqp_codegen.AmqpSpec(specPath) + ... + + def generateImpl(specPath): + spec = amqp_codegen.AmqpSpec(specPath) + ... + + if __name__ == "__main__": + amqp_codegen.do_main_dict({"header": generateHeader, + "body": generateImpl}) + +The reasons for allowing more than one action, are that + + - many languages have separate "header"-type files (C and Erlang, to + name two) + - `Makefile`s often require separate rules for generating the two + kinds of file, but it's convenient to keep the generation code + together in a single python module + +The main reason things are laid out this way, however, is simply that +it's an accident of the history of the code. We may change the API to +`amqp_codegen` in future to clean things up a little. diff --git a/rabbitmq-server-3.3.5/codegen/amqp-rabbitmq-0.8.json b/rabbitmq-server-3.3.5/codegen/amqp-rabbitmq-0.8.json new file mode 100644 index 0000000..35f8856 --- /dev/null +++ b/rabbitmq-server-3.3.5/codegen/amqp-rabbitmq-0.8.json @@ -0,0 +1,659 @@ +{ + "name": "AMQP", + "major-version": 8, + "minor-version": 0, + "port": 5672, + "copyright": [ + "Copyright (C) 2008-2013 GoPivotal, Inc.\n", + "\n", + "Permission is hereby granted, free of charge, to any person\n", + "obtaining a copy of this file (the \"Software\"), to deal in the\n", + "Software without restriction, including without limitation the \n", + "rights to use, copy, modify, merge, publish, distribute, \n", + "sublicense, and/or sell copies of the Software, and to permit \n", + "persons to whom the Software is furnished to do so, subject to \n", + "the following conditions:\n", + "\n", + "The above copyright notice and this permission notice shall be\n", + "included in all copies or substantial portions of the Software.\n", + "\n", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n", + "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n", + "OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n", + "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n", + "HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n", + "WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n", + "OTHER DEALINGS IN THE SOFTWARE.\n", + "\n", + "Class information entered from amqp_xml0-8.pdf and domain types from amqp-xml-doc0-9.pdf\n", + "\n", + "b3cb053f15e7b98808c0ccc67f23cb3e amqp_xml0-8.pdf\n", + "http://www.twiststandards.org/index.php?option=com_docman&task=cat_view&gid=28&&Itemid=90\n", + "8444db91e2949dbecfb2585e9eef6d64 amqp-xml-doc0-9.pdf\n", + "https://jira.amqp.org/confluence/download/attachments/720900/amqp-xml-doc0-9.pdf?version=1\n"], + + "domains": [ + ["access-ticket", "short"], + ["bit", "bit"], + ["channel-id", "longstr"], + ["class-id", "short"], + ["consumer-tag", "shortstr"], + ["delivery-tag", "longlong"], + ["destination", "shortstr"], + ["duration", "longlong"], + ["exchange-name", "shortstr"], + ["known-hosts", "shortstr"], + ["long", "long"], + ["longlong", "longlong"], + ["longstr", "longstr"], + ["method-id", "short"], + ["no-ack", "bit"], + ["no-local", "bit"], + ["octet", "octet"], + ["offset", "longlong"], + ["path", "shortstr"], + ["peer-properties", "table"], + ["queue-name", "shortstr"], + ["redelivered", "bit"], + ["reference", "longstr"], + ["reject-code", "short"], + ["reject-text", "shortstr"], + ["reply-code", "short"], + ["reply-text", "shortstr"], + ["security-token", "longstr"], + ["short", "short"], + ["shortstr", "shortstr"], + ["table", "table"], + ["timestamp", "timestamp"] + ], + + "constants": [ + {"name": "FRAME-METHOD", "value": 1}, + {"name": "FRAME-HEADER", "value": 2}, + {"name": "FRAME-BODY", "value": 3}, + {"name": "FRAME-OOB-METHOD", "value": 4}, + {"name": "FRAME-OOB-HEADER", "value": 5}, + {"name": "FRAME-OOB-BODY", "value": 6}, + {"name": "FRAME-TRACE", "value": 7}, + {"name": "FRAME-HEARTBEAT", "value": 8}, + {"name": "FRAME-MIN-SIZE", "value": 4096}, + {"name": "FRAME-END", "value": 206}, + {"name": "REPLY-SUCCESS", "value": 200}, + {"name": "NOT-DELIVERED", "value": 310, "class": "soft-error"}, + {"name": "CONTENT-TOO-LARGE", "value": 311, "class": "soft-error"}, + {"name": "NO-ROUTE", "value": 312, "class": "soft-error"}, + {"name": "NO-CONSUMERS", "value": 313, "class": "soft-error"}, + {"name": "ACCESS-REFUSED", "value": 403, "class": "soft-error"}, + {"name": "NOT-FOUND", "value": 404, "class": "soft-error"}, + {"name": "RESOURCE-LOCKED", "value": 405, "class": "soft-error"}, + {"name": "PRECONDITION-FAILED", "value": 406, "class": "soft-error"}, + {"name": "CONNECTION-FORCED", "value": 320, "class": "hard-error"}, + {"name": "INVALID-PATH", "value": 402, "class": "hard-error"}, + {"name": "FRAME-ERROR", "value": 501, "class": "hard-error"}, + {"name": "SYNTAX-ERROR", "value": 502, "class": "hard-error"}, + {"name": "COMMAND-INVALID", "value": 503, "class": "hard-error"}, + {"name": "CHANNEL-ERROR", "value": 504, "class": "hard-error"}, + {"name": "UNEXPECTED-FRAME", "value": 505, "class": "hard-error"}, + {"name": "RESOURCE-ERROR", "value": 506, "class": "hard-error"}, + {"name": "NOT-ALLOWED", "value": 530, "class": "hard-error"}, + {"name": "NOT-IMPLEMENTED", "value": 540, "class": "hard-error"}, + {"name": "INTERNAL-ERROR", "value": 541, "class": "hard-error"} + ], + + "classes": [ + { + "id": 10, + "methods": [{"id": 10, + "arguments": [{"type": "octet", "name": "version-major", "default-value": 0}, + {"type": "octet", "name": "version-minor", "default-value": 8}, + {"domain": "peer-properties", "name": "server-properties"}, + {"type": "longstr", "name": "mechanisms", "default-value": "PLAIN"}, + {"type": "longstr", "name": "locales", "default-value": "en_US"}], + "name": "start", + "synchronous" : true}, + {"id": 11, + "arguments": [{"domain": "peer-properties", "name": "client-properties"}, + {"type": "shortstr", "name": "mechanism", "default-value": "PLAIN"}, + {"type": "longstr", "name": "response"}, + {"type": "shortstr", "name": "locale", "default-value": "en_US"}], + "name": "start-ok"}, + {"id": 20, + "arguments": [{"type": "longstr", "name": "challenge"}], + "name": "secure", + "synchronous" : true}, + {"id": 21, + "arguments": [{"type": "longstr", "name": "response"}], + "name": "secure-ok"}, + {"id": 30, + "arguments": [{"type": "short", "name": "channel-max", "default-value": 0}, + {"type": "long", "name": "frame-max", "default-value": 0}, + {"type": "short", "name": "heartbeat", "default-value": 0}], + "name": "tune", + "synchronous" : true}, + {"id": 31, + "arguments": [{"type": "short", "name": "channel-max", "default-value": 0}, + {"type": "long", "name": "frame-max", "default-value": 0}, + {"type": "short", "name": "heartbeat", "default-value": 0}], + "name": "tune-ok"}, + {"id": 40, + "arguments": [{"type": "shortstr", "name": "virtual-host", "default-value": "/"}, + {"type": "shortstr", "name": "capabilities", "default-value": ""}, + {"type": "bit", "name": "insist", "default-value": false}], + "name": "open", + "synchronous" : true}, + {"id": 41, + "arguments": [{"type": "shortstr", "name": "known-hosts", "default-value": ""}], + "name": "open-ok"}, + {"id": 50, + "arguments": [{"type": "shortstr", "name": "host"}, + {"type": "shortstr", "name": "known-hosts", "default-value": ""}], + "name": "redirect"}, + {"id": 60, + "arguments": [{"type": "short", "name": "reply-code"}, + {"type": "shortstr", "name": "reply-text", "default-value": ""}, + {"type": "short", "name": "class-id"}, + {"type": "short", "name": "method-id"}], + "name": "close", + "synchronous" : true}, + {"id": 61, + "arguments": [], + "name": "close-ok"}], + "name": "connection", + "properties": [] + }, + { + "id": 20, + "methods": [{"id": 10, + "arguments": [{"type": "shortstr", "name": "out-of-band", "default-value": ""}], + "name": "open", + "synchronous" : true}, + {"id": 11, + "arguments": [], + "name": "open-ok"}, + {"id": 20, + "arguments": [{"type": "bit", "name": "active"}], + "name": "flow", + "synchronous" : true}, + {"id": 21, + "arguments": [{"type": "bit", "name": "active"}], + "name": "flow-ok"}, + {"id": 30, + "arguments": [{"type": "short", "name": "reply-code"}, + {"type": "shortstr", "name": "reply-text", "default-value": ""}, + {"type": "table", "name": "details", "default-value": {}}], + "name": "alert"}, + {"id": 40, + "arguments": [{"type": "short", "name": "reply-code"}, + {"type": "shortstr", "name": "reply-text", "default-value": ""}, + {"type": "short", "name": "class-id"}, + {"type": "short", "name": "method-id"}], + "name": "close", + "synchronous" : true}, + {"id": 41, + "arguments": [], + "name": "close-ok"}], + "name": "channel" + }, + { + "id": 30, + "methods": [{"id": 10, + "arguments": [{"type": "shortstr", "name": "realm", "default-value": "/data"}, + {"type": "bit", "name": "exclusive", "default-value": false}, + {"type": "bit", "name": "passive", "default-value": true}, + {"type": "bit", "name": "active", "default-value": true}, + {"type": "bit", "name": "write", "default-value": true}, + {"type": "bit", "name": "read", "default-value": true}], + "name": "request", + "synchronous" : true}, + {"id": 11, + "arguments": [{"type": "short", "name": "ticket", "default-value": 1}], + "name": "request-ok"}], + "name": "access" + }, + { + "id": 40, + "methods": [{"id": 10, + "arguments": [{"type": "short", "name": "ticket", "default-value": 1}, + {"type": "shortstr", "name": "exchange"}, + {"type": "shortstr", "name": "type", "default-value": "direct"}, + {"type": "bit", "name": "passive", "default-value": false}, + {"type": "bit", "name": "durable", "default-value": false}, + {"type": "bit", "name": "auto-delete", "default-value": false}, + {"type": "bit", "name": "internal", "default-value": false}, + {"type": "bit", "name": "nowait", "default-value": false}, + {"type": "table", "name": "arguments", "default-value": {}}], + "name": "declare", + "synchronous" : true}, + {"id": 11, + "arguments": [], + "name": "declare-ok"}, + {"id": 20, + "arguments": [{"type": "short", "name": "ticket", "default-value": 1}, + {"type": "shortstr", "name": "exchange"}, + {"type": "bit", "name": "if-unused", "default-value": false}, + {"type": "bit", "name": "nowait", "default-value": false}], + "name": "delete", + "synchronous" : true}, + {"id": 21, + "arguments": [], + "name": "delete-ok"}], + "name": "exchange" + }, + { + "id": 50, + "methods": [{"id": 10, + "arguments": [{"type": "short", "name": "ticket", "default-value": 1}, + {"type": "shortstr", "name": "queue", "default-value": ""}, + {"type": "bit", "name": "passive", "default-value": false}, + {"type": "bit", "name": "durable", "default-value": false}, + {"type": "bit", "name": "exclusive", "default-value": false}, + {"type": "bit", "name": "auto-delete", "default-value": false}, + {"type": "bit", "name": "nowait", "default-value": false}, + {"type": "table", "name": "arguments", "default-value": {}}], + "name": "declare", + "synchronous" : true}, + {"id": 11, + "arguments": [{"type": "shortstr", "name": "queue"}, + {"type": "long", "name": "message-count"}, + {"type": "long", "name": "consumer-count"}], + "name": "declare-ok"}, + {"id": 20, + "arguments": [{"type": "short", "name": "ticket", "default-value": 1}, + {"type": "shortstr", "name": "queue", "default-value": ""}, + {"type": "shortstr", "name": "exchange"}, + {"type": "shortstr", "name": "routing-key", "default-value": ""}, + {"type": "bit", "name": "nowait", "default-value": false}, + {"type": "table", "name": "arguments", "default-value": {}}], + "name": "bind", + "synchronous" : true}, + {"id": 21, + "arguments": [], + "name": "bind-ok"}, + {"id": 30, + "arguments": [{"type": "short", "name": "ticket", "default-value": 1}, + {"type": "shortstr", "name": "queue", "default-value": ""}, + {"type": "bit", "name": "nowait", "default-value": false}], + "name": "purge", + "synchronous" : true}, + {"id": 31, + "arguments": [{"type": "long", "name": "message-count"}], + "name": "purge-ok"}, + {"id": 40, + "arguments": [{"type": "short", "name": "ticket", "default-value": 1}, + {"type": "shortstr", "name": "queue", "default-value": ""}, + {"type": "bit", "name": "if-unused", "default-value": false}, + {"type": "bit", "name": "if-empty", "default-value": false}, + {"type": "bit", "name": "nowait", "default-value": false}], + "name": "delete", + "synchronous" : true}, + {"id": 41, + "arguments": [{"type": "long", "name": "message-count"}], + "name": "delete-ok"}, + {"id": 50, + "arguments": [{"type": "short", "name": "ticket", "default-value": 1}, + {"type": "shortstr", "name": "queue", "default-value": ""}, + {"type": "shortstr", "name": "exchange"}, + {"type": "shortstr", "name": "routing-key", "default-value": ""}, + {"type": "table", "name": "arguments", "default-value": {}}], + "name": "unbind", + "synchronous" : true}, + {"id": 51, + "arguments": [], + "name": "unbind-ok"} + ], + "name": "queue" + }, + { + "id": 60, + "methods": [{"id": 10, + "arguments": [{"type": "long", "name": "prefetch-size", "default-value": 0}, + {"type": "short", "name": "prefetch-count", "default-value": 0}, + {"type": "bit", "name": "global", "default-value": false}], + "name": "qos", + "synchronous" : true}, + {"id": 11, + "arguments": [], + "name": "qos-ok"}, + {"id": 20, + "arguments": [{"domain": "access-ticket", "name": "ticket", "default-value": 1}, + {"type": "shortstr", "name": "queue", "default-value": ""}, + {"type": "shortstr", "name": "consumer-tag", "default-value": ""}, + {"type": "bit", "name": "no-local", "default-value": false}, + {"type": "bit", "name": "no-ack", "default-value": false}, + {"type": "bit", "name": "exclusive", "default-value": false}, + {"type": "bit", "name": "nowait", "default-value": false}], + "name": "consume", + "synchronous" : true}, + {"id": 21, + "arguments": [{"type": "shortstr", "name": "consumer-tag"}], + "name": "consume-ok"}, + {"id": 30, + "arguments": [{"type": "shortstr", "name": "consumer-tag"}, + {"type": "bit", "name": "nowait", "default-value": false}], + "name": "cancel", + "synchronous" : true}, + {"id": 31, + "arguments": [{"type": "shortstr", "name": "consumer-tag"}], + "name": "cancel-ok"}, + {"content": true, + "id": 40, + "arguments": [{"type": "short", "name": "ticket", "default-value": 1}, + {"type": "shortstr", "name": "exchange", "default-value": ""}, + {"type": "shortstr", "name": "routing-key", "default-value": ""}, + {"type": "bit", "name": "mandatory", "default-value": false}, + {"type": "bit", "name": "immediate", "default-value": false}], + "name": "publish"}, + {"content": true, + "id": 50, + "arguments": [{"type": "short", "name": "reply-code"}, + {"type": "shortstr", "name": "reply-text", "default-value": ""}, + {"type": "shortstr", "name": "exchange"}, + {"type": "shortstr", "name": "routing-key"}], + "name": "return"}, + {"content": true, + "id": 60, + "arguments": [{"type": "shortstr", "name": "consumer-tag"}, + {"type": "longlong", "name": "delivery-tag"}, + {"type": "bit", "name": "redelivered", "default-value": false}, + {"type": "shortstr", "name": "exchange"}, + {"type": "shortstr", "name": "routing-key"}], + "name": "deliver"}, + {"id": 70, + "arguments": [{"type": "short", "name": "ticket", "default-value": 1}, + {"type": "shortstr", "name": "queue", "default-value": ""}, + {"type": "bit", "name": "no-ack", "default-value": false}], + "name": "get", + "synchronous" : true}, + {"content": true, + "id": 71, + "arguments": [{"type": "longlong", "name": "delivery-tag"}, + {"type": "bit", "name": "redelivered", "default-value": false}, + {"type": "shortstr", "name": "exchange"}, + {"type": "shortstr", "name": "routing-key"}, + {"type": "long", "name": "message-count"}], + "name": "get-ok"}, + {"id": 72, + "arguments": [{"type": "shortstr", "name": "cluster-id", "default-value": ""}], + "name": "get-empty"}, + {"id": 80, + "arguments": [{"type": "longlong", "name": "delivery-tag", "default-value": 0}, + {"type": "bit", "name": "multiple", "default-value": false}], + "name": "ack"}, + {"id": 90, + "arguments": [{"type": "longlong", "name": "delivery-tag"}, + {"type": "bit", "name": "requeue", "default-value": true}], + "name": "reject"}, + {"id": 100, + "arguments": [{"type": "bit", "name": "requeue", "default-value": false}], + "name": "recover-async"}, + {"id": 110, + "arguments": [{"type": "bit", "name": "requeue", "default-value": false}], + "name": "recover", + "synchronous" : true}, + {"id": 111, + "arguments": [], + "name": "recover-ok"}], + "name": "basic", + "properties": [{"type": "shortstr", "name": "content-type"}, + {"type": "shortstr", "name": "content-encoding"}, + {"type": "table", "name": "headers"}, + {"type": "octet", "name": "delivery-mode"}, + {"type": "octet", "name": "priority"}, + {"type": "shortstr", "name": "correlation-id"}, + {"type": "shortstr", "name": "reply-to"}, + {"type": "shortstr", "name": "expiration"}, + {"type": "shortstr", "name": "message-id"}, + {"type": "timestamp", "name": "timestamp"}, + {"type": "shortstr", "name": "type"}, + {"type": "shortstr", "name": "user-id"}, + {"type": "shortstr", "name": "app-id"}, + {"type": "shortstr", "name": "cluster-id"}] + }, + { + "id": 70, + "methods": [{"id": 10, + "arguments": [{"type": "long", "name": "prefetch-size", "default-value": 0}, + {"type": "short", "name": "prefetch-count", "default-value": 0}, + {"type": "bit", "name": "global", "default-value": false}], + "name": "qos", + "synchronous" : true}, + {"id": 11, + "arguments": [], + "name": "qos-ok"}, + {"id": 20, + "arguments": [{"type": "short", "name": "ticket", "default-value": 1}, + {"type": "shortstr", "name": "queue", "default-value": ""}, + {"type": "shortstr", "name": "consumer-tag", "default-value": ""}, + {"type": "bit", "name": "no-local", "default-value": false}, + {"type": "bit", "name": "no-ack", "default-value": false}, + {"type": "bit", "name": "exclusive", "default-value": false}, + {"type": "bit", "name": "nowait", "default-value": false}], + "name": "consume", + "synchronous" : true}, + {"id": 21, + "arguments": [{"type": "shortstr", "name": "consumer-tag"}], + "name": "consume-ok"}, + {"id": 30, + "arguments": [{"type": "shortstr", "name": "consumer-tag"}, + {"type": "bit", "name": "nowait", "default-value": false}], + "name": "cancel", + "synchronous" : true}, + {"id": 31, + "arguments": [{"type": "shortstr", "name": "consumer-tag"}], + "name": "cancel-ok"}, + {"id": 40, + "arguments": [{"type": "shortstr", "name": "identifier"}, + {"type": "longlong", "name": "content-size"}], + "name": "open", + "synchronous" : true}, + {"id": 41, + "arguments": [{"type": "longlong", "name": "staged-size"}], + "name": "open-ok"}, + {"content": true, + "id": 50, + "arguments": [], + "name": "stage"}, + {"id": 60, + "arguments": [{"type": "short", "name": "ticket", "default-value": 1}, + {"type": "shortstr", "name": "exchange", "default-value": ""}, + {"type": "shortstr", "name": "routing-key", "default-value": ""}, + {"type": "bit", "name": "mandatory", "default-value": false}, + {"type": "bit", "name": "immediate", "default-value": false}, + {"type": "shortstr", "name": "identifier"}], + "name": "publish"}, + {"content": true, + "id": 70, + "arguments": [{"type": "short", "name": "reply-code", "default-value": 200}, + {"type": "shortstr", "name": "reply-text", "default-value": ""}, + {"type": "shortstr", "name": "exchange"}, + {"type": "shortstr", "name": "routing-key"}], + "name": "return"}, + {"id": 80, + "arguments": [{"type": "shortstr", "name": "consumer-tag"}, + {"type": "longlong", "name": "delivery-tag"}, + {"type": "bit", "name": "redelivered", "default-value": false}, + {"type": "shortstr", "name": "exchange"}, + {"type": "shortstr", "name": "routing-key"}, + {"type": "shortstr", "name": "identifier"}], + "name": "deliver"}, + {"id": 90, + "arguments": [{"type": "longlong", "name": "delivery-tag", "default-value": 0}, + {"type": "bit", "name": "multiple", "default-value": false}], + "name": "ack"}, + {"id": 100, + "arguments": [{"type": "longlong", "name": "delivery-tag"}, + {"type": "bit", "name": "requeue", "default-value": true}], + "name": "reject"}], + "name": "file", + "properties": [{"type": "shortstr", "name": "content-type"}, + {"type": "shortstr", "name": "content-encoding"}, + {"type": "table", "name": "headers"}, + {"type": "octet", "name": "priority"}, + {"type": "shortstr", "name": "reply-to"}, + {"type": "shortstr", "name": "message-id"}, + {"type": "shortstr", "name": "filename"}, + {"type": "timestamp", "name": "timestamp"}, + {"type": "shortstr", "name": "cluster-id"}] + }, + { + "id": 80, + "methods": [{"id": 10, + "arguments": [{"type": "long", "name": "prefetch-size", "default-value": 0}, + {"type": "short", "name": "prefetch-count", "default-value": 0}, + {"type": "long", "name": "consume-rate", "default-value": 0}, + {"type": "bit", "name": "global", "default-value": false}], + "name": "qos", + "synchronous" : true}, + {"id": 11, + "arguments": [], + "name": "qos-ok"}, + {"id": 20, + "arguments": [{"type": "short", "name": "ticket", "default-value": 1}, + {"type": "shortstr", "name": "queue", "default-value": ""}, + {"type": "shortstr", "name": "consumer-tag", "default-value": ""}, + {"type": "bit", "name": "no-local", "default-value": false}, + {"type": "bit", "name": "exclusive", "default-value": false}, + {"type": "bit", "name": "nowait", "default-value": false}], + "name": "consume", + "synchronous" : true}, + {"id": 21, + "arguments": [{"type": "shortstr", "name": "consumer-tag"}], + "name": "consume-ok"}, + {"id": 30, + "arguments": [{"type": "shortstr", "name": "consumer-tag"}, + {"type": "bit", "name": "nowait", "default-value": false}], + "name": "cancel", + "synchronous" : true}, + {"id": 31, + "arguments": [{"type": "shortstr", "name": "consumer-tag"}], + "name": "cancel-ok"}, + {"content": true, + "id": 40, + "arguments": [{"type": "short", "name": "ticket", "default-value": 1}, + {"type": "shortstr", "name": "exchange", "default-value": ""}, + {"type": "shortstr", "name": "routing-key", "default-value": ""}, + {"type": "bit", "name": "mandatory", "default-value": false}, + {"type": "bit", "name": "immediate", "default-value": false}], + "name": "publish"}, + {"content": true, + "id": 50, + "arguments": [{"type": "short", "name": "reply-code", "default-value": 200}, + {"type": "shortstr", "name": "reply-text", "default-value": ""}, + {"type": "shortstr", "name": "exchange"}, + {"type": "shortstr", "name": "routing-key"}], + "name": "return"}, + {"content": true, + "id": 60, + "arguments": [{"type": "shortstr", "name": "consumer-tag"}, + {"type": "longlong", "name": "delivery-tag"}, + {"type": "shortstr", "name": "exchange"}, + {"type": "shortstr", "name": "queue"}], + "name": "deliver"}], + "name": "stream", + "properties": [{"type": "shortstr", "name": "content-type"}, + {"type": "shortstr", "name": "content-encoding"}, + {"type": "table", "name": "headers"}, + {"type": "octet", "name": "priority"}, + {"type": "timestamp", "name": "timestamp"}] + }, + { + "id": 90, + "methods": [{"id": 10, + "arguments": [], + "name": "select", + "synchronous" : true}, + {"id": 11, + "arguments": [], + "name": "select-ok"}, + {"id": 20, + "arguments": [], + "name": "commit", + "synchronous" : true}, + {"id": 21, + "arguments": [], + "name": "commit-ok"}, + {"id": 30, + "arguments": [], + "name": "rollback", + "synchronous" : true}, + {"id": 31, + "arguments": [], + "name": "rollback-ok"}], + "name": "tx" + }, + { + "id": 100, + "methods": [{"id": 10, + "arguments": [], + "name": "select", + "synchronous" : true}, + {"id": 11, + "arguments": [], + "name": "select-ok"}, + {"id": 20, + "arguments": [{"type": "shortstr", "name": "dtx-identifier"}], + "name": "start", + "synchronous" : true}, + {"id": 21, + "arguments": [], "name": "start-ok"}], + "name": "dtx" + }, + { + "id": 110, + "methods": [{"content": true, + "id": 10, + "arguments": [{"type": "table", "name": "meta-data"}], + "name": "request"}], + "name": "tunnel", + "properties": [{"type": "table", "name": "headers"}, + {"type": "shortstr", "name": "proxy-name"}, + {"type": "shortstr", "name": "data-name"}, + {"type": "octet", "name": "durable"}, + {"type": "octet", "name": "broadcast"}] + }, + { + "id": 120, + "methods": [{"id": 10, + "arguments": [{"type": "octet", "name": "integer-1"}, + {"type": "short", "name": "integer-2"}, + {"type": "long", "name": "integer-3"}, + {"type": "longlong", "name": "integer-4"}, + {"type": "octet", "name": "operation"}], + "name": "integer", + "synchronous" : true}, + {"id": 11, + "arguments": [{"type": "longlong", "name": "result"}], + "name": "integer-ok"}, + {"id": 20, + "arguments": [{"type": "shortstr", "name": "string-1"}, + {"type": "longstr", "name": "string-2"}, + {"type": "octet", "name": "operation"}], + "name": "string", + "synchronous" : true}, + {"id": 21, + "arguments": [{"type": "longstr", "name": "result"}], + "name": "string-ok"}, + {"id": 30, + "arguments": [{"type": "table", "name": "table"}, + {"type": "octet", "name": "integer-op"}, + {"type": "octet", "name": "string-op"}], + "name": "table", + "synchronous" : true}, + {"id": 31, + "arguments": [{"type": "longlong", "name": "integer-result"}, + {"type": "longstr", "name": "string-result"}], + "name": "table-ok"}, + {"content": true, + "id": 40, + "arguments": [], + "name": "content", + "synchronous" : true}, + {"content": true, + "id": 41, + "arguments": [{"type": "long", "name": "content-checksum"}], + "name": "content-ok"}], + "name": "test" + } + ] +} diff --git a/rabbitmq-server-3.3.5/codegen/amqp-rabbitmq-0.9.1.json b/rabbitmq-server-3.3.5/codegen/amqp-rabbitmq-0.9.1.json new file mode 100644 index 0000000..0c3ee2a --- /dev/null +++ b/rabbitmq-server-3.3.5/codegen/amqp-rabbitmq-0.9.1.json @@ -0,0 +1,473 @@ +{ + "name": "AMQP", + "major-version": 0, + "minor-version": 9, + "revision": 1, + "port": 5672, + "copyright": [ + "Copyright (C) 2008-2013 GoPivotal, Inc.\n", + "\n", + "Permission is hereby granted, free of charge, to any person\n", + "obtaining a copy of this file (the \"Software\"), to deal in the\n", + "Software without restriction, including without limitation the \n", + "rights to use, copy, modify, merge, publish, distribute, \n", + "sublicense, and/or sell copies of the Software, and to permit \n", + "persons to whom the Software is furnished to do so, subject to \n", + "the following conditions:\n", + "\n", + "The above copyright notice and this permission notice shall be\n", + "included in all copies or substantial portions of the Software.\n", + "\n", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n", + "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n", + "OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n", + "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n", + "HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n", + "WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n", + "OTHER DEALINGS IN THE SOFTWARE.\n", + "\n", + "Class information entered from amqp_xml0-8.pdf and domain types from amqp-xml-doc0-9.pdf\n", + "Updated for 0-9-1 by Tony Garnock-Jones\n", + "\n", + "b3cb053f15e7b98808c0ccc67f23cb3e amqp_xml0-8.pdf\n", + "http://www.twiststandards.org/index.php?option=com_docman&task=cat_view&gid=28&&Itemid=90\n", + "8444db91e2949dbecfb2585e9eef6d64 amqp-xml-doc0-9.pdf\n", + "https://jira.amqp.org/confluence/download/attachments/720900/amqp-xml-doc0-9.pdf?version=1\n"], + + "domains": [ + ["bit", "bit"], + ["channel-id", "longstr"], + ["class-id", "short"], + ["consumer-tag", "shortstr"], + ["delivery-tag", "longlong"], + ["destination", "shortstr"], + ["duration", "longlong"], + ["exchange-name", "shortstr"], + ["long", "long"], + ["longlong", "longlong"], + ["longstr", "longstr"], + ["method-id", "short"], + ["no-ack", "bit"], + ["no-local", "bit"], + ["octet", "octet"], + ["offset", "longlong"], + ["path", "shortstr"], + ["peer-properties", "table"], + ["queue-name", "shortstr"], + ["redelivered", "bit"], + ["reference", "longstr"], + ["reject-code", "short"], + ["reject-text", "shortstr"], + ["reply-code", "short"], + ["reply-text", "shortstr"], + ["security-token", "longstr"], + ["short", "short"], + ["shortstr", "shortstr"], + ["table", "table"], + ["timestamp", "timestamp"] + ], + + "constants": [ + {"name": "FRAME-METHOD", "value": 1}, + {"name": "FRAME-HEADER", "value": 2}, + {"name": "FRAME-BODY", "value": 3}, + {"name": "FRAME-HEARTBEAT", "value": 8}, + {"name": "FRAME-MIN-SIZE", "value": 4096}, + {"name": "FRAME-END", "value": 206}, + {"name": "REPLY-SUCCESS", "value": 200}, + {"name": "CONTENT-TOO-LARGE", "value": 311, "class": "soft-error"}, + {"name": "NO-ROUTE", "value": 312, "class": "soft-error"}, + {"name": "NO-CONSUMERS", "value": 313, "class": "soft-error"}, + {"name": "ACCESS-REFUSED", "value": 403, "class": "soft-error"}, + {"name": "NOT-FOUND", "value": 404, "class": "soft-error"}, + {"name": "RESOURCE-LOCKED", "value": 405, "class": "soft-error"}, + {"name": "PRECONDITION-FAILED", "value": 406, "class": "soft-error"}, + {"name": "CONNECTION-FORCED", "value": 320, "class": "hard-error"}, + {"name": "INVALID-PATH", "value": 402, "class": "hard-error"}, + {"name": "FRAME-ERROR", "value": 501, "class": "hard-error"}, + {"name": "SYNTAX-ERROR", "value": 502, "class": "hard-error"}, + {"name": "COMMAND-INVALID", "value": 503, "class": "hard-error"}, + {"name": "CHANNEL-ERROR", "value": 504, "class": "hard-error"}, + {"name": "UNEXPECTED-FRAME", "value": 505, "class": "hard-error"}, + {"name": "RESOURCE-ERROR", "value": 506, "class": "hard-error"}, + {"name": "NOT-ALLOWED", "value": 530, "class": "hard-error"}, + {"name": "NOT-IMPLEMENTED", "value": 540, "class": "hard-error"}, + {"name": "INTERNAL-ERROR", "value": 541, "class": "hard-error"} + ], + + "classes": [ + { + "id": 10, + "methods": [{"id": 10, + "arguments": [{"type": "octet", "name": "version-major", "default-value": 0}, + {"type": "octet", "name": "version-minor", "default-value": 9}, + {"domain": "peer-properties", "name": "server-properties"}, + {"type": "longstr", "name": "mechanisms", "default-value": "PLAIN"}, + {"type": "longstr", "name": "locales", "default-value": "en_US"}], + "name": "start", + "synchronous" : true}, + {"id": 11, + "arguments": [{"domain": "peer-properties", "name": "client-properties"}, + {"type": "shortstr", "name": "mechanism", "default-value": "PLAIN"}, + {"type": "longstr", "name": "response"}, + {"type": "shortstr", "name": "locale", "default-value": "en_US"}], + "name": "start-ok"}, + {"id": 20, + "arguments": [{"type": "longstr", "name": "challenge"}], + "name": "secure", + "synchronous" : true}, + {"id": 21, + "arguments": [{"type": "longstr", "name": "response"}], + "name": "secure-ok"}, + {"id": 30, + "arguments": [{"type": "short", "name": "channel-max", "default-value": 0}, + {"type": "long", "name": "frame-max", "default-value": 0}, + {"type": "short", "name": "heartbeat", "default-value": 0}], + "name": "tune", + "synchronous" : true}, + {"id": 31, + "arguments": [{"type": "short", "name": "channel-max", "default-value": 0}, + {"type": "long", "name": "frame-max", "default-value": 0}, + {"type": "short", "name": "heartbeat", "default-value": 0}], + "name": "tune-ok"}, + {"id": 40, + "arguments": [{"type": "shortstr", "name": "virtual-host", "default-value": "/"}, + {"type": "shortstr", "name": "capabilities", "default-value": ""}, + {"type": "bit", "name": "insist", "default-value": false}], + "name": "open", + "synchronous" : true}, + {"id": 41, + "arguments": [{"type": "shortstr", "name": "known-hosts", "default-value": ""}], + "name": "open-ok"}, + {"id": 50, + "arguments": [{"type": "short", "name": "reply-code"}, + {"type": "shortstr", "name": "reply-text", "default-value": ""}, + {"type": "short", "name": "class-id"}, + {"type": "short", "name": "method-id"}], + "name": "close", + "synchronous" : true}, + {"id": 51, + "arguments": [], + "name": "close-ok"}, + {"id": 60, + "arguments": [{"type": "shortstr", "name": "reason", "default-value": ""}], + "name": "blocked"}, + {"id": 61, + "arguments": [], + "name": "unblocked"}], + "name": "connection", + "properties": [] + }, + { + "id": 20, + "methods": [{"id": 10, + "arguments": [{"type": "shortstr", "name": "out-of-band", "default-value": ""}], + "name": "open", + "synchronous" : true}, + {"id": 11, + "arguments": [{"type": "longstr", "name": "channel-id", "default-value": ""}], + "name": "open-ok"}, + {"id": 20, + "arguments": [{"type": "bit", "name": "active"}], + "name": "flow", + "synchronous" : true}, + {"id": 21, + "arguments": [{"type": "bit", "name": "active"}], + "name": "flow-ok"}, + {"id": 40, + "arguments": [{"type": "short", "name": "reply-code"}, + {"type": "shortstr", "name": "reply-text", "default-value": ""}, + {"type": "short", "name": "class-id"}, + {"type": "short", "name": "method-id"}], + "name": "close", + "synchronous" : true}, + {"id": 41, + "arguments": [], + "name": "close-ok"}], + "name": "channel" + }, + { + "id": 30, + "methods": [{"id": 10, + "arguments": [{"type": "shortstr", "name": "realm", "default-value": "/data"}, + {"type": "bit", "name": "exclusive", "default-value": false}, + {"type": "bit", "name": "passive", "default-value": true}, + {"type": "bit", "name": "active", "default-value": true}, + {"type": "bit", "name": "write", "default-value": true}, + {"type": "bit", "name": "read", "default-value": true}], + "name": "request", + "synchronous" : true}, + {"id": 11, + "arguments": [{"type": "short", "name": "ticket", "default-value": 1}], + "name": "request-ok"}], + "name": "access" + }, + { + "id": 40, + "methods": [{"id": 10, + "arguments": [{"type": "short", "name": "ticket", "default-value": 0}, + {"type": "shortstr", "name": "exchange"}, + {"type": "shortstr", "name": "type", "default-value": "direct"}, + {"type": "bit", "name": "passive", "default-value": false}, + {"type": "bit", "name": "durable", "default-value": false}, + {"type": "bit", "name": "auto-delete", "default-value": false}, + {"type": "bit", "name": "internal", "default-value": false}, + {"type": "bit", "name": "nowait", "default-value": false}, + {"type": "table", "name": "arguments", "default-value": {}}], + "name": "declare", + "synchronous" : true}, + {"id": 11, + "arguments": [], + "name": "declare-ok"}, + {"id": 20, + "arguments": [{"type": "short", "name": "ticket", "default-value": 0}, + {"type": "shortstr", "name": "exchange"}, + {"type": "bit", "name": "if-unused", "default-value": false}, + {"type": "bit", "name": "nowait", "default-value": false}], + "name": "delete", + "synchronous" : true}, + {"id": 21, + "arguments": [], + "name": "delete-ok"}, + {"id": 30, + "arguments": [{"type": "short", "name": "ticket", "default-value": 0}, + {"type": "shortstr", "name": "destination"}, + {"type": "shortstr", "name": "source"}, + {"type": "shortstr", "name": "routing-key", "default-value": ""}, + {"type": "bit", "name": "nowait", "default-value": false}, + {"type": "table", "name": "arguments", "default-value": {}}], + "name": "bind", + "synchronous" : true}, + {"id": 31, + "arguments": [], + "name": "bind-ok"}, + {"id": 40, + "arguments": [{"type": "short", "name": "ticket", "default-value": 0}, + {"type": "shortstr", "name": "destination"}, + {"type": "shortstr", "name": "source"}, + {"type": "shortstr", "name": "routing-key", "default-value": ""}, + {"type": "bit", "name": "nowait", "default-value": false}, + {"type": "table", "name": "arguments", "default-value": {}}], + "name": "unbind", + "synchronous" : true}, + {"id": 51, + "arguments": [], + "name": "unbind-ok"}], + "name": "exchange" + }, + { + "id": 50, + "methods": [{"id": 10, + "arguments": [{"type": "short", "name": "ticket", "default-value": 0}, + {"type": "shortstr", "name": "queue", "default-value": ""}, + {"type": "bit", "name": "passive", "default-value": false}, + {"type": "bit", "name": "durable", "default-value": false}, + {"type": "bit", "name": "exclusive", "default-value": false}, + {"type": "bit", "name": "auto-delete", "default-value": false}, + {"type": "bit", "name": "nowait", "default-value": false}, + {"type": "table", "name": "arguments", "default-value": {}}], + "name": "declare", + "synchronous" : true}, + {"id": 11, + "arguments": [{"type": "shortstr", "name": "queue"}, + {"type": "long", "name": "message-count"}, + {"type": "long", "name": "consumer-count"}], + "name": "declare-ok"}, + {"id": 20, + "arguments": [{"type": "short", "name": "ticket", "default-value": 0}, + {"type": "shortstr", "name": "queue", "default-value": ""}, + {"type": "shortstr", "name": "exchange"}, + {"type": "shortstr", "name": "routing-key", "default-value": ""}, + {"type": "bit", "name": "nowait", "default-value": false}, + {"type": "table", "name": "arguments", "default-value": {}}], + "name": "bind", + "synchronous" : true}, + {"id": 21, + "arguments": [], + "name": "bind-ok"}, + {"id": 30, + "arguments": [{"type": "short", "name": "ticket", "default-value": 0}, + {"type": "shortstr", "name": "queue", "default-value": ""}, + {"type": "bit", "name": "nowait", "default-value": false}], + "name": "purge", + "synchronous" : true}, + {"id": 31, + "arguments": [{"type": "long", "name": "message-count"}], + "name": "purge-ok"}, + {"id": 40, + "arguments": [{"type": "short", "name": "ticket", "default-value": 0}, + {"type": "shortstr", "name": "queue", "default-value": ""}, + {"type": "bit", "name": "if-unused", "default-value": false}, + {"type": "bit", "name": "if-empty", "default-value": false}, + {"type": "bit", "name": "nowait", "default-value": false}], + "name": "delete", + "synchronous" : true}, + {"id": 41, + "arguments": [{"type": "long", "name": "message-count"}], + "name": "delete-ok"}, + {"id": 50, + "arguments": [{"type": "short", "name": "ticket", "default-value": 0}, + {"type": "shortstr", "name": "queue", "default-value": ""}, + {"type": "shortstr", "name": "exchange"}, + {"type": "shortstr", "name": "routing-key", "default-value": ""}, + {"type": "table", "name": "arguments", "default-value": {}}], + "name": "unbind", + "synchronous" : true}, + {"id": 51, + "arguments": [], + "name": "unbind-ok"} + ], + "name": "queue" + }, + { + "id": 60, + "methods": [{"id": 10, + "arguments": [{"type": "long", "name": "prefetch-size", "default-value": 0}, + {"type": "short", "name": "prefetch-count", "default-value": 0}, + {"type": "bit", "name": "global", "default-value": false}], + "name": "qos", + "synchronous" : true}, + {"id": 11, + "arguments": [], + "name": "qos-ok"}, + {"id": 20, + "arguments": [{"domain": "short", "name": "ticket", "default-value": 0}, + {"type": "shortstr", "name": "queue", "default-value": ""}, + {"type": "shortstr", "name": "consumer-tag", "default-value": ""}, + {"type": "bit", "name": "no-local", "default-value": false}, + {"type": "bit", "name": "no-ack", "default-value": false}, + {"type": "bit", "name": "exclusive", "default-value": false}, + {"type": "bit", "name": "nowait", "default-value": false}, + {"type": "table", "name": "arguments", "default-value": {}}], + "name": "consume", + "synchronous" : true}, + {"id": 21, + "arguments": [{"type": "shortstr", "name": "consumer-tag"}], + "name": "consume-ok"}, + {"id": 30, + "arguments": [{"type": "shortstr", "name": "consumer-tag"}, + {"type": "bit", "name": "nowait", "default-value": false}], + "name": "cancel", + "synchronous" : true}, + {"id": 31, + "arguments": [{"type": "shortstr", "name": "consumer-tag"}], + "name": "cancel-ok"}, + {"content": true, + "id": 40, + "arguments": [{"type": "short", "name": "ticket", "default-value": 0}, + {"type": "shortstr", "name": "exchange", "default-value": ""}, + {"type": "shortstr", "name": "routing-key", "default-value": ""}, + {"type": "bit", "name": "mandatory", "default-value": false}, + {"type": "bit", "name": "immediate", "default-value": false}], + "name": "publish"}, + {"content": true, + "id": 50, + "arguments": [{"type": "short", "name": "reply-code"}, + {"type": "shortstr", "name": "reply-text", "default-value": ""}, + {"type": "shortstr", "name": "exchange"}, + {"type": "shortstr", "name": "routing-key"}], + "name": "return"}, + {"content": true, + "id": 60, + "arguments": [{"type": "shortstr", "name": "consumer-tag"}, + {"type": "longlong", "name": "delivery-tag"}, + {"type": "bit", "name": "redelivered", "default-value": false}, + {"type": "shortstr", "name": "exchange"}, + {"type": "shortstr", "name": "routing-key"}], + "name": "deliver"}, + {"id": 70, + "arguments": [{"type": "short", "name": "ticket", "default-value": 0}, + {"type": "shortstr", "name": "queue", "default-value": ""}, + {"type": "bit", "name": "no-ack", "default-value": false}], + "name": "get", + "synchronous" : true}, + {"content": true, + "id": 71, + "arguments": [{"type": "longlong", "name": "delivery-tag"}, + {"type": "bit", "name": "redelivered", "default-value": false}, + {"type": "shortstr", "name": "exchange"}, + {"type": "shortstr", "name": "routing-key"}, + {"type": "long", "name": "message-count"}], + "name": "get-ok"}, + {"id": 72, + "arguments": [{"type": "shortstr", "name": "cluster-id", "default-value": ""}], + "name": "get-empty"}, + {"id": 80, + "arguments": [{"type": "longlong", "name": "delivery-tag", "default-value": 0}, + {"type": "bit", "name": "multiple", "default-value": false}], + "name": "ack"}, + {"id": 90, + "arguments": [{"type": "longlong", "name": "delivery-tag"}, + {"type": "bit", "name": "requeue", "default-value": true}], + "name": "reject"}, + {"id": 100, + "arguments": [{"type": "bit", "name": "requeue", "default-value": false}], + "name": "recover-async"}, + {"id": 110, + "arguments": [{"type": "bit", "name": "requeue", "default-value": false}], + "name": "recover", + "synchronous" : true}, + {"id": 111, + "arguments": [], + "name": "recover-ok"}, + {"id": 120, + "arguments": [{"type": "longlong", "name": "delivery-tag", "default-value": 0}, + {"type": "bit", "name": "multiple", "default-value": false}, + {"type": "bit", "name": "requeue", "default-value": true}], + "name": "nack"}], + "name": "basic", + "properties": [{"type": "shortstr", "name": "content-type"}, + {"type": "shortstr", "name": "content-encoding"}, + {"type": "table", "name": "headers"}, + {"type": "octet", "name": "delivery-mode"}, + {"type": "octet", "name": "priority"}, + {"type": "shortstr", "name": "correlation-id"}, + {"type": "shortstr", "name": "reply-to"}, + {"type": "shortstr", "name": "expiration"}, + {"type": "shortstr", "name": "message-id"}, + {"type": "timestamp", "name": "timestamp"}, + {"type": "shortstr", "name": "type"}, + {"type": "shortstr", "name": "user-id"}, + {"type": "shortstr", "name": "app-id"}, + {"type": "shortstr", "name": "cluster-id"}] + }, + { + "id": 90, + "methods": [{"id": 10, + "arguments": [], + "name": "select", + "synchronous" : true}, + {"id": 11, + "arguments": [], + "name": "select-ok"}, + {"id": 20, + "arguments": [], + "name": "commit", + "synchronous" : true}, + {"id": 21, + "arguments": [], + "name": "commit-ok"}, + {"id": 30, + "arguments": [], + "name": "rollback", + "synchronous" : true}, + {"id": 31, + "arguments": [], + "name": "rollback-ok"}], + "name": "tx" + }, + { + "id": 85, + "methods": [{"id": 10, + "arguments": [ + {"type": "bit", "name": "nowait", "default-value": false}], + "name": "select", + "synchronous": true}, + {"id": 11, + "arguments": [], + "name": "select-ok"}], + "name": "confirm" + } + ] +} diff --git a/rabbitmq-server-3.3.5/codegen/amqp_codegen.py b/rabbitmq-server-3.3.5/codegen/amqp_codegen.py new file mode 100644 index 0000000..2623a5d --- /dev/null +++ b/rabbitmq-server-3.3.5/codegen/amqp_codegen.py @@ -0,0 +1,286 @@ +## The contents of this file are subject to the Mozilla Public License +## Version 1.1 (the "License"); you may not use this file except in +## compliance with the License. You may obtain a copy of the License +## at http://www.mozilla.org/MPL/ +## +## Software distributed under the License is distributed on an "AS IS" +## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +## the License for the specific language governing rights and +## limitations under the License. +## +## The Original Code is RabbitMQ. +## +## The Initial Developer of the Original Code is GoPivotal, Inc. +## Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +## + +from __future__ import nested_scopes +import re +import sys +import os +from optparse import OptionParser + +try: + try: + import simplejson as json + except ImportError, e: + if sys.hexversion >= 0x20600f0: + import json + else: + raise e +except ImportError: + print >> sys.stderr , " You don't appear to have simplejson.py installed" + print >> sys.stderr , " (an implementation of a JSON reader and writer in Python)." + print >> sys.stderr , " You can install it:" + print >> sys.stderr , " - by running 'apt-get install python-simplejson' on Debian-based systems," + print >> sys.stderr , " - by running 'yum install python-simplejson' on Fedora/Red Hat system," + print >> sys.stderr , " - by running 'port install py25-simplejson' on Macports on OS X" + print >> sys.stderr , " (you may need to say 'make PYTHON=python2.5', as well)," + print >> sys.stderr , " - from sources from 'http://pypi.python.org/pypi/simplejson'" + print >> sys.stderr , " - simplejson is a standard json library in the Python core since 2.6" + sys.exit(1) + +def insert_base_types(d): + for t in ['octet', 'shortstr', 'longstr', 'short', 'long', + 'longlong', 'bit', 'table', 'timestamp']: + d[t] = t + +class AmqpSpecFileMergeConflict(Exception): pass + +# If ignore_conflicts is true, then we allow acc and new to conflict, +# with whatever's already in acc winning and new being ignored. If +# ignore_conflicts is false, acc and new must not conflict. + +def default_spec_value_merger(key, acc, new, ignore_conflicts): + if acc is None or acc == new or ignore_conflicts: + return new + else: + raise AmqpSpecFileMergeConflict(key, acc, new) + +def extension_info_merger(key, acc, new, ignore_conflicts): + return acc + [new] + +def domains_merger(key, acc, new, ignore_conflicts): + merged = dict((k, v) for [k, v] in acc) + for [k, v] in new: + if merged.has_key(k): + if not ignore_conflicts: + raise AmqpSpecFileMergeConflict(key, acc, new) + else: + merged[k] = v + + return [[k, v] for (k, v) in merged.iteritems()] + +def merge_dict_lists_by(dict_key, acc, new, ignore_conflicts): + acc_index = set(v[dict_key] for v in acc) + result = list(acc) # shallow copy + for v in new: + if v[dict_key] in acc_index: + if not ignore_conflicts: + raise AmqpSpecFileMergeConflict(description, acc, new) + else: + result.append(v) + return result + +def constants_merger(key, acc, new, ignore_conflicts): + return merge_dict_lists_by("name", acc, new, ignore_conflicts) + +def methods_merger(classname, acc, new, ignore_conflicts): + return merge_dict_lists_by("name", acc, new, ignore_conflicts) + +def properties_merger(classname, acc, new, ignore_conflicts): + return merge_dict_lists_by("name", acc, new, ignore_conflicts) + +def class_merger(acc, new, ignore_conflicts): + acc["methods"] = methods_merger(acc["name"], + acc["methods"], + new["methods"], + ignore_conflicts) + acc["properties"] = properties_merger(acc["name"], + acc.get("properties", []), + new.get("properties", []), + ignore_conflicts) + +def classes_merger(key, acc, new, ignore_conflicts): + acc_dict = dict((v["name"], v) for v in acc) + result = list(acc) # shallow copy + for w in new: + if w["name"] in acc_dict: + class_merger(acc_dict[w["name"]], w, ignore_conflicts) + else: + result.append(w) + return result + +mergers = { + "extension": (extension_info_merger, []), + "domains": (domains_merger, []), + "constants": (constants_merger, []), + "classes": (classes_merger, []), +} + +def merge_load_specs(filenames, ignore_conflicts): + handles = [open(filename) for filename in filenames] + docs = [json.load(handle) for handle in handles] + spec = {} + for doc in docs: + for (key, value) in doc.iteritems(): + (merger, default_value) = mergers.get(key, (default_spec_value_merger, None)) + spec[key] = merger(key, spec.get(key, default_value), value, ignore_conflicts) + for handle in handles: handle.close() + return spec + +class AmqpSpec: + # Slight wart: use a class member rather than change the ctor signature + # to avoid breaking everyone else's code. + ignore_conflicts = False + + def __init__(self, filenames): + self.spec = merge_load_specs(filenames, AmqpSpec.ignore_conflicts) + + self.major = self.spec['major-version'] + self.minor = self.spec['minor-version'] + self.revision = self.spec.has_key('revision') and self.spec['revision'] or 0 + self.port = self.spec['port'] + + self.domains = {} + insert_base_types(self.domains) + for entry in self.spec['domains']: + self.domains[ entry[0] ] = entry[1] + + self.constants = [] + for d in self.spec['constants']: + if d.has_key('class'): + klass = d['class'] + else: + klass = '' + self.constants.append((d['name'], d['value'], klass)) + + self.classes = [] + for element in self.spec['classes']: + self.classes.append(AmqpClass(self, element)) + + def allClasses(self): + return self.classes + + def allMethods(self): + return [m for c in self.classes for m in c.allMethods()] + + def resolveDomain(self, n): + return self.domains[n] + +class AmqpEntity: + def __init__(self, element): + self.element = element + self.name = element['name'] + +class AmqpClass(AmqpEntity): + def __init__(self, spec, element): + AmqpEntity.__init__(self, element) + self.spec = spec + self.index = int(self.element['id']) + + self.methods = [] + for method_element in self.element['methods']: + self.methods.append(AmqpMethod(self, method_element)) + + self.hasContentProperties = False + for method in self.methods: + if method.hasContent: + self.hasContentProperties = True + break + + self.fields = [] + if self.element.has_key('properties'): + index = 0 + for e in self.element['properties']: + self.fields.append(AmqpField(self, e, index)) + index = index + 1 + + def allMethods(self): + return self.methods + + def __repr__(self): + return 'AmqpClass("' + self.name + '")' + +class AmqpMethod(AmqpEntity): + def __init__(self, klass, element): + AmqpEntity.__init__(self, element) + self.klass = klass + self.index = int(self.element['id']) + if self.element.has_key('synchronous'): + self.isSynchronous = self.element['synchronous'] + else: + self.isSynchronous = False + if self.element.has_key('content'): + self.hasContent = self.element['content'] + else: + self.hasContent = False + self.arguments = [] + + index = 0 + for argument in element['arguments']: + self.arguments.append(AmqpField(self, argument, index)) + index = index + 1 + + def __repr__(self): + return 'AmqpMethod("' + self.klass.name + "." + self.name + '" ' + repr(self.arguments) + ')' + +class AmqpField(AmqpEntity): + def __init__(self, method, element, index): + AmqpEntity.__init__(self, element) + self.method = method + self.index = index + + if self.element.has_key('type'): + self.domain = self.element['type'] + else: + self.domain = self.element['domain'] + + if self.element.has_key('default-value'): + self.defaultvalue = self.element['default-value'] + else: + self.defaultvalue = None + + def __repr__(self): + return 'AmqpField("' + self.name + '")' + +def do_main(header_fn, body_fn): + do_main_dict({"header": header_fn, "body": body_fn}) + +def do_main_dict(funcDict): + def usage(): + print >> sys.stderr , "Usage:" + print >> sys.stderr , " %s ... " % (sys.argv[0]) + print >> sys.stderr , " where is one of %s" % ", ".join([k for k in funcDict.keys()]) + + def execute(fn, amqp_specs, out_file): + stdout = sys.stdout + f = open(out_file, 'w') + success = False + try: + sys.stdout = f + fn(amqp_specs) + success = True + finally: + sys.stdout = stdout + f.close() + if not success: + os.remove(out_file) + + parser = OptionParser() + parser.add_option("--ignore-conflicts", action="store_true", dest="ignore_conflicts", default=False) + (options, args) = parser.parse_args() + + if len(args) < 3: + usage() + sys.exit(1) + else: + function = args[0] + sources = args[1:-1] + dest = args[-1] + AmqpSpec.ignore_conflicts = options.ignore_conflicts + if funcDict.has_key(function): + execute(funcDict[function], sources, dest) + else: + usage() + sys.exit(1) diff --git a/rabbitmq-server-3.3.5/codegen/credit_extension.json b/rabbitmq-server-3.3.5/codegen/credit_extension.json new file mode 100644 index 0000000..b74391f --- /dev/null +++ b/rabbitmq-server-3.3.5/codegen/credit_extension.json @@ -0,0 +1,54 @@ +{ + "extension": { + "name": "credit", + "version": "0.1", + "status": [ + "This extension is used internally by the broker and plugins. ", + "It is NOT intended to be used by regular clients over the ", + "network. This extension is subject to change without notice; ", + "hence you are strongly discouraged from building clients ", + "which use it."], + "copyright": [ + "Copyright (C) 2008-2013 GoPivotal, Inc.\n", + "\n", + "Permission is hereby granted, free of charge, to any person\n", + "obtaining a copy of this file (the \"Software\"), to deal in the\n", + "Software without restriction, including without limitation the \n", + "rights to use, copy, modify, merge, publish, distribute, \n", + "sublicense, and/or sell copies of the Software, and to permit \n", + "persons to whom the Software is furnished to do so, subject to \n", + "the following conditions:\n", + "\n", + "The above copyright notice and this permission notice shall be\n", + "included in all copies or substantial portions of the Software.\n", + "\n", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n", + "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n", + "OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n", + "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n", + "HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n", + "WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n", + "OTHER DEALINGS IN THE SOFTWARE.\n"] + }, + + "classes": [ + { + "id": 60, + "methods": [{"id": 200, + "arguments": [{"type": "shortstr", "name": "consumer-tag", "default-value": ""}, + {"type": "long", "name": "credit"}, + {"type": "bit", "name": "drain"}], + "name": "credit", + "synchronous" : true}, + {"id": 201, + "arguments": [{"type": "long", "name": "available"}], + "name": "credit-ok"}, + {"id": 202, + "arguments": [{"type": "shortstr", "name": "consumer-tag", "default-value": ""}, + {"type": "long", "name": "credit-drained"}], + "name": "credit-drained"}], + "name": "basic" + } + ] +} diff --git a/rabbitmq-server-3.3.5/codegen/demo_extension.json b/rabbitmq-server-3.3.5/codegen/demo_extension.json new file mode 100644 index 0000000..7a636f1 --- /dev/null +++ b/rabbitmq-server-3.3.5/codegen/demo_extension.json @@ -0,0 +1,18 @@ +{ + "extension": { + "name": "demo", + "version": "1.0", + "copyright": "Copyright (C) 2009-2013 GoPivotal, Inc." + }, + "domains": [ + ["foo-domain", "shortstr"] + ], + "constants": [ + {"name": "FOO-CONSTANT", "value": 121212} + ], + "classes": [ + {"name": "demo", + "id": 555, + "methods": [{"name": "one", "id": 1, "arguments": []}]} + ] +} diff --git a/rabbitmq-server-3.3.5/codegen/license_info b/rabbitmq-server-3.3.5/codegen/license_info new file mode 100644 index 0000000..1cebe90 --- /dev/null +++ b/rabbitmq-server-3.3.5/codegen/license_info @@ -0,0 +1,4 @@ +The files amqp-rabbitmq-0.8.json and amqp-rabbitmq-0.9.1.json are +"Copyright (C) 2008-2013 GoPivotal", Inc. and are covered by the MIT +license. + diff --git a/rabbitmq-server-3.3.5/docs/examples-to-end.xsl b/rabbitmq-server-3.3.5/docs/examples-to-end.xsl new file mode 100644 index 0000000..4db1d5c --- /dev/null +++ b/rabbitmq-server-3.3.5/docs/examples-to-end.xsl @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + Examples + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [] + + + + {} + + + + + + + + diff --git a/rabbitmq-server-3.3.5/docs/html-to-website-xml.xsl b/rabbitmq-server-3.3.5/docs/html-to-website-xml.xsl new file mode 100644 index 0000000..d83d507 --- /dev/null +++ b/rabbitmq-server-3.3.5/docs/html-to-website-xml.xsl @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + +type="text/xml" href="page.xsl" + + + <xsl:value-of select="document($original)/refentry/refnamediv/refname"/><xsl:if test="document($original)/refentry/refmeta/manvolnum">(<xsl:value-of select="document($original)/refentry/refmeta/manvolnum"/>)</xsl:if> manual page + + + + +

+ This is the manual page for + (). +

+

+ See a list of all manual pages. +

+
+ +

+ This is the documentation for + . +

+
+
+

+ For more general documentation, please see the + administrator's guide. +

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+    
+  
+
+ + +
+ +
+
+ +
+ diff --git a/rabbitmq-server-3.3.5/docs/rabbitmq-echopid.xml b/rabbitmq-server-3.3.5/docs/rabbitmq-echopid.xml new file mode 100644 index 0000000..d3dcea5 --- /dev/null +++ b/rabbitmq-server-3.3.5/docs/rabbitmq-echopid.xml @@ -0,0 +1,71 @@ + + + + + RabbitMQ Server + + The RabbitMQ Team <info@rabbitmq.com> + + + + + rabbitmq-echopid.bat + RabbitMQ Server + + + + rabbitmq-echopid.bat + return the process id of the Erlang runtime hosting RabbitMQ + + + + + rabbitmq-echopid.bat + sname + + + + + Description + + RabbitMQ is an implementation of AMQP, the emerging + standard for high performance enterprise messaging. The + RabbitMQ server is a robust and scalable implementation of + an AMQP broker. + + + Running rabbitmq-echopid will attempt to + discover and echo the process id (PID) of the Erlang runtime + process (erl.exe) that is hosting RabbitMQ. To allow erl.exe + time to start up and load RabbitMQ, the script will wait for + ten seconds before timing out if a suitable PID cannot be + found. + + + If a PID is discovered, the script will echo it to stdout + before exiting with a ERRORLEVEL of 0. If no PID is + discovered before the timeout, nothing is written to stdout + and the script exits setting ERRORLEVEL to 1. + + + Note that this script only exists on Windows due to the need + to wait for erl.exe and possibly time-out. To obtain the PID + on Unix set RABBITMQ_PID_FILE before starting + rabbitmq-server and do not use "-detached". + + + + + Options + + + sname + + +The short-name form of the RabbitMQ node name. + + + + + + diff --git a/rabbitmq-server-3.3.5/docs/rabbitmq-env.conf.5.xml b/rabbitmq-server-3.3.5/docs/rabbitmq-env.conf.5.xml new file mode 100644 index 0000000..c887596 --- /dev/null +++ b/rabbitmq-server-3.3.5/docs/rabbitmq-env.conf.5.xml @@ -0,0 +1,83 @@ + + + + + RabbitMQ Server + + The RabbitMQ Team <info@rabbitmq.com> + + + + + rabbitmq-env.conf + 5 + RabbitMQ Server + + + + rabbitmq-env.conf + default settings for RabbitMQ AMQP server + + + + Description + +/etc/rabbitmq/rabbitmq-env.conf contains variable settings that override the +defaults built in to the RabbitMQ startup scripts. + + +The file is interpreted by the system shell, and so should consist of +a sequence of shell environment variable definitions. Normal shell +syntax is permitted (since the file is sourced using the shell "." +operator), including line comments starting with "#". + + +In order of preference, the startup scripts get their values from the +environment, from /etc/rabbitmq/rabbitmq-env.conf and finally from the +built-in default values. For example, for the RABBITMQ_NODENAME +setting, + + + RABBITMQ_NODENAME + + +from the environment is checked first. If it is absent or equal to the +empty string, then + + + NODENAME + + +from /etc/rabbitmq/rabbitmq-env.conf is checked. If it is also absent +or set equal to the empty string then the default value from the +startup script is used. + + +The variable names in /etc/rabbitmq/rabbitmq-env.conf are always equal to the +environment variable names, with the RABBITMQ_ prefix removed: +RABBITMQ_NODE_PORT from the environment becomes NODE_PORT in the +/etc/rabbitmq/rabbitmq-env.conf file, etc. + + For example: + +# I am a complete /etc/rabbitmq/rabbitmq-env.conf file. +# Comment lines start with a hash character. +# This is a /bin/sh script file - use ordinary envt var syntax +NODENAME=hare + + + This is an example of a complete + /etc/rabbitmq/rabbitmq-env.conf file that overrides the default Erlang + node name from "rabbit" to "hare". + + + + + + See also + + rabbitmq-server1 + rabbitmqctl1 + + + diff --git a/rabbitmq-server-3.3.5/docs/rabbitmq-plugins.1.xml b/rabbitmq-server-3.3.5/docs/rabbitmq-plugins.1.xml new file mode 100644 index 0000000..8ecb4fc --- /dev/null +++ b/rabbitmq-server-3.3.5/docs/rabbitmq-plugins.1.xml @@ -0,0 +1,182 @@ + + + + + + + RabbitMQ Server + + The RabbitMQ Team <info@rabbitmq.com> + + + + + rabbitmq-plugins + 1 + RabbitMQ Service + + + + rabbitmq-plugins + command line tool for managing RabbitMQ broker plugins + + + + + rabbitmq-plugins + command + command options + + + + + Description + + rabbitmq-plugins is a command line tool for managing + RabbitMQ broker plugins. It allows one to enable, disable and browse + plugins. It must be run by a user with write permissions to the RabbitMQ + configuration directory. + + + Some plugins depend on others to work + correctly. rabbitmq-plugins traverses these + dependencies and enables all required plugins. Plugins listed on + the rabbitmq-plugins command line are marked as + explicitly enabled; dependent plugins are marked as implicitly + enabled. Implicitly enabled plugins are automatically disabled again + when they are no longer required. + + + + + Commands + + + + list -v -m -E -e pattern + + + + -v + Show all plugin details (verbose). + + + -m + Show only plugin names (minimal). + + + -E + Show only explicitly enabled + plugins. + + + -e + Show only explicitly or implicitly + enabled plugins. + + + pattern + Pattern to filter the plugin names by. + + + + Lists all plugins, their versions, dependencies and + descriptions. Each plugin is prefixed with a status + indicator - [ ] to indicate that the plugin is not + enabled, [E] to indicate that it is explicitly enabled, + [e] to indicate that it is implicitly enabled, and [!] to + indicate that it is enabled but missing and thus not + operational. + + + If the optional pattern is given, only plugins whose + name matches pattern are shown. + + For example: + rabbitmq-plugins list + + This command lists all plugins, on one line each. + + rabbitmq-plugins list -v + + This command lists all plugins. + + rabbitmq-plugins list -v management + + This command lists all plugins whose name contains "management". + + rabbitmq-plugins list -e rabbit + + This command lists all implicitly or explicitly enabled + RabbitMQ plugins. + + + + + + enable plugin ... + + + + plugin + One or more plugins to enable. + + + + Enables the specified plugins and all their + dependencies. + + + For example: + rabbitmq-plugins enable rabbitmq_shovel rabbitmq_management + + This command enables the shovel and + management plugins and all their + dependencies. + + + + + + disable plugin ... + + + + plugin + One or more plugins to disable. + + + + Disables the specified plugins and all plugins that + depend on them. + + + For example: + rabbitmq-plugins disable amqp_client + + This command disables amqp_client and + all plugins that depend on it. + + + + + + + + diff --git a/rabbitmq-server-3.3.5/docs/rabbitmq-server.1.xml b/rabbitmq-server-3.3.5/docs/rabbitmq-server.1.xml new file mode 100644 index 0000000..32ae842 --- /dev/null +++ b/rabbitmq-server-3.3.5/docs/rabbitmq-server.1.xml @@ -0,0 +1,132 @@ + + + + + RabbitMQ Server + + The RabbitMQ Team <info@rabbitmq.com> + + + + + rabbitmq-server + 1 + RabbitMQ Server + + + + rabbitmq-server + start RabbitMQ AMQP server + + + + + rabbitmq-server + -detached + + + + + Description + + RabbitMQ is an implementation of AMQP, the emerging standard for high +performance enterprise messaging. The RabbitMQ server is a robust and +scalable implementation of an AMQP broker. + + +Running rabbitmq-server in the foreground displays a banner message, +and reports on progress in the startup sequence, concluding with the +message "broker running", indicating that the RabbitMQ broker has been +started successfully. To shut down the server, just terminate the +process or use rabbitmqctl(1). + + + + + Environment + + + + RABBITMQ_MNESIA_BASE + + +Defaults to /var/lib/rabbitmq/mnesia. Set this to the directory where +Mnesia database files should be placed. + + + + + + RABBITMQ_LOG_BASE + + +Defaults to /var/log/rabbitmq. Log files generated by the server will +be placed in this directory. + + + + + + RABBITMQ_NODENAME + + +Defaults to rabbit. This can be useful if you want to run more than +one node per machine - RABBITMQ_NODENAME should be unique per +erlang-node-and-machine combination. See the +clustering on a single +machine guide for details. + + + + + + RABBITMQ_NODE_IP_ADDRESS + + +By default RabbitMQ will bind to all interfaces, on IPv4 and IPv6 if +available. Set this if you only want to bind to one network interface +or address family. + + + + + + RABBITMQ_NODE_PORT + + +Defaults to 5672. + + + + + + + + + Options + + + -detached + + + Start the server process in the background. Note that this will + cause the pid not to be written to the pid file. + + For example: + rabbitmq-server -detached + + Runs RabbitMQ AMQP server in the background. + + + + + + + + See also + + rabbitmq-env.conf5 + rabbitmqctl1 + + + diff --git a/rabbitmq-server-3.3.5/docs/rabbitmq-service.xml b/rabbitmq-server-3.3.5/docs/rabbitmq-service.xml new file mode 100644 index 0000000..a4bd158 --- /dev/null +++ b/rabbitmq-server-3.3.5/docs/rabbitmq-service.xml @@ -0,0 +1,218 @@ + + + + + RabbitMQ Server + + The RabbitMQ Team <info@rabbitmq.com> + + + + + rabbitmq-service.bat + RabbitMQ Server + + + + rabbitmq-service.bat + manage RabbitMQ AMQP service + + + + + rabbitmq-service.bat + command + + + + + Description + + RabbitMQ is an implementation of AMQP, the emerging standard for high +performance enterprise messaging. The RabbitMQ server is a robust and +scalable implementation of an AMQP broker. + + +Running rabbitmq-service allows the RabbitMQ broker to be run as a +service on NT/2000/2003/XP/Vista® environments. The RabbitMQ broker +service can be started and stopped using the Windows® services +applet. + + +By default the service will run in the authentication context of the +local system account. It is therefore necessary to synchronise Erlang +cookies between the local system account (typically +C:\WINDOWS\.erlang.cookie and the account that will be used to +run rabbitmqctl. + + + + + Commands + + + + help + + +Display usage information. + + + + + + install + + +Install the service. The service will not be started. +Subsequent invocations will update the service parameters if +relevant environment variables were modified or if the active +plugins were changed. + + + + + + remove + + +Remove the service. If the service is running then it will +automatically be stopped before being removed. No files will be +deleted as a consequence and rabbitmq-server will remain operable. + + + + + + start + + +Start the service. The service must have been correctly installed +beforehand. + + + + + + stop + + +Stop the service. The service must be running for this command to +have any effect. + + + + + + disable + + +Disable the service. This is the equivalent of setting the startup +type to Disabled using the service control panel. + + + + + + enable + + +Enable the service. This is the equivalent of setting the startup +type to Automatic using the service control panel. + + + + + + + + Environment + + + + RABBITMQ_SERVICENAME + + +Defaults to RabbitMQ. + + + + + + RABBITMQ_BASE + + +Defaults to the application data directory of the current user. +This is the location of log and database directories. + + + + + + + RABBITMQ_NODENAME + + +Defaults to rabbit. This can be useful if you want to run more than +one node per machine - RABBITMQ_NODENAME should be unique per +erlang-node-and-machine combination. See the +clustering on a single +machine guide for details. + + + + + + RABBITMQ_NODE_IP_ADDRESS + + +By default RabbitMQ will bind to all interfaces, on IPv4 and IPv6 if +available. Set this if you only want to bind to one network interface +or address family. + + + + + + RABBITMQ_NODE_PORT + + +Defaults to 5672. + + + + + + ERLANG_SERVICE_MANAGER_PATH + + +Defaults to C:\Program Files\erl5.5.5\erts-5.5.5\bin +(or C:\Program Files (x86)\erl5.5.5\erts-5.5.5\bin for 64-bit +environments). This is the installation location of the Erlang service +manager. + + + + + + RABBITMQ_CONSOLE_LOG + + +Set this varable to new or reuse to have the console +output from the server redirected to a file named SERVICENAME.debug +in the application data directory of the user that installed the service. +Under Vista this will be C:\Users\AppData\username\SERVICENAME. +Under previous versions of Windows this will be +C:\Documents and Settings\username\Application Data\SERVICENAME. +If RABBITMQ_CONSOLE_LOG is set to new then a new file will be +created each time the service starts. If RABBITMQ_CONSOLE_LOG is +set to reuse then the file will be overwritten each time the +service starts. The default behaviour when RABBITMQ_CONSOLE_LOG is +not set or set to a value other than new or reuse is to discard +the server output. + + + + + + diff --git a/rabbitmq-server-3.3.5/docs/rabbitmq.config.example b/rabbitmq-server-3.3.5/docs/rabbitmq.config.example new file mode 100644 index 0000000..a128bfb --- /dev/null +++ b/rabbitmq-server-3.3.5/docs/rabbitmq.config.example @@ -0,0 +1,567 @@ +%% -*- mode: erlang -*- +%% ---------------------------------------------------------------------------- +%% RabbitMQ Sample Configuration File. +%% +%% See http://www.rabbitmq.com/configure.html for details. +%% ---------------------------------------------------------------------------- +[ + {rabbit, + [%% + %% Network Connectivity + %% ==================== + %% + + %% By default, RabbitMQ will listen on all interfaces, using + %% the standard (reserved) AMQP port. + %% + %% {tcp_listeners, [5672]}, + + %% To listen on a specific interface, provide a tuple of {IpAddress, Port}. + %% For example, to listen only on localhost for both IPv4 and IPv6: + %% + %% {tcp_listeners, [{"127.0.0.1", 5672}, + %% {"::1", 5672}]}, + + %% SSL listeners are configured in the same fashion as TCP listeners, + %% including the option to control the choice of interface. + %% + %% {ssl_listeners, [5671]}, + + %% Log levels (currently just used for connection logging). + %% One of 'info', 'warning', 'error' or 'none', in decreasing order + %% of verbosity. Defaults to 'info'. + %% + %% {log_levels, [{connection, info}]}, + + %% Set to 'true' to perform reverse DNS lookups when accepting a + %% connection. Hostnames will then be shown instead of IP addresses + %% in rabbitmqctl and the management plugin. + %% + %% {reverse_dns_lookups, true}, + + %% + %% Security / AAA + %% ============== + %% + + %% The default "guest" user is only permitted to access the server + %% via a loopback interface (e.g. localhost). + %% {loopback_users, [<<"guest">>]}, + %% + %% Uncomment the following line if you want to allow access to the + %% guest user from anywhere on the network. + %% {loopback_users, []}, + + %% Configuring SSL. + %% See http://www.rabbitmq.com/ssl.html for full documentation. + %% + %% {ssl_options, [{cacertfile, "/path/to/testca/cacert.pem"}, + %% {certfile, "/path/to/server/cert.pem"}, + %% {keyfile, "/path/to/server/key.pem"}, + %% {verify, verify_peer}, + %% {fail_if_no_peer_cert, false}]}, + + %% Choose the available SASL mechanism(s) to expose. + %% The two default (built in) mechanisms are 'PLAIN' and + %% 'AMQPLAIN'. Additional mechanisms can be added via + %% plugins. + %% + %% See http://www.rabbitmq.com/authentication.html for more details. + %% + %% {auth_mechanisms, ['PLAIN', 'AMQPLAIN']}, + + %% Select an authentication database to use. RabbitMQ comes bundled + %% with a built-in auth-database, based on mnesia. + %% + %% {auth_backends, [rabbit_auth_backend_internal]}, + + %% Configurations supporting the rabbitmq_auth_mechanism_ssl and + %% rabbitmq_auth_backend_ldap plugins. + %% + %% NB: These options require that the relevant plugin is enabled. + %% See http://www.rabbitmq.com/plugins.html for further details. + + %% The RabbitMQ-auth-mechanism-ssl plugin makes it possible to + %% authenticate a user based on the client's SSL certificate. + %% + %% To use auth-mechanism-ssl, add to or replace the auth_mechanisms + %% list with the entry 'EXTERNAL'. + %% + %% {auth_mechanisms, ['EXTERNAL']}, + + %% The rabbitmq_auth_backend_ldap plugin allows the broker to + %% perform authentication and authorisation by deferring to an + %% external LDAP server. + %% + %% For more information about configuring the LDAP backend, see + %% http://www.rabbitmq.com/ldap.html. + %% + %% Enable the LDAP auth backend by adding to or replacing the + %% auth_backends entry: + %% + %% {auth_backends, [rabbit_auth_backend_ldap]}, + + %% This pertains to both the rabbitmq_auth_mechanism_ssl plugin and + %% STOMP ssl_cert_login configurations. See the rabbitmq_stomp + %% configuration section later in this fail and the README in + %% https://github.com/rabbitmq/rabbitmq-auth-mechanism-ssl for further + %% details. + %% + %% To use the SSL cert's CN instead of its DN as the username + %% + %% {ssl_cert_login_from, common_name}, + + %% + %% Default User / VHost + %% ==================== + %% + + %% On first start RabbitMQ will create a vhost and a user. These + %% config items control what gets created. See + %% http://www.rabbitmq.com/access-control.html for further + %% information about vhosts and access control. + %% + %% {default_vhost, <<"/">>}, + %% {default_user, <<"guest">>}, + %% {default_pass, <<"guest">>}, + %% {default_permissions, [<<".*">>, <<".*">>, <<".*">>]}, + + %% Tags for default user + %% + %% For more details about tags, see the documentation for the + %% Management Plugin at http://www.rabbitmq.com/management.html. + %% + %% {default_user_tags, [administrator]}, + + %% + %% Additional network and protocol related configuration + %% ===================================================== + %% + + %% Set the default AMQP heartbeat delay (in seconds). + %% + %% {heartbeat, 600}, + + %% Set the max permissible size of an AMQP frame (in bytes). + %% + %% {frame_max, 131072}, + + %% Set the max permissible number of channels per connection. + %% 0 means "no limit". + %% + %% {channel_max, 128}, + + %% Customising Socket Options. + %% + %% See (http://www.erlang.org/doc/man/inet.html#setopts-2) for + %% further documentation. + %% + %% {tcp_listen_options, [binary, + %% {packet, raw}, + %% {reuseaddr, true}, + %% {backlog, 128}, + %% {nodelay, true}, + %% {exit_on_close, false}]}, + + %% + %% Resource Limits & Flow Control + %% ============================== + %% + %% See http://www.rabbitmq.com/memory.html for full details. + + %% Memory-based Flow Control threshold. + %% + %% {vm_memory_high_watermark, 0.4}, + + %% Fraction of the high watermark limit at which queues start to + %% page message out to disc in order to free up memory. + %% + %% {vm_memory_high_watermark_paging_ratio, 0.5}, + + %% Set disk free limit (in bytes). Once free disk space reaches this + %% lower bound, a disk alarm will be set - see the documentation + %% listed above for more details. + %% + %% {disk_free_limit, 50000000}, + + %% Alternatively, we can set a limit relative to total available RAM. + %% + %% {disk_free_limit, {mem_relative, 1.0}}, + + %% + %% Misc/Advanced Options + %% ===================== + %% + %% NB: Change these only if you understand what you are doing! + %% + + %% To announce custom properties to clients on connection: + %% + %% {server_properties, []}, + + %% How to respond to cluster partitions. + %% See http://www.rabbitmq.com/partitions.html for further details. + %% + %% {cluster_partition_handling, ignore}, + + %% Make clustering happen *automatically* at startup - only applied + %% to nodes that have just been reset or started for the first time. + %% See http://www.rabbitmq.com/clustering.html#auto-config for + %% further details. + %% + %% {cluster_nodes, {['rabbit@my.host.com'], disc}}, + + %% Set (internal) statistics collection granularity. + %% + %% {collect_statistics, none}, + + %% Statistics collection interval (in milliseconds). + %% + %% {collect_statistics_interval, 5000}, + + %% Explicitly enable/disable hipe compilation. + %% + %% {hipe_compile, true} + + ]}, + + %% ---------------------------------------------------------------------------- + %% Advanced Erlang Networking/Clustering Options. + %% + %% See http://www.rabbitmq.com/clustering.html for details + %% ---------------------------------------------------------------------------- + {kernel, + [%% Sets the net_kernel tick time. + %% Please see http://erlang.org/doc/man/kernel_app.html and + %% http://www.rabbitmq.com/nettick.html for further details. + %% + %% {net_ticktime, 60} + ]}, + + %% ---------------------------------------------------------------------------- + %% RabbitMQ Management Plugin + %% + %% See http://www.rabbitmq.com/management.html for details + %% ---------------------------------------------------------------------------- + + {rabbitmq_management, + [%% Pre-Load schema definitions from the following JSON file. See + %% http://www.rabbitmq.com/management.html#load-definitions + %% + %% {load_definitions, "/path/to/schema.json"}, + + %% Log all requests to the management HTTP API to a file. + %% + %% {http_log_dir, "/path/to/access.log"}, + + %% Change the port on which the HTTP listener listens, + %% specifying an interface for the web server to bind to. + %% Also set the listener to use SSL and provide SSL options. + %% + %% {listener, [{port, 12345}, + %% {ip, "127.0.0.1"}, + %% {ssl, true}, + %% {ssl_opts, [{cacertfile, "/path/to/cacert.pem"}, + %% {certfile, "/path/to/cert.pem"}, + %% {keyfile, "/path/to/key.pem"}]}]}, + + %% Configure how long aggregated data (such as message rates and queue + %% lengths) is retained. Please read the plugin's documentation in + %% https://www.rabbitmq.com/management.html#configuration for more + %% details. + %% + %% {sample_retention_policies, + %% [{global, [{60, 5}, {3600, 60}, {86400, 1200}]}, + %% {basic, [{60, 5}, {3600, 60}]}, + %% {detailed, [{10, 5}]}]} + ]}, + + {rabbitmq_management_agent, + [%% Misc/Advanced Options + %% + %% NB: Change these only if you understand what you are doing! + %% + %% {force_fine_statistics, true} + ]}, + + %% ---------------------------------------------------------------------------- + %% RabbitMQ Shovel Plugin + %% + %% See http://www.rabbitmq.com/shovel.html for details + %% ---------------------------------------------------------------------------- + + {rabbitmq_shovel, + [{shovels, + [%% A named shovel worker. + %% {my_first_shovel, + %% [ + + %% List the source broker(s) from which to consume. + %% + %% {sources, + %% [%% URI(s) and pre-declarations for all source broker(s). + %% {brokers, ["amqp://user:password@host.domain/my_vhost"]}, + %% {declarations, []} + %% ]}, + + %% List the destination broker(s) to publish to. + %% {destinations, + %% [%% A singular version of the 'brokers' element. + %% {broker, "amqp://"}, + %% {declarations, []} + %% ]}, + + %% Name of the queue to shovel messages from. + %% + %% {queue, <<"your-queue-name-goes-here">>}, + + %% Optional prefetch count. + %% + %% {prefetch_count, 10}, + + %% when to acknowledge messages: + %% - no_ack: never (auto) + %% - on_publish: after each message is republished + %% - on_confirm: when the destination broker confirms receipt + %% + %% {ack_mode, on_confirm}, + + %% Overwrite fields of the outbound basic.publish. + %% + %% {publish_fields, [{exchange, <<"my_exchange">>}, + %% {routing_key, <<"from_shovel">>}]}, + + %% Static list of basic.properties to set on re-publication. + %% + %% {publish_properties, [{delivery_mode, 2}]}, + + %% The number of seconds to wait before attempting to + %% reconnect in the event of a connection failure. + %% + %% {reconnect_delay, 2.5} + + %% ]} %% End of my_first_shovel + ]} + %% Rather than specifying some values per-shovel, you can specify + %% them for all shovels here. + %% + %% {defaults, [{prefetch_count, 0}, + %% {ack_mode, on_confirm}, + %% {publish_fields, []}, + %% {publish_properties, [{delivery_mode, 2}]}, + %% {reconnect_delay, 2.5}]} + ]}, + + %% ---------------------------------------------------------------------------- + %% RabbitMQ Stomp Adapter + %% + %% See http://www.rabbitmq.com/stomp.html for details + %% ---------------------------------------------------------------------------- + + {rabbitmq_stomp, + [%% Network Configuration - the format is generally the same as for the broker + + %% Listen only on localhost (ipv4 & ipv6) on a specific port. + %% {tcp_listeners, [{"127.0.0.1", 61613}, + %% {"::1", 61613}]}, + + %% Listen for SSL connections on a specific port. + %% {ssl_listeners, [61614]}, + + %% Additional SSL options + + %% Extract a name from the client's certificate when using SSL. + %% + %% {ssl_cert_login, true}, + + %% Set a default user name and password. This is used as the default login + %% whenever a CONNECT frame omits the login and passcode headers. + %% + %% Please note that setting this will allow clients to connect without + %% authenticating! + %% + %% {default_user, [{login, "guest"}, + %% {passcode, "guest"}]}, + + %% If a default user is configured, or you have configured use SSL client + %% certificate based authentication, you can choose to allow clients to + %% omit the CONNECT frame entirely. If set to true, the client is + %% automatically connected as the default user or user supplied in the + %% SSL certificate whenever the first frame sent on a session is not a + %% CONNECT frame. + %% + %% {implicit_connect, true} + ]}, + + %% ---------------------------------------------------------------------------- + %% RabbitMQ MQTT Adapter + %% + %% See http://hg.rabbitmq.com/rabbitmq-mqtt/file/stable/README.md for details + %% ---------------------------------------------------------------------------- + + {rabbitmq_mqtt, + [%% Set the default user name and password. Will be used as the default login + %% if a connecting client provides no other login details. + %% + %% Please note that setting this will allow clients to connect without + %% authenticating! + %% + %% {default_user, <<"guest">>}, + %% {default_pass, <<"guest">>}, + + %% Enable anonymous access. If this is set to false, clients MUST provide + %% login information in order to connect. See the default_user/default_pass + %% configuration elements for managing logins without authentication. + %% + %% {allow_anonymous, true}, + + %% If you have multiple chosts, specify the one to which the + %% adapter connects. + %% + %% {vhost, <<"/">>}, + + %% Specify the exchange to which messages from MQTT clients are published. + %% + %% {exchange, <<"amq.topic">>}, + + %% Specify TTL (time to live) to control the lifetime of non-clean sessions. + %% + %% {subscription_ttl, 1800000}, + + %% Set the prefetch count (governing the maximum number of unacknowledged + %% messages that will be delivered). + %% + %% {prefetch, 10}, + + %% TCP/SSL Configuration (as per the broker configuration). + %% + %% {tcp_listeners, [1883]}, + %% {ssl_listeners, []}, + + %% TCP/Socket options (as per the broker configuration). + %% + %% {tcp_listen_options, [binary, + %% {packet, raw}, + %% {reuseaddr, true}, + %% {backlog, 128}, + %% {nodelay, true}]} + ]}, + + %% ---------------------------------------------------------------------------- + %% RabbitMQ AMQP 1.0 Support + %% + %% See http://hg.rabbitmq.com/rabbitmq-amqp1.0/file/default/README.md + %% for details + %% ---------------------------------------------------------------------------- + + {rabbitmq_amqp1_0, + [%% Connections that are not authenticated with SASL will connect as this + %% account. See the README for more information. + %% + %% Please note that setting this will allow clients to connect without + %% authenticating! + %% + %% {default_user, "guest"}, + + %% Enable protocol strict mode. See the README for more information. + %% + %% {protocol_strict_mode, false} + ]}, + + %% ---------------------------------------------------------------------------- + %% RabbitMQ LDAP Plugin + %% + %% See http://www.rabbitmq.com/ldap.html for details. + %% + %% ---------------------------------------------------------------------------- + + {rabbitmq_auth_backend_ldap, + [%% + %% Connecting to the LDAP server(s) + %% ================================ + %% + + %% Specify servers to bind to. You *must* set this in order for the plugin + %% to work properly. + %% + %% {servers, ["your-server-name-goes-here"]}, + + %% Connect to the LDAP server using SSL + %% + %% {use_ssl, false}, + + %% Specify the LDAP port to connect to + %% + %% {port, 389}, + + %% LDAP connection timeout, in milliseconds or 'infinity' + %% + %% {timeout, infinity}, + + %% Enable logging of LDAP queries. + %% One of + %% - false (no logging is performed) + %% - true (verbose logging of the logic used by the plugin) + %% - network (as true, but additionally logs LDAP network traffic) + %% + %% Defaults to false. + %% + %% {log, false}, + + %% + %% Authentication + %% ============== + %% + + %% Pattern to convert the username given through AMQP to a DN before + %% binding + %% + %% {user_dn_pattern, "cn=${username},ou=People,dc=example,dc=com"}, + + %% Alternatively, you can convert a username to a Distinguished + %% Name via an LDAP lookup after binding. See the documentation for + %% full details. + + %% When converting a username to a dn via a lookup, set these to + %% the name of the attribute that represents the user name, and the + %% base DN for the lookup query. + %% + %% {dn_lookup_attribute, "userPrincipalName"}, + %% {dn_lookup_base, "DC=gopivotal,DC=com"}, + + %% Controls how to bind for authorisation queries and also to + %% retrieve the details of users logging in without presenting a + %% password (e.g., SASL EXTERNAL). + %% One of + %% - as_user (to bind as the authenticated user - requires a password) + %% - anon (to bind anonymously) + %% - {UserDN, Password} (to bind with a specified user name and password) + %% + %% Defaults to 'as_user'. + %% + %% {other_bind, as_user}, + + %% + %% Authorisation + %% ============= + %% + + %% The LDAP plugin can perform a variety of queries against your + %% LDAP server to determine questions of authorisation. See + %% http://www.rabbitmq.com/ldap.html#authorisation for more + %% information. + + %% Set the query to use when determining vhost access + %% + %% {vhost_access_query, {in_group, + %% "ou=${vhost}-users,ou=vhosts,dc=example,dc=com"}}, + + %% Set the query to use when determining resource (e.g., queue) access + %% + %% {resource_access_query, {constant, true}}, + + %% Set queries to determine which tags a user has + %% + %% {tag_queries, []} + ]} +]. diff --git a/rabbitmq-server-3.3.5/docs/rabbitmqctl.1.xml b/rabbitmq-server-3.3.5/docs/rabbitmqctl.1.xml new file mode 100644 index 0000000..01b024a --- /dev/null +++ b/rabbitmq-server-3.3.5/docs/rabbitmqctl.1.xml @@ -0,0 +1,1768 @@ + + + + + + + RabbitMQ Server + + The RabbitMQ Team <info@rabbitmq.com> + + + + + rabbitmqctl + 1 + RabbitMQ Service + + + + rabbitmqctl + command line tool for managing a RabbitMQ broker + + + + + rabbitmqctl + -n node + -q + command + command options + + + + + Description + + RabbitMQ is an implementation of AMQP, the emerging standard for high + performance enterprise messaging. The RabbitMQ server is a robust and + scalable implementation of an AMQP broker. + + + rabbitmqctl is a command line tool for managing a + RabbitMQ broker. It performs all actions by connecting to one of the + broker's nodes. + + + Diagnostic information is displayed if the broker was not + running, could not be reached, or rejected the connection due to + mismatching Erlang cookies. + + + + + Options + + + -n node + + + Default node is "rabbit@server", where server is the local host. On + a host named "server.example.com", the node name of the RabbitMQ + Erlang node will usually be rabbit@server (unless RABBITMQ_NODENAME + has been set to some non-default value at broker startup time). The + output of hostname -s is usually the correct suffix to use after the + "@" sign. See rabbitmq-server(1) for details of configuring the + RabbitMQ broker. + + + + + -q + + + Quiet output mode is selected with the "-q" flag. Informational + messages are suppressed when quiet mode is in effect. + + + + + + + + Commands + + + Application and Cluster Management + + + + stop pid_file + + + Stops the Erlang node on which RabbitMQ is running. To + restart the node follow the instructions for Running + the Server in the installation + guide. + + + If a is specified, also waits + for the process specified there to terminate. See the + description of the command below + for details on this file. + + For example: + rabbitmqctl stop + + This command instructs the RabbitMQ node to terminate. + + + + + + stop_app + + + Stops the RabbitMQ application, leaving the Erlang node + running. + + + This command is typically run prior to performing other + management actions that require the RabbitMQ application + to be stopped, e.g. reset. + + For example: + rabbitmqctl stop_app + + This command instructs the RabbitMQ node to stop the + RabbitMQ application. + + + + + + start_app + + + Starts the RabbitMQ application. + + + This command is typically run after performing other + management actions that required the RabbitMQ application + to be stopped, e.g. reset. + + For example: + rabbitmqctl start_app + + This command instructs the RabbitMQ node to start the + RabbitMQ application. + + + + + + wait pid_file + + + Wait for the RabbitMQ application to start. + + + This command will wait for the RabbitMQ application to + start at the node. It will wait for the pid file to + be created, then for a process with a pid specified in the + pid file to start, and then for the RabbitMQ application + to start in that process. It will fail if the process + terminates without starting the RabbitMQ application. + + + A suitable pid file is created by + the rabbitmq-server script. By + default this is located in the Mnesia directory. Modify + the RABBITMQ_PID_FILE environment + variable to change the location. + + For example: + rabbitmqctl wait /var/run/rabbitmq/pid + + This command will return when the RabbitMQ node has + started up. + + + + + + reset + + + Return a RabbitMQ node to its virgin state. + + + Removes the node from any cluster it belongs to, removes + all data from the management database, such as configured + users and vhosts, and deletes all persistent + messages. + + + For reset and force_reset to + succeed the RabbitMQ application must have been stopped, + e.g. with stop_app. + + For example: + rabbitmqctl reset + + This command resets the RabbitMQ node. + + + + + + force_reset + + + Forcefully return a RabbitMQ node to its virgin state. + + + The force_reset command differs from + reset in that it resets the node + unconditionally, regardless of the current management + database state and cluster configuration. It should only + be used as a last resort if the database or cluster + configuration has been corrupted. + + + For reset and force_reset to + succeed the RabbitMQ application must have been stopped, + e.g. with stop_app. + + For example: + rabbitmqctl force_reset + + This command resets the RabbitMQ node. + + + + + + rotate_logs suffix + + + Instruct the RabbitMQ node to rotate the log files. + + + The RabbitMQ broker appends the contents of its log + files to files with names composed of the original name + and the suffix, and then resumes logging to freshly + created files at the original location. I.e. effectively + the current log contents are moved to the end of the + suffixed files. + + + When the target files do not exist they are created. + When no is specified, the empty + log files are simply created at the original location; + no rotation takes place. + + For example: + rabbitmqctl rotate_logs .1 + + This command instructs the RabbitMQ node to append the contents + of the log files to files with names consisting of the original logs' + names and ".1" suffix, e.g. rabbit@mymachine.log.1 and + rabbit@mymachine-sasl.log.1. Finally, logging resumes to + fresh files at the old locations. + + + + + + + + Cluster management + + + + join_cluster clusternode --ram + + + + clusternode + Node to cluster with. + + + --ram + + + If provided, the node will join the cluster as a RAM node. + + + + + + Instruct the node to become a member of the cluster that the + specified node is in. Before clustering, the node is reset, so be + careful when using this command. For this command to succeed the + RabbitMQ application must have been stopped, e.g. with stop_app. + + + Cluster nodes can be of two types: disc or RAM. Disc nodes + replicate data in RAM and on disc, thus providing redundancy in + the event of node failure and recovery from global events such + as power failure across all nodes. RAM nodes replicate data in + RAM only (with the exception of queue contents, which can reside + on disc if the queue is persistent or too big to fit in memory) + and are mainly used for scalability. RAM nodes are more + performant only when managing resources (e.g. adding/removing + queues, exchanges, or bindings). A cluster must always have at + least one disc node, and usually should have more than one. + + + The node will be a disc node by default. If you wish to + create a RAM node, provide the --ram flag. + + + After executing the cluster command, whenever + the RabbitMQ application is started on the current node it will + attempt to connect to the nodes that were in the cluster when the + node went down. + + + To leave a cluster, reset the node. You can + also remove nodes remotely with the + forget_cluster_node command. + + + For more details see the clustering + guide. + + For example: + rabbitmqctl join_cluster hare@elena --ram + + This command instructs the RabbitMQ node to join the cluster that + hare@elena is part of, as a ram node. + + + + + cluster_status + + + Displays all the nodes in the cluster grouped by node type, + together with the currently running nodes. + + For example: + rabbitmqctl cluster_status + + This command displays the nodes in the cluster. + + + + + change_cluster_node_type disc | ram + + + + Changes the type of the cluster node. The node must be stopped for + this operation to succeed, and when turning a node into a RAM node + the node must not be the only disc node in the cluster. + + For example: + rabbitmqctl change_cluster_node_type disc + + This command will turn a RAM node into a disc node. + + + + + forget_cluster_node --offline + + + + --offline + + + Enables node removal from an offline node. This is only + useful in the situation where all the nodes are offline and + the last node to go down cannot be brought online, thus + preventing the whole cluster from starting. It should not be + used in any other circumstances since it can lead to + inconsistencies. + + + + + + Removes a cluster node remotely. The node that is being removed + must be offline, while the node we are removing from must be + online, except when using the --offline flag. + + + When using the --offline flag the node you + connect to will become the canonical source for cluster metadata + (e.g. which queues exist), even if it was not before. Therefore + you should use this command on the latest node to shut down if + at all possible. + + For example: + rabbitmqctl -n hare@mcnulty forget_cluster_node rabbit@stringer + + This command will remove the node + rabbit@stringer from the node + hare@mcnulty. + + + + + update_cluster_nodes clusternode + + + + + clusternode + + + The node to consult for up to date information. + + + + + + Instructs an already clustered node to contact + clusternode to cluster when waking up. This is + different from join_cluster since it does not + join any cluster - it checks that the node is already in a cluster + with clusternode. + + + The need for this command is motivated by the fact that clusters + can change while a node is offline. Consider the situation in + which node A and B are clustered. A goes down, C clusters with B, + and then B leaves the cluster. When A wakes up, it'll try to + contact B, but this will fail since B is not in the cluster + anymore. update_cluster_nodes -n A C will solve + this situation. + + + + + sync_queue queue + + + + + queue + + + The name of the queue to synchronise. + + + + + + Instructs a mirrored queue with unsynchronised slaves to + synchronise itself. The queue will block while + synchronisation takes place (all publishers to and + consumers from the queue will block). The queue must be + mirrored for this command to succeed. + + + Note that unsynchronised queues from which messages are + being drained will become synchronised eventually. This + command is primarily useful for queues which are not + being drained. + + + + + cancel_sync_queue queue + + + + + queue + + + The name of the queue to cancel synchronisation for. + + + + + + Instructs a synchronising mirrored queue to stop + synchronising itself. + + + + + set_cluster_name name + + + Sets the cluster name. The cluster name is announced to + clients on connection, and used by the federation and + shovel plugins to record where a message has been. The + cluster name is by default derived from the hostname of + the first node in the cluster, but can be changed. + + For example: + rabbitmqctl set_cluster_name london + + This sets the cluster name to "london". + + + + + + + + User management + + Note that rabbitmqctl manages the RabbitMQ + internal user database. Users from any alternative + authentication backend will not be visible + to rabbitmqctl. + + + + add_user username password + + + + username + The name of the user to create. + + + password + The password the created user will use to log in to the broker. + + + For example: + rabbitmqctl add_user tonyg changeit + + This command instructs the RabbitMQ broker to create a + (non-administrative) user named tonyg with + (initial) password + changeit. + + + + + + delete_user username + + + + username + The name of the user to delete. + + + For example: + rabbitmqctl delete_user tonyg + + This command instructs the RabbitMQ broker to delete the + user named tonyg. + + + + + + change_password username newpassword + + + + username + The name of the user whose password is to be changed. + + + newpassword + The new password for the user. + + + For example: + rabbitmqctl change_password tonyg newpass + + This command instructs the RabbitMQ broker to change the + password for the user named tonyg to + newpass. + + + + + + clear_password username + + + + username + The name of the user whose password is to be cleared. + + + For example: + rabbitmqctl clear_password tonyg + + This command instructs the RabbitMQ broker to clear the + password for the user named + tonyg. This user now cannot log in with a password (but may be able to through e.g. SASL EXTERNAL if configured). + + + + + + set_user_tags username tag ... + + + + username + The name of the user whose tags are to + be set. + + + tag + Zero, one or more tags to set. Any + existing tags will be removed. + + + For example: + rabbitmqctl set_user_tags tonyg administrator + + This command instructs the RabbitMQ broker to ensure the user + named tonyg is an administrator. This has no + effect when the user logs in via AMQP, but can be used to permit + the user to manage users, virtual hosts and permissions when the + user logs in via some other means (for example with the + management plugin). + + rabbitmqctl set_user_tags tonyg + + This command instructs the RabbitMQ broker to remove any + tags from the user named tonyg. + + + + + + list_users + + + Lists users. Each result row will contain the user name + followed by a list of the tags set for that user. + + For example: + rabbitmqctl list_users + + This command instructs the RabbitMQ broker to list all + users. + + + + + + + + Access control + + Note that rabbitmqctl manages the RabbitMQ + internal user database. Permissions for users from any + alternative authorisation backend will not be visible + to rabbitmqctl. + + + + add_vhost vhostpath + + + + vhostpath + The name of the virtual host entry to create. + + + + Creates a virtual host. + + For example: + rabbitmqctl add_vhost test + + This command instructs the RabbitMQ broker to create a new + virtual host called test. + + + + + + delete_vhost vhostpath + + + + vhostpath + The name of the virtual host entry to delete. + + + + Deletes a virtual host. + + + Deleting a virtual host deletes all its exchanges, + queues, bindings, user permissions, parameters and policies. + + For example: + rabbitmqctl delete_vhost test + + This command instructs the RabbitMQ broker to delete the + virtual host called test. + + + + + + list_vhosts vhostinfoitem ... + + + Lists virtual hosts. + + + The vhostinfoitem parameter is used to indicate which + virtual host information items to include in the results. The column order in the + results will match the order of the parameters. + vhostinfoitem can take any value from + the list that follows: + + + + name + The name of the virtual host with non-ASCII characters escaped as in C. + + + tracing + Whether tracing is enabled for this virtual host. + + + + If no vhostinfoitems are specified + then the vhost name is displayed. + + For example: + rabbitmqctl list_vhosts name tracing + + This command instructs the RabbitMQ broker to list all + virtual hosts. + + + + + + set_permissions -p vhostpath user conf write read + + + + vhostpath + The name of the virtual host to which to grant the user access, defaulting to /. + + + user + The name of the user to grant access to the specified virtual host. + + + conf + A regular expression matching resource names for which the user is granted configure permissions. + + + write + A regular expression matching resource names for which the user is granted write permissions. + + + read + A regular expression matching resource names for which the user is granted read permissions. + + + + Sets user permissions. + + For example: + rabbitmqctl set_permissions -p /myvhost tonyg "^tonyg-.*" ".*" ".*" + + This command instructs the RabbitMQ broker to grant the + user named tonyg access to the virtual host + called /myvhost, with configure permissions + on all resources whose names starts with "tonyg-", and + write and read permissions on all resources. + + + + + + clear_permissions -p vhostpath username + + + + vhostpath + The name of the virtual host to which to deny the user access, defaulting to /. + + + username + The name of the user to deny access to the specified virtual host. + + + + Sets user permissions. + + For example: + rabbitmqctl clear_permissions -p /myvhost tonyg + + This command instructs the RabbitMQ broker to deny the + user named tonyg access to the virtual host + called /myvhost. + + + + + + list_permissions -p vhostpath + + + + vhostpath + The name of the virtual host for which to list the users that have been granted access to it, and their permissions. Defaults to /. + + + + Lists permissions in a virtual host. + + For example: + rabbitmqctl list_permissions -p /myvhost + + This command instructs the RabbitMQ broker to list all + the users which have been granted access to the virtual + host called /myvhost, and the + permissions they have for operations on resources in + that virtual host. Note that an empty string means no + permissions granted. + + + + + + list_user_permissions username + + + + username + The name of the user for which to list the permissions. + + + + Lists user permissions. + + For example: + rabbitmqctl list_user_permissions tonyg + + This command instructs the RabbitMQ broker to list all the + virtual hosts to which the user named tonyg + has been granted access, and the permissions the user has + for operations on resources in these virtual hosts. + + + + + + + + Parameter Management + + Certain features of RabbitMQ (such as the federation plugin) + are controlled by dynamic, + cluster-wide parameters. Each parameter + consists of a component name, a name and a value, and is + associated with a virtual host. The component name and name are + strings, and the value is an Erlang term. Parameters can be + set, cleared and listed. In general you should refer to the + documentation for the feature in question to see how to set + parameters. + + + + set_parameter -p vhostpath component_name name value + + + Sets a parameter. + + + + component_name + + The name of the component for which the + parameter is being set. + + + + name + + The name of the parameter being set. + + + + value + + The value for the parameter, as a + JSON term. In most shells you are very likely to + need to quote this. + + + + For example: + rabbitmqctl set_parameter federation local_username '"guest"' + + This command sets the parameter local_username for the federation component in the default virtual host to the JSON term "guest". + + + + + clear_parameter -p vhostpath component_name key + + + Clears a parameter. + + + + component_name + + The name of the component for which the + parameter is being cleared. + + + + name + + The name of the parameter being cleared. + + + + For example: + rabbitmqctl clear_parameter federation local_username + + This command clears the parameter local_username for the federation component in the default virtual host. + + + + + list_parameters -p vhostpath + + + Lists all parameters for a virtual host. + + For example: + rabbitmqctl list_parameters + + This command lists all parameters in the default virtual host. + + + + + + + + Policy Management + + Policies are used to control and modify the behaviour of queues + and exchanges on a cluster-wide basis. Policies apply within a + given vhost, and consist of a name, pattern, definition and an + optional priority. Policies can be set, cleared and listed. + + + + set_policy -p vhostpath --priority priority --apply-to apply-to name pattern definition + + + Sets a policy. + + + + name + + The name of the policy. + + + + pattern + + The regular expression, which when matches on a given resources causes the policy to apply. + + + + definition + + The definition of the policy, as a + JSON term. In most shells you are very likely to + need to quote this. + + + + priority + + The priority of the policy as an integer. Higher numbers indicate greater precedence. The default is 0. + + + + apply-to + + Which types of object this policy should apply to - "queues", "exchanges" or "all". The default is "all". + + + + For example: + rabbitmqctl set_policy federate-me "^amq." '{"federation-upstream-set":"all"}' + + This command sets the policy federate-me in the default virtual host so that built-in exchanges are federated. + + + + + clear_policy -p vhostpath name + + + Clears a policy. + + + + name + + The name of the policy being cleared. + + + + For example: + rabbitmqctl clear_policy federate-me + + This command clears the federate-me policy in the default virtual host. + + + + + list_policies -p vhostpath + + + Lists all policies for a virtual host. + + For example: + rabbitmqctl list_policies + + This command lists all policies in the default virtual host. + + + + + + + + Server Status + + The server status queries interrogate the server and return a list of + results with tab-delimited columns. Some queries (list_queues, + list_exchanges, list_bindings, and + list_consumers) accept an + optional vhost parameter. This parameter, if present, must be + specified immediately after the query. + + + The list_queues, list_exchanges and list_bindings commands accept an + optional virtual host parameter for which to display results. The + default value is "/". + + + + + list_queues -p vhostpath queueinfoitem ... + + + Returns queue details. Queue details of the / virtual host + are returned if the "-p" flag is absent. The "-p" flag can be used to + override this default. + + + The queueinfoitem parameter is used to indicate which queue + information items to include in the results. The column order in the + results will match the order of the parameters. + queueinfoitem can take any value from the list + that follows: + + + + name + The name of the queue with non-ASCII characters escaped as in C. + + + durable + Whether or not the queue survives server restarts. + + + auto_delete + Whether the queue will be deleted automatically when no longer used. + + + arguments + Queue arguments. + + + policy + Policy name applying to the queue. + + + pid + Id of the Erlang process associated with the queue. + + + owner_pid + Id of the Erlang process representing the connection + which is the exclusive owner of the queue. Empty if the + queue is non-exclusive. + + + exclusive_consumer_pid + Id of the Erlang process representing the channel of the + exclusive consumer subscribed to this queue. Empty if + there is no exclusive consumer. + + + exclusive_consumer_tag + Consumer tag of the exclusive consumer subscribed to + this queue. Empty if there is no exclusive consumer. + + + messages_ready + Number of messages ready to be delivered to clients. + + + messages_unacknowledged + Number of messages delivered to clients but not yet acknowledged. + + + messages + Sum of ready and unacknowledged messages + (queue depth). + + + consumers + Number of consumers. + + + consumer_utilisation + Fraction of the time (between 0.0 and 1.0) + that the queue is able to immediately deliver messages to + consumers. This can be less than 1.0 if consumers are limited + by network congestion or prefetch count. + + + memory + Bytes of memory consumed by the Erlang process associated with the + queue, including stack, heap and internal structures. + + + slave_pids + If the queue is mirrored, this gives the IDs of the current slaves. + + + synchronised_slave_pids + If the queue is mirrored, this gives the IDs of + the current slaves which are synchronised with the master - + i.e. those which could take over from the master without + message loss. + + + status + The status of the queue. Normally + 'running', but may be "{syncing, MsgCount}" if the queue is + synchronising. + + + + If no queueinfoitems are specified then queue name and depth are + displayed. + + + For example: + + rabbitmqctl list_queues -p /myvhost messages consumers + + This command displays the depth and number of consumers for each + queue of the virtual host named /myvhost. + + + + + + list_exchanges -p vhostpath exchangeinfoitem ... + + + Returns exchange details. Exchange details of the / virtual host + are returned if the "-p" flag is absent. The "-p" flag can be used to + override this default. + + + The exchangeinfoitem parameter is used to indicate which + exchange information items to include in the results. The column order in the + results will match the order of the parameters. + exchangeinfoitem can take any value from the list + that follows: + + + + name + The name of the exchange with non-ASCII characters escaped as in C. + + + type + The exchange type (such as + [direct, + topic, headers, + fanout]). + + + durable + Whether or not the exchange survives server restarts. + + + auto_delete + Whether the exchange will be deleted automatically when no longer used. + + + internal + Whether the exchange is internal, i.e. cannot be directly published to by a client. + + + arguments + Exchange arguments. + + + policy + Policy name for applying to the exchange. + + + + If no exchangeinfoitems are specified then + exchange name and type are displayed. + + + For example: + + rabbitmqctl list_exchanges -p /myvhost name type + + This command displays the name and type for each + exchange of the virtual host named /myvhost. + + + + + + list_bindings -p vhostpath bindinginfoitem ... + + + Returns binding details. By default the bindings for + the / virtual host are returned. The + "-p" flag can be used to override this default. + + + The bindinginfoitem parameter is used + to indicate which binding information items to include + in the results. The column order in the results will + match the order of the parameters. + bindinginfoitem can take any value + from the list that follows: + + + + source_name + The name of the source of messages to + which the binding is attached. With non-ASCII + characters escaped as in C. + + + source_kind + The kind of the source of messages to + which the binding is attached. Currently always + exchange. With non-ASCII characters escaped as in + C. + + + destination_name + The name of the destination of + messages to which the binding is attached. With + non-ASCII characters escaped as in + C. + + + destination_kind + The kind of the destination of + messages to which the binding is attached. With + non-ASCII characters escaped as in + C. + + + routing_key + The binding's routing key, with + non-ASCII characters escaped as in C. + + + arguments + The binding's arguments. + + + + If no bindinginfoitems are specified then + all above items are displayed. + + + For example: + + rabbitmqctl list_bindings -p /myvhost exchange_name queue_name + + This command displays the exchange name and queue name + of the bindings in the virtual host + named /myvhost. + + + + + + list_connections connectioninfoitem ... + + + Returns TCP/IP connection statistics. + + + The connectioninfoitem parameter is used to indicate + which connection information items to include in the results. The + column order in the results will match the order of the parameters. + connectioninfoitem can take any value from the list + that follows: + + + + + pid + Id of the Erlang process associated with the connection. + + + name + Readable name for the connection. + + + port + Server port. + + + host + Server hostname obtained via reverse + DNS, or its IP address if reverse DNS failed or was + not enabled. + + + peer_port + Peer port. + + + peer_host + Peer hostname obtained via reverse + DNS, or its IP address if reverse DNS failed or was + not enabled. + + + ssl + Boolean indicating whether the + connection is secured with SSL. + + + ssl_protocol + SSL protocol + (e.g. tlsv1) + + + ssl_key_exchange + SSL key exchange algorithm + (e.g. rsa) + + + ssl_cipher + SSL cipher algorithm + (e.g. aes_256_cbc) + + + ssl_hash + SSL hash function + (e.g. sha) + + + peer_cert_subject + The subject of the peer's SSL + certificate, in RFC4514 form. + + + peer_cert_issuer + The issuer of the peer's SSL + certificate, in RFC4514 form. + + + peer_cert_validity + The period for which the peer's SSL + certificate is valid. + + + + state + Connection state (one of [starting, tuning, + opening, running, flow, blocking, blocked, closing, closed]). + + + channels + Number of channels using the connection. + + + protocol + Version of the AMQP protocol in use (currently one of {0,9,1} or {0,8,0}). Note that if a client requests an AMQP 0-9 connection, we treat it as AMQP 0-9-1. + + + auth_mechanism + SASL authentication mechanism used, such as PLAIN. + + + user + Username associated with the connection. + + + vhost + Virtual host name with non-ASCII characters escaped as in C. + + + timeout + Connection timeout / negotiated heartbeat interval, in seconds. + + + frame_max + Maximum frame size (bytes). + + + channel_max + Maximum number of channels on this connection. + + + client_properties + Informational properties transmitted by the client + during connection establishment. + + + recv_oct + Octets received. + + + recv_cnt + Packets received. + + + send_oct + Octets send. + + + send_cnt + Packets sent. + + + send_pend + Send queue size. + + + + If no connectioninfoitems are + specified then user, peer host, peer port, time since + flow control and memory block state are displayed. + + + + For example: + + rabbitmqctl list_connections send_pend port + + This command displays the send queue size and server port for each + connection. + + + + + + list_channels channelinfoitem ... + + + Returns information on all current channels, the logical + containers executing most AMQP commands. This includes + channels that are part of ordinary AMQP connections, and + channels created by various plug-ins and other extensions. + + + The channelinfoitem parameter is used to + indicate which channel information items to include in the + results. The column order in the results will match the + order of the parameters. + channelinfoitem can take any value from the list + that follows: + + + + + pid + Id of the Erlang process associated with the connection. + + + connection + Id of the Erlang process associated with the connection + to which the channel belongs. + + + name + Readable name for the channel. + + + number + The number of the channel, which uniquely identifies it within + a connection. + + + user + Username associated with the channel. + + + vhost + Virtual host in which the channel operates. + + + transactional + True if the channel is in transactional mode, false otherwise. + + + confirm + True if the channel is in confirm mode, false otherwise. + + + consumer_count + Number of logical AMQP consumers retrieving messages via + the channel. + + + messages_unacknowledged + Number of messages delivered via this channel but not + yet acknowledged. + + + messages_uncommitted + Number of messages received in an as yet + uncommitted transaction. + + + acks_uncommitted + Number of acknowledgements received in an as yet + uncommitted transaction. + + + messages_unconfirmed + Number of published messages not yet + confirmed. On channels not in confirm mode, this + remains 0. + + + prefetch_count + QoS prefetch limit for new consumers, 0 if unlimited. + + + global_prefetch_count + QoS prefetch limit for the entire channel, 0 if unlimited. + + + + If no channelinfoitems are specified then pid, + user, consumer_count, and messages_unacknowledged are assumed. + + + + For example: + + rabbitmqctl list_channels connection messages_unacknowledged + + This command displays the connection process and count + of unacknowledged messages for each channel. + + + + + + list_consumers -p vhostpath + + + List consumers, i.e. subscriptions to a queue's message + stream. Each line printed shows, separated by tab + characters, the name of the queue subscribed to, the id of + the channel process via which the subscription was created + and is managed, the consumer tag which uniquely identifies + the subscription within a channel, a boolean + indicating whether acknowledgements are expected for + messages delivered to this consumer, an integer indicating + the prefetch limit (with 0 meaning 'none'), and any arguments + for this consumer. + + + + + + status + + + Displays broker status information such as the running + applications on the current Erlang node, RabbitMQ and + Erlang versions, OS name, memory and file descriptor + statistics. (See the cluster_status + command to find out which nodes are clustered and + running.) + + For example: + rabbitmqctl status + + This command displays information about the RabbitMQ + broker. + + + + + + environment + + + Display the name and value of each variable in the + application environment. + + + + + + report + + + Generate a server status report containing a + concatenation of all server status information for + support purposes. The output should be redirected to a + file when accompanying a support request. + + + For example: + + rabbitmqctl report > server_report.txt + + This command creates a server report which may be + attached to a support request email. + + + + + + eval expr + + + Evaluate an arbitrary Erlang expression. + + + For example: + + rabbitmqctl eval 'node().' + + This command returns the name of the node to which rabbitmqctl has connected. + + + + + + + + Miscellaneous + + + close_connection connectionpid explanation + + + + connectionpid + Id of the Erlang process associated with the connection to close. + + + explanation + Explanation string. + + + + Instruct the broker to close the connection associated + with the Erlang process id (see also the + list_connections + command), passing the string to the + connected client as part of the AMQP connection shutdown + protocol. + + For example: + rabbitmqctl close_connection "<rabbit@tanto.4262.0>" "go away" + + This command instructs the RabbitMQ broker to close the + connection associated with the Erlang process + id <rabbit@tanto.4262.0>, passing the + explanation go away to the connected client. + + + + + trace_on -p vhost + + + + vhost + The name of the virtual host for which to start tracing. + + + + Starts tracing. + + + + + + trace_off -p vhost + + + + vhost + The name of the virtual host for which to stop tracing. + + + + Stops tracing. + + + + + set_vm_memory_high_watermark fraction + + + + fraction + + The new memory threshold fraction at which flow + control is triggered, as a floating point number + greater than or equal to 0. + + + + + + + + + + diff --git a/rabbitmq-server-3.3.5/docs/remove-namespaces.xsl b/rabbitmq-server-3.3.5/docs/remove-namespaces.xsl new file mode 100644 index 0000000..7f7f3c1 --- /dev/null +++ b/rabbitmq-server-3.3.5/docs/remove-namespaces.xsl @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/rabbitmq-server-3.3.5/docs/usage.xsl b/rabbitmq-server-3.3.5/docs/usage.xsl new file mode 100644 index 0000000..586f830 --- /dev/null +++ b/rabbitmq-server-3.3.5/docs/usage.xsl @@ -0,0 +1,74 @@ + + + + + + + + + + +%% Generated, do not edit! +-module(). +-export([usage/0]). +usage() -> %QUOTE%Usage: + + + + + + + + + + + + Options: + + + + + , + + + + + + + + + + + + + Commands: + + + + + + + + + +%QUOTE%. + + + +<> must be a member of the list [, ]. + + + + + + + + + +[] +<> + + diff --git a/rabbitmq-server-3.3.5/ebin/rabbit_app.in b/rabbitmq-server-3.3.5/ebin/rabbit_app.in new file mode 100644 index 0000000..aff1472 --- /dev/null +++ b/rabbitmq-server-3.3.5/ebin/rabbit_app.in @@ -0,0 +1,77 @@ +{application, rabbit, %% -*- erlang -*- + [{description, "RabbitMQ"}, + {id, "RabbitMQ"}, + {vsn, "3.3.5"}, + {modules, []}, + {registered, [rabbit_amqqueue_sup, + rabbit_log, + rabbit_node_monitor, + rabbit_router, + rabbit_sup, + rabbit_tcp_client_sup, + rabbit_direct_client_sup]}, + {applications, [kernel, stdlib, sasl, mnesia, os_mon, xmerl]}, +%% we also depend on crypto, public_key and ssl but they shouldn't be +%% in here as we don't actually want to start it + {mod, {rabbit, []}}, + {env, [{tcp_listeners, [5672]}, + {ssl_listeners, []}, + {ssl_options, []}, + {vm_memory_high_watermark, 0.4}, + {vm_memory_high_watermark_paging_ratio, 0.5}, + {disk_free_limit, 50000000}, %% 50MB + {msg_store_index_module, rabbit_msg_store_ets_index}, + {backing_queue_module, rabbit_variable_queue}, + %% 0 ("no limit") would make a better default, but that + %% breaks the QPid Java client + {frame_max, 131072}, + {channel_max, 0}, + {heartbeat, 580}, + {msg_store_file_size_limit, 16777216}, + {queue_index_max_journal_entries, 65536}, + {default_user, <<"guest">>}, + {default_pass, <<"guest">>}, + {default_user_tags, [administrator]}, + {default_vhost, <<"/">>}, + {default_permissions, [<<".*">>, <<".*">>, <<".*">>]}, + {loopback_users, [<<"guest">>]}, + {cluster_nodes, {[], disc}}, + {server_properties, []}, + {collect_statistics, none}, + {collect_statistics_interval, 5000}, + {auth_mechanisms, ['PLAIN', 'AMQPLAIN']}, + {auth_backends, [rabbit_auth_backend_internal]}, + {delegate_count, 16}, + {trace_vhosts, []}, + {log_levels, [{connection, info}]}, + {ssl_cert_login_from, distinguished_name}, + {reverse_dns_lookups, false}, + {cluster_partition_handling, ignore}, + {tcp_listen_options, [binary, + {packet, raw}, + {reuseaddr, true}, + {backlog, 128}, + {nodelay, true}, + {linger, {true, 0}}, + {exit_on_close, false}]}, + {halt_on_upgrade_failure, true}, + {hipe_compile, false}, + %% see bug 24513 for how this list was created + {hipe_modules, + [rabbit_reader, rabbit_channel, gen_server2, rabbit_exchange, + rabbit_command_assembler, rabbit_framing_amqp_0_9_1, rabbit_basic, + rabbit_event, lists, queue, priority_queue, rabbit_router, + rabbit_trace, rabbit_misc, rabbit_binary_parser, + rabbit_exchange_type_direct, rabbit_guid, rabbit_net, + rabbit_amqqueue_process, rabbit_variable_queue, + rabbit_binary_generator, rabbit_writer, delegate, gb_sets, lqueue, + sets, orddict, rabbit_amqqueue, rabbit_limiter, gb_trees, + rabbit_queue_index, rabbit_exchange_decorator, gen, dict, ordsets, + file_handle_cache, rabbit_msg_store, array, + rabbit_msg_store_ets_index, rabbit_msg_file, + rabbit_exchange_type_fanout, rabbit_exchange_type_topic, mnesia, + mnesia_lib, rpc, mnesia_tm, qlc, sofs, proplists, credit_flow, + pmon, ssl_connection, tls_connection, ssl_record, tls_record, + gen_fsm, ssl]}, + {ssl_apps, [asn1, crypto, public_key, ssl]} + ]}]}. diff --git a/rabbitmq-server-3.3.5/generate_app b/rabbitmq-server-3.3.5/generate_app new file mode 100644 index 0000000..fb0eb1e --- /dev/null +++ b/rabbitmq-server-3.3.5/generate_app @@ -0,0 +1,16 @@ +#!/usr/bin/env escript +%% -*- erlang -*- + +main([InFile, OutFile | SrcDirs]) -> + Modules = [list_to_atom(filename:basename(F, ".erl")) || + SrcDir <- SrcDirs, + F <- filelib:wildcard("*.erl", SrcDir)], + {ok, [{application, Application, Properties}]} = file:consult(InFile), + NewProperties = + case proplists:get_value(modules, Properties) of + [] -> lists:keyreplace(modules, 1, Properties, {modules, Modules}); + _ -> Properties + end, + file:write_file( + OutFile, + io_lib:format("~p.~n", [{application, Application, NewProperties}])). diff --git a/rabbitmq-server-3.3.5/generate_deps b/rabbitmq-server-3.3.5/generate_deps new file mode 100644 index 0000000..ddfca81 --- /dev/null +++ b/rabbitmq-server-3.3.5/generate_deps @@ -0,0 +1,57 @@ +#!/usr/bin/env escript +%% -*- erlang -*- +-mode(compile). + +%% We expect the list of Erlang source and header files to arrive on +%% stdin, with the entries colon-separated. +main([TargetFile, EbinDir]) -> + ErlsAndHrls = [ string:strip(S,left) || + S <- string:tokens(io:get_line(""), ":\n")], + ErlFiles = [F || F <- ErlsAndHrls, lists:suffix(".erl", F)], + Modules = sets:from_list( + [list_to_atom(filename:basename(FileName, ".erl")) || + FileName <- ErlFiles]), + HrlFiles = [F || F <- ErlsAndHrls, lists:suffix(".hrl", F)], + IncludeDirs = lists:usort([filename:dirname(Path) || Path <- HrlFiles]), + Headers = sets:from_list(HrlFiles), + Deps = lists:foldl( + fun (Path, Deps1) -> + dict:store(Path, detect_deps(IncludeDirs, EbinDir, + Modules, Headers, Path), + Deps1) + end, dict:new(), ErlFiles), + {ok, Hdl} = file:open(TargetFile, [write, delayed_write]), + dict:fold( + fun (_Path, [], ok) -> + ok; + (Path, Dep, ok) -> + Module = filename:basename(Path, ".erl"), + ok = file:write(Hdl, [EbinDir, "/", Module, ".beam: ", + Path]), + ok = sets:fold(fun (E, ok) -> file:write(Hdl, [" ", E]) end, + ok, Dep), + file:write(Hdl, ["\n"]) + end, ok, Deps), + ok = file:write(Hdl, [TargetFile, ": ", escript:script_name(), "\n"]), + ok = file:sync(Hdl), + ok = file:close(Hdl). + +detect_deps(IncludeDirs, EbinDir, Modules, Headers, Path) -> + {ok, Forms} = epp:parse_file(Path, IncludeDirs, [{use_specs, true}]), + lists:foldl( + fun ({attribute, _LineNumber, Attribute, Behaviour}, Deps) + when Attribute =:= behaviour orelse Attribute =:= behavior -> + case sets:is_element(Behaviour, Modules) of + true -> sets:add_element( + [EbinDir, "/", atom_to_list(Behaviour), ".beam"], + Deps); + false -> Deps + end; + ({attribute, _LineNumber, file, {FileName, _LineNumber1}}, Deps) -> + case sets:is_element(FileName, Headers) of + true -> sets:add_element(FileName, Deps); + false -> Deps + end; + (_Form, Deps) -> + Deps + end, sets:new(), Forms). diff --git a/rabbitmq-server-3.3.5/include/gm_specs.hrl b/rabbitmq-server-3.3.5/include/gm_specs.hrl new file mode 100644 index 0000000..245c23b --- /dev/null +++ b/rabbitmq-server-3.3.5/include/gm_specs.hrl @@ -0,0 +1,28 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-ifdef(use_specs). + +-type(callback_result() :: 'ok' | {'stop', any()} | {'become', atom(), args()}). +-type(args() :: any()). +-type(members() :: [pid()]). + +-spec(joined/2 :: (args(), members()) -> callback_result()). +-spec(members_changed/3 :: (args(), members(), members()) -> callback_result()). +-spec(handle_msg/3 :: (args(), pid(), any()) -> callback_result()). +-spec(terminate/2 :: (args(), term()) -> any()). + +-endif. diff --git a/rabbitmq-server-3.3.5/include/rabbit.hrl b/rabbitmq-server-3.3.5/include/rabbit.hrl new file mode 100644 index 0000000..5ac3197 --- /dev/null +++ b/rabbitmq-server-3.3.5/include/rabbit.hrl @@ -0,0 +1,133 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-record(user, {username, + tags, + auth_backend, %% Module this user came from + impl %% Scratch space for that module + }). + +-record(internal_user, {username, password_hash, tags}). +-record(permission, {configure, write, read}). +-record(user_vhost, {username, virtual_host}). +-record(user_permission, {user_vhost, permission}). + +-record(vhost, {virtual_host, dummy}). + +-record(content, + {class_id, + properties, %% either 'none', or a decoded record/tuple + properties_bin, %% either 'none', or an encoded properties binary + %% Note: at most one of properties and properties_bin can be + %% 'none' at once. + protocol, %% The protocol under which properties_bin was encoded + payload_fragments_rev %% list of binaries, in reverse order (!) + }). + +-record(resource, {virtual_host, kind, name}). + +-record(exchange, {name, type, durable, auto_delete, internal, arguments, + scratches, policy, decorators}). +-record(exchange_serial, {name, next}). + +-record(amqqueue, {name, durable, auto_delete, exclusive_owner = none, + arguments, pid, slave_pids, sync_slave_pids, policy, + gm_pids, decorators}). + +%% mnesia doesn't like unary records, so we add a dummy 'value' field +-record(route, {binding, value = const}). +-record(reverse_route, {reverse_binding, value = const}). + +-record(binding, {source, key, destination, args = []}). +-record(reverse_binding, {destination, key, source, args = []}). + +-record(topic_trie_node, {trie_node, edge_count, binding_count}). +-record(topic_trie_edge, {trie_edge, node_id}). +-record(topic_trie_binding, {trie_binding, value = const}). + +-record(trie_node, {exchange_name, node_id}). +-record(trie_edge, {exchange_name, node_id, word}). +-record(trie_binding, {exchange_name, node_id, destination, arguments}). + +-record(listener, {node, protocol, host, ip_address, port}). + +-record(runtime_parameters, {key, value}). + +-record(basic_message, {exchange_name, routing_keys = [], content, id, + is_persistent}). + +-record(ssl_socket, {tcp, ssl}). +-record(delivery, {mandatory, confirm, sender, message, msg_seq_no}). +-record(amqp_error, {name, explanation = "", method = none}). + +-record(event, {type, props, reference = undefined, timestamp}). + +-record(message_properties, {expiry, needs_confirming = false}). + +-record(plugin, {name, %% atom() + version, %% string() + description, %% string() + type, %% 'ez' or 'dir' + dependencies, %% [{atom(), string()}] + location}). %% string() + +%%---------------------------------------------------------------------------- + +-define(COPYRIGHT_MESSAGE, "Copyright (C) 2007-2014 GoPivotal, Inc."). +-define(INFORMATION_MESSAGE, "Licensed under the MPL. See http://www.rabbitmq.com/"). +-define(ERTS_MINIMUM, "5.6.3"). + +%% EMPTY_FRAME_SIZE, 8 = 1 + 2 + 4 + 1 +%% - 1 byte of frame type +%% - 2 bytes of channel number +%% - 4 bytes of frame payload length +%% - 1 byte of payload trailer FRAME_END byte +%% See rabbit_binary_generator:check_empty_frame_size/0, an assertion +%% called at startup. +-define(EMPTY_FRAME_SIZE, 8). + +-define(MAX_WAIT, 16#ffffffff). + +-define(HIBERNATE_AFTER_MIN, 1000). +-define(DESIRED_HIBERNATE, 10000). +-define(CREDIT_DISC_BOUND, {2000, 500}). + +%% This is dictated by `erlang:send_after' on which we depend to implement TTL. +-define(MAX_EXPIRY_TIMER, 4294967295). + +-define(INVALID_HEADERS_KEY, <<"x-invalid-headers">>). +-define(ROUTING_HEADERS, [<<"CC">>, <<"BCC">>]). +-define(DELETED_HEADER, <<"BCC">>). + +%% Trying to send a term across a cluster larger than 2^31 bytes will +%% cause the VM to exit with "Absurdly large distribution output data +%% buffer". So we limit the max message size to 2^31 - 10^6 bytes (1MB +%% to allow plenty of leeway for the #basic_message{} and #content{} +%% wrapping the message body). +-define(MAX_MSG_SIZE, 2147383648). + +%% First number is maximum size in bytes before we start to +%% truncate. The following 4-tuple is: +%% +%% 1) Maximum size of printable lists and binaries. +%% 2) Maximum size of any structural term. +%% 3) Amount to decrease 1) every time we descend while truncating. +%% 4) Amount to decrease 2) every time we descend while truncating. +%% +%% Whole thing feeds into truncate:log_event/2. +-define(LOG_TRUNC, {100000, {2000, 100, 50, 5}}). + +-define(store_proc_name(N), rabbit_misc:store_proc_name(?MODULE, N)). diff --git a/rabbitmq-server-3.3.5/include/rabbit_msg_store.hrl b/rabbitmq-server-3.3.5/include/rabbit_msg_store.hrl new file mode 100644 index 0000000..4e726b0 --- /dev/null +++ b/rabbitmq-server-3.3.5/include/rabbit_msg_store.hrl @@ -0,0 +1,25 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-include("rabbit.hrl"). + +-ifdef(use_specs). + +-type(msg() :: any()). + +-endif. + +-record(msg_location, {msg_id, ref_count, file, offset, total_size}). diff --git a/rabbitmq-server-3.3.5/plugins-src/Makefile b/rabbitmq-server-3.3.5/plugins-src/Makefile new file mode 100644 index 0000000..9b9f5e8 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/Makefile @@ -0,0 +1,174 @@ +.PHONY: default +default: + @echo No default target && false + +REPOS:= \ + rabbitmq-server \ + rabbitmq-codegen \ + rabbitmq-java-client \ + rabbitmq-dotnet-client \ + rabbitmq-test \ + cowboy-wrapper \ + eldap-wrapper \ + mochiweb-wrapper \ + rabbitmq-amqp1.0 \ + rabbitmq-auth-backend-ldap \ + rabbitmq-auth-mechanism-ssl \ + rabbitmq-consistent-hash-exchange \ + rabbitmq-erlang-client \ + rabbitmq-federation \ + rabbitmq-federation-management \ + rabbitmq-management \ + rabbitmq-management-agent \ + rabbitmq-management-visualiser \ + rabbitmq-metronome \ + rabbitmq-web-dispatch \ + rabbitmq-mqtt \ + rabbitmq-shovel \ + rabbitmq-shovel-management \ + rabbitmq-stomp \ + rabbitmq-toke \ + rabbitmq-tracing \ + rabbitmq-web-stomp \ + rabbitmq-web-stomp-examples \ + sockjs-erlang-wrapper \ + toke \ + webmachine-wrapper + +BRANCH:=default + +HG_CORE_REPOBASE:=$(shell dirname `hg paths default 2>/dev/null` 2>/dev/null) +ifndef HG_CORE_REPOBASE +HG_CORE_REPOBASE:=http://hg.rabbitmq.com/ +endif + +VERSION:=0.0.0 + +#---------------------------------- + +all: + $(MAKE) -f all-packages.mk all-packages VERSION=$(VERSION) + +test: + $(MAKE) -f all-packages.mk test-all-packages VERSION=$(VERSION) + +release: + $(MAKE) -f all-packages.mk all-releasable VERSION=$(VERSION) + +clean: + $(MAKE) -f all-packages.mk clean-all-packages + +check-xref: + $(MAKE) -f all-packages.mk check-xref-packages + +plugins-dist: release + rm -rf $(PLUGINS_DIST_DIR) + mkdir -p $(PLUGINS_DIST_DIR) + $(MAKE) -f all-packages.mk copy-releasable VERSION=$(VERSION) PLUGINS_DIST_DIR=$(PLUGINS_DIST_DIR) + +plugins-srcdist: + rm -rf $(PLUGINS_SRC_DIST_DIR) + mkdir -p $(PLUGINS_SRC_DIST_DIR)/licensing + + rsync -a --exclude '.hg*' rabbitmq-erlang-client $(PLUGINS_SRC_DIST_DIR)/ + touch $(PLUGINS_SRC_DIST_DIR)/rabbitmq-erlang-client/.srcdist_done + + rsync -a --exclude '.hg*' rabbitmq-server $(PLUGINS_SRC_DIST_DIR)/ + touch $(PLUGINS_SRC_DIST_DIR)/rabbitmq-server/.srcdist_done + + $(MAKE) -f all-packages.mk copy-srcdist VERSION=$(VERSION) PLUGINS_SRC_DIST_DIR=$(PLUGINS_SRC_DIST_DIR) + cp Makefile *.mk generate* $(PLUGINS_SRC_DIST_DIR)/ + echo "This is the released version of rabbitmq-public-umbrella. \ +You can clone the full version with: hg clone http://hg.rabbitmq.com/rabbitmq-public-umbrella" > $(PLUGINS_SRC_DIST_DIR)/README + + PRESERVE_CLONE_DIR=1 make -C $(PLUGINS_SRC_DIST_DIR) clean + rm -rf $(PLUGINS_SRC_DIST_DIR)/rabbitmq-server + +#---------------------------------- +# Convenience aliases + +.PHONY: co +co: checkout + +.PHONY: ci +ci: checkin + +.PHONY: up +up: update + +.PHONY: st +st: status + +.PHONY: up_c +up_c: named_update + +#---------------------------------- + +$(REPOS): + hg clone $(HG_CORE_REPOBASE)/$@ + +.PHONY: checkout +checkout: $(REPOS) + +#---------------------------------- +# Subrepository management + + +# $(1) is the target +# $(2) is the target dependency. Can use % to get current REPO +# $(3) is the target body. Can use % to get current REPO +define repo_target + +.PHONY: $(1) +$(1): $(2) + $(3) + +endef + +# $(1) is the list of repos +# $(2) is the suffix +# $(3) is the target dependency. Can use % to get current REPO +# $(4) is the target body. Can use % to get current REPO +define repo_targets +$(foreach REPO,$(1),$(call repo_target,$(REPO)+$(2),\ + $(patsubst %,$(3),$(REPO)),$(patsubst %,$(4),$(REPO)))) +endef + +# Do not allow status to fork with -j otherwise output will be garbled +.PHONY: status +status: checkout + $(foreach DIR,. $(REPOS), \ + (cd $(DIR); OUT=$$(hg st -mad); \ + if \[ ! -z "$$OUT" \]; then echo "\n$(DIR):\n$$OUT"; fi) &&) true + +.PHONY: pull +pull: $(foreach DIR,. $(REPOS),$(DIR)+pull) + +$(eval $(call repo_targets,. $(REPOS),pull,| %,(cd % && hg pull))) + +.PHONY: update +update: $(foreach DIR,. $(REPOS),$(DIR)+update) + +$(eval $(call repo_targets,. $(REPOS),update,%+pull,(cd % && hg up))) + +.PHONY: named_update +named_update: $(foreach DIR,. $(REPOS),$(DIR)+named_update) + +$(eval $(call repo_targets,. $(REPOS),named_update,%+pull,\ + (cd % && hg up -C $(BRANCH)))) + +.PHONY: tag +tag: $(foreach DIR,. $(REPOS),$(DIR)+tag) + +$(eval $(call repo_targets,. $(REPOS),tag,| %,(cd % && hg tag $(TAG)))) + +.PHONY: push +push: $(foreach DIR,. $(REPOS),$(DIR)+push) + +# "|| true" sicne hg push fails if there are no changes +$(eval $(call repo_targets,. $(REPOS),push,| %,(cd % && hg push -f || true))) + +.PHONY: checkin +checkin: $(foreach DIR,. $(REPOS),$(DIR)+checkin) + +$(eval $(call repo_targets,. $(REPOS),checkin,| %,(cd % && hg ci))) diff --git a/rabbitmq-server-3.3.5/plugins-src/README b/rabbitmq-server-3.3.5/plugins-src/README new file mode 100644 index 0000000..ae655c6 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/README @@ -0,0 +1 @@ +This is the released version of rabbitmq-public-umbrella. You can clone the full version with: hg clone http://hg.rabbitmq.com/rabbitmq-public-umbrella diff --git a/rabbitmq-server-3.3.5/plugins-src/all-packages.mk b/rabbitmq-server-3.3.5/plugins-src/all-packages.mk new file mode 100644 index 0000000..1d02a3d --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/all-packages.mk @@ -0,0 +1,13 @@ +UMBRELLA_BASE_DIR:=. + +include common.mk + +CHAIN_TESTS:=true + +# Pull in all the packages +$(foreach PACKAGE_MK,$(wildcard */package.mk),$(eval $(call do_package,$(call canonical_path,$(patsubst %/,%,$(dir $(PACKAGE_MK))))))) + +# ...and the non-integrated ones +$(foreach V,$(.VARIABLES),$(if $(filter NON_INTEGRATED_%,$(filter-out NON_INTEGRATED_DEPS_%,$V)),$(eval $(call do_package,$(subst NON_INTEGRATED_,,$V))))) + +test-all-packages: $(CHAINED_TESTS) diff --git a/rabbitmq-server-3.3.5/plugins-src/common.mk b/rabbitmq-server-3.3.5/plugins-src/common.mk new file mode 100644 index 0000000..d8ed4f8 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/common.mk @@ -0,0 +1,143 @@ +# Various global definitions + +# UMBRELLA_BASE_DIR should be set to the path of the +# rabbitmq-public-umbrella directory before this file is included. + +# Make version check +REQUIRED_MAKE_VERSION:=3.81 +ifneq ($(shell ( echo "$(MAKE_VERSION)" ; echo "$(REQUIRED_MAKE_VERSION)" ) | sort -t. -n | head -1),$(REQUIRED_MAKE_VERSION)) +$(error GNU make version $(REQUIRED_MAKE_VERSION) required) +endif + +# This is the standard trick for making pattern substitution work +# (amongst others) when the replacement needs to include a comma. +COMMA:=, + +# Global settings that can be overridden on the command line + +# These ones are expected to be passed down to the sub-makes invoked +# for non-integrated packages +VERSION ?= 0.0.0 +ERL ?= erl +ERL_OPTS ?= +ERLC ?= erlc +ERLC_OPTS ?= -Wall +debug_info +TMPDIR ?= /tmp + +NODENAME ?= rabbit-test +ERL_CALL ?= erl_call +ERL_CALL_OPTS ?= -sname $(NODENAME) -e + +# Where we put all the files produced when running tests. +TEST_TMPDIR=$(TMPDIR)/rabbitmq-test + +# Callable functions + +# Convert a package name to the corresponding erlang app name +define package_to_app_name +$(subst -,_,$(1)) +endef + +# If the variable named $(1) holds a non-empty value, return it. +# Otherwise, set the variable to $(2) and return that value. +define memoize +$(if $($(1)),$($(1)),$(eval $(1):=$(2))$(2)) +endef + +# Return a canonical form for the path in $(1) +# +# Absolute path names can be a bit verbose. This provides a way to +# canonicalize path names with more concise results. +define canonical_path +$(call memoize,SHORT_$(realpath $(1)),$(1)) +endef + +# Convert a package name to a path name +define package_to_path +$(call canonical_path,$(UMBRELLA_BASE_DIR)/$(1)) +endef + +# Produce a cp command to copy from $(1) to $(2), unless $(1) is +# empty, in which case do nothing. +# +# The optional $(3) gives a suffix to append to the command, if a +# command is produced. +define copy +$(if $(1),cp -r $(1) $(2)$(if $(3), $(3))) +endef + +# Produce the makefile fragment for the package with path in $(1), if +# it hasn't already been visited. The path should have been +# canonicalized via canonical_path. +define do_package +# Have we already visited this package? If so, skip it +ifndef DONE_$(1) +PACKAGE_DIR:=$(1) +include $(UMBRELLA_BASE_DIR)/do-package.mk +endif +endef + +# This is used to chain test rules, so that test-all-packages works in +# the presence of 'make -j' +define chain_test +$(if $(CHAIN_TESTS),$(CHAINED_TESTS)$(eval CHAINED_TESTS+=$(1))) +endef + +# Mark the non-integrated repos +NON_INTEGRATED_$(call package_to_path,rabbitmq-server):=true +NON_INTEGRATED_$(call package_to_path,rabbitmq-erlang-client):=true +NON_INTEGRATED_$(call package_to_path,rabbitmq-java-client):=true +NON_INTEGRATED_$(call package_to_path,rabbitmq-dotnet-client):=true +NON_INTEGRATED_DEPS_$(call package_to_path,rabbitmq-erlang-client):=rabbitmq-server + +# Where the coverage package lives +COVERAGE_PATH:=$(call package_to_path,coverage) + +# Where the rabbitmq-server package lives +RABBITMQ_SERVER_PATH=$(call package_to_path,rabbitmq-server) + +# Cleaning support +ifndef MAKECMDGOALS +TESTABLEGOALS:=$(.DEFAULT_GOAL) +else +TESTABLEGOALS:=$(MAKECMDGOALS) +endif + +# The CLEANING variable can be used to determine whether the top-level +# goal is cleaning related. In particular, it can be used to prevent +# including generated files when cleaning, which might otherwise +# trigger undesirable activity. +ifeq "$(strip $(patsubst clean%,,$(patsubst %clean,,$(TESTABLEGOALS))))" "" +CLEANING:=true +endif + +# Include a generated makefile fragment +# +# Note that this includes using "-include", and thus make will proceed +# even if an error occurs while the fragment is being re-made (we +# don't use "include" becuase it will produce a superfluous error +# message when the fragment is re-made because it doesn't exist). +# Thus you should also list the fragment as a dependency of any rules +# that will refer to the contents of the fragment. +define safe_include +ifndef CLEANING +-include $(1) + +# If we fail to make the fragment, make will just loop trying to +# create it. So we have to explicitly catch that case. +$$(if $$(MAKE_RESTARTS),$$(if $$(wildcard $(1)),,$$(error Failed to produce $(1)))) + +endif +endef + +# This is not the make default, but it is a good idea +.DELETE_ON_ERROR: + +# Declarations for global targets +.PHONY: all-releasable copy-releasable copy-srcdist all-packages clean-all-packages +all-releasable:: +copy-releasable:: +copy-srcdist:: +all-packages:: +clean-all-packages:: +check-xref-packages:: diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/.srcdist_done b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/.srcdist_done new file mode 100644 index 0000000..e69de29 diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0001-R12-fake-iodata-type.patch b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0001-R12-fake-iodata-type.patch new file mode 100644 index 0000000..f1d8e6a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0001-R12-fake-iodata-type.patch @@ -0,0 +1,40 @@ +From c2303fb756eeb8bd92dc04764970a43f59940208 Mon Sep 17 00:00:00 2001 +From: Marek Majkowski +Date: Thu, 26 Jan 2012 12:48:41 +0000 +Subject: [PATCH 1/7] R12 - Fake iodata() type + +--- + include/http.hrl | 2 +- + src/cowboy_http.erl | 3 ++- + 2 files changed, 3 insertions(+), 2 deletions(-) + +diff --git a/include/http.hrl b/include/http.hrl +index c66f2b0..c98f873 100644 +--- a/include/http.hrl ++++ b/include/http.hrl +@@ -47,7 +47,7 @@ + %% Response. + resp_state = waiting :: locked | waiting | chunks | done, + resp_headers = [] :: cowboy_http:headers(), +- resp_body = <<>> :: iodata() | {non_neg_integer(), ++ resp_body = <<>> :: cowboy_http:fake_iodata() | {non_neg_integer(), + fun(() -> {sent, non_neg_integer()})}, + + %% Functions. +diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl +index 32b0ca9..95a7334 100644 +--- a/src/cowboy_http.erl ++++ b/src/cowboy_http.erl +@@ -46,7 +46,8 @@ + | 'Expires' | 'Last-Modified' | 'Accept-Ranges' | 'Set-Cookie' + | 'Set-Cookie2' | 'X-Forwarded-For' | 'Cookie' | 'Keep-Alive' + | 'Proxy-Connection' | binary(). +--type headers() :: [{header(), iodata()}]. ++-type fake_iodata() :: iolist() | binary(). ++-type headers() :: [{header(), fake_iodata()}]. + -type status() :: non_neg_integer() | binary(). + + -export_type([method/0, uri/0, version/0, header/0, headers/0, status/0]). +-- +1.7.0.4 + diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0002-R12-drop-all-references-to-boolean-type.patch b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0002-R12-drop-all-references-to-boolean-type.patch new file mode 100644 index 0000000..aaeedd6 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0002-R12-drop-all-references-to-boolean-type.patch @@ -0,0 +1,165 @@ +From 257e64326ad786d19328d343da0ff7d29adbae4e Mon Sep 17 00:00:00 2001 +From: Marek Majkowski +Date: Thu, 26 Jan 2012 12:51:30 +0000 +Subject: [PATCH 2/7] R12 - drop all references to boolean() type + +--- + src/cowboy_cookies.erl | 8 -------- + src/cowboy_http.erl | 1 - + src/cowboy_http_protocol.erl | 3 +-- + src/cowboy_http_req.erl | 2 -- + src/cowboy_http_static.erl | 5 ----- + src/cowboy_http_websocket.erl | 2 +- + 6 files changed, 2 insertions(+), 19 deletions(-) + +diff --git a/src/cowboy_cookies.erl b/src/cowboy_cookies.erl +index 6818a86..7f5ab60 100644 +--- a/src/cowboy_cookies.erl ++++ b/src/cowboy_cookies.erl +@@ -112,7 +112,6 @@ cookie(Key, Value, Options) when is_binary(Key) + %% Internal. + + %% @doc Check if a character is a white space character. +--spec is_whitespace(char()) -> boolean(). + is_whitespace($\s) -> true; + is_whitespace($\t) -> true; + is_whitespace($\r) -> true; +@@ -120,7 +119,6 @@ is_whitespace($\n) -> true; + is_whitespace(_) -> false. + + %% @doc Check if a character is a seperator. +--spec is_separator(char()) -> boolean(). + is_separator(C) when C < 32 -> true; + is_separator($\s) -> true; + is_separator($\t) -> true; +@@ -144,7 +142,6 @@ is_separator($}) -> true; + is_separator(_) -> false. + + %% @doc Check if a binary has an ASCII seperator character. +--spec has_seperator(binary()) -> boolean(). + has_seperator(<<>>) -> + false; + has_seperator(<<$/, Rest/binary>>) -> +@@ -228,7 +225,6 @@ read_quoted(<>, Acc) -> + read_quoted(Rest, <>). + + %% @doc Drop characters while a function returns true. +--spec binary_dropwhile(fun((char()) -> boolean()), binary()) -> binary(). + binary_dropwhile(_F, <<"">>) -> + <<"">>; + binary_dropwhile(F, String) -> +@@ -246,8 +242,6 @@ skip_whitespace(String) -> + binary_dropwhile(fun is_whitespace/1, String). + + %% @doc Split a binary when the current character causes F to return true. +--spec binary_splitwith(fun((char()) -> boolean()), binary(), binary()) +- -> {binary(), binary()}. + binary_splitwith(_F, Head, <<>>) -> + {Head, <<>>}; + binary_splitwith(F, Head, Tail) -> +@@ -260,8 +254,6 @@ binary_splitwith(F, Head, Tail) -> + end. + + %% @doc Split a binary with a function returning true or false on each char. +--spec binary_splitwith(fun((char()) -> boolean()), binary()) +- -> {binary(), binary()}. + binary_splitwith(F, String) -> + binary_splitwith(F, <<>>, String). + +diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl +index 95a7334..d7261c8 100644 +--- a/src/cowboy_http.erl ++++ b/src/cowboy_http.erl +@@ -755,7 +755,6 @@ urlencode(Bin, Opts) -> + Upper = proplists:get_value(upper, Opts, false), + urlencode(Bin, <<>>, Plus, Upper). + +--spec urlencode(binary(), binary(), boolean(), boolean()) -> binary(). + urlencode(<>, Acc, P=Plus, U=Upper) -> + if C >= $0, C =< $9 -> urlencode(Rest, <>, P, U); + C >= $A, C =< $Z -> urlencode(Rest, <>, P, U); +diff --git a/src/cowboy_http_protocol.erl b/src/cowboy_http_protocol.erl +index baee081..b80745f 100644 +--- a/src/cowboy_http_protocol.erl ++++ b/src/cowboy_http_protocol.erl +@@ -55,7 +55,7 @@ + max_line_length :: integer(), + timeout :: timeout(), + buffer = <<>> :: binary(), +- hibernate = false :: boolean(), ++ hibernate = false, + loop_timeout = infinity :: timeout(), + loop_timeout_ref :: undefined | reference() + }). +@@ -440,7 +440,6 @@ format_header(Field) when byte_size(Field) =< 20; byte_size(Field) > 32 -> + format_header(Field) -> + format_header(Field, true, <<>>). + +--spec format_header(binary(), boolean(), binary()) -> binary(). + format_header(<<>>, _Any, Acc) -> + Acc; + %% Replicate a bug in OTP for compatibility reasons when there's a - right +diff --git a/src/cowboy_http_req.erl b/src/cowboy_http_req.erl +index 92d96ad..d729d6c 100644 +--- a/src/cowboy_http_req.erl ++++ b/src/cowboy_http_req.erl +@@ -515,13 +515,11 @@ set_resp_body_fun(StreamLen, StreamFun, Req) -> + + + %% @doc Return whether the given header has been set for the response. +--spec has_resp_header(cowboy_http:header(), #http_req{}) -> boolean(). + has_resp_header(Name, #http_req{resp_headers=RespHeaders}) -> + NameBin = header_to_binary(Name), + lists:keymember(NameBin, 1, RespHeaders). + + %% @doc Return whether a body has been set for the response. +--spec has_resp_body(#http_req{}) -> boolean(). + has_resp_body(#http_req{resp_body={Length, _}}) -> + Length > 0; + has_resp_body(#http_req{resp_body=RespBody}) -> +diff --git a/src/cowboy_http_static.erl b/src/cowboy_http_static.erl +index 0ee996a..d370046 100644 +--- a/src/cowboy_http_static.erl ++++ b/src/cowboy_http_static.erl +@@ -207,8 +207,6 @@ allowed_methods(Req, State) -> + {['GET', 'HEAD'], Req, State}. + + %% @private +--spec malformed_request(#http_req{}, #state{}) -> +- {boolean(), #http_req{}, #state{}}. + malformed_request(Req, #state{filepath=error}=State) -> + {true, Req, State}; + malformed_request(Req, State) -> +@@ -216,8 +214,6 @@ malformed_request(Req, State) -> + + + %% @private Check if the resource exists under the document root. +--spec resource_exists(#http_req{}, #state{}) -> +- {boolean(), #http_req{}, #state{}}. + resource_exists(Req, #state{fileinfo={error, _}}=State) -> + {false, Req, State}; + resource_exists(Req, #state{fileinfo={ok, Fileinfo}}=State) -> +@@ -227,7 +223,6 @@ resource_exists(Req, #state{fileinfo={ok, Fileinfo}}=State) -> + %% @private + %% Access to a file resource is forbidden if it exists and the local node does + %% not have permission to read it. Directory listings are always forbidden. +--spec forbidden(#http_req{}, #state{}) -> {boolean(), #http_req{}, #state{}}. + forbidden(Req, #state{fileinfo={_, #file_info{type=directory}}}=State) -> + {true, Req, State}; + forbidden(Req, #state{fileinfo={error, eacces}}=State) -> +diff --git a/src/cowboy_http_websocket.erl b/src/cowboy_http_websocket.erl +index 0f0204c..5f59891 100644 +--- a/src/cowboy_http_websocket.erl ++++ b/src/cowboy_http_websocket.erl +@@ -54,7 +54,7 @@ + timeout = infinity :: timeout(), + timeout_ref = undefined :: undefined | reference(), + messages = undefined :: undefined | {atom(), atom(), atom()}, +- hibernate = false :: boolean(), ++ hibernate = false, + eop :: undefined | tuple(), %% hixie-76 specific. + origin = undefined :: undefined | binary() %% hixie-76 specific. + }). +-- +1.7.0.4 + diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0003-R12-drop-all-references-to-reference-type.patch b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0003-R12-drop-all-references-to-reference-type.patch new file mode 100644 index 0000000..e0ebae9 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0003-R12-drop-all-references-to-reference-type.patch @@ -0,0 +1,55 @@ +From 4db80ab7bacf04502ad2d29d4760e04a6d787a83 Mon Sep 17 00:00:00 2001 +From: Marek Majkowski +Date: Thu, 26 Jan 2012 12:52:23 +0000 +Subject: [PATCH 3/7] R12: drop all references to reference() type + +--- + src/cowboy_http_protocol.erl | 2 +- + src/cowboy_http_websocket.erl | 2 +- + src/cowboy_listener.erl | 2 +- + 3 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/cowboy_http_protocol.erl b/src/cowboy_http_protocol.erl +index b80745f..0183785 100644 +--- a/src/cowboy_http_protocol.erl ++++ b/src/cowboy_http_protocol.erl +@@ -57,7 +57,7 @@ + buffer = <<>> :: binary(), + hibernate = false, + loop_timeout = infinity :: timeout(), +- loop_timeout_ref :: undefined | reference() ++ loop_timeout_ref + }). + + %% API. +diff --git a/src/cowboy_http_websocket.erl b/src/cowboy_http_websocket.erl +index 5f59891..5100213 100644 +--- a/src/cowboy_http_websocket.erl ++++ b/src/cowboy_http_websocket.erl +@@ -52,7 +52,7 @@ + opts :: any(), + challenge = undefined :: undefined | binary() | {binary(), binary()}, + timeout = infinity :: timeout(), +- timeout_ref = undefined :: undefined | reference(), ++ timeout_ref = undefined, + messages = undefined :: undefined | {atom(), atom(), atom()}, + hibernate = false, + eop :: undefined | tuple(), %% hixie-76 specific. +diff --git a/src/cowboy_listener.erl b/src/cowboy_listener.erl +index c19d079..86e87f1 100644 +--- a/src/cowboy_listener.erl ++++ b/src/cowboy_listener.erl +@@ -23,8 +23,8 @@ + + -record(state, { + req_pools = [] :: [{atom(), non_neg_integer()}], +- reqs_table :: ets:tid(), +- queue = [] :: [{pid(), reference()}] ++ reqs_table, ++ queue = [] + }). + + %% API. +-- +1.7.0.4 + diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0004-R12-drop-references-to-iodata-type.patch b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0004-R12-drop-references-to-iodata-type.patch new file mode 100644 index 0000000..d6f097c --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0004-R12-drop-references-to-iodata-type.patch @@ -0,0 +1,50 @@ +From dfb750f491208a8e30cab0fa701dd866d60734b8 Mon Sep 17 00:00:00 2001 +From: Marek Majkowski +Date: Thu, 26 Jan 2012 12:53:08 +0000 +Subject: [PATCH 4/7] R12: drop references to iodata() type + +--- + src/cowboy_http_req.erl | 6 ------ + 1 files changed, 0 insertions(+), 6 deletions(-) + +diff --git a/src/cowboy_http_req.erl b/src/cowboy_http_req.erl +index d729d6c..64e757c 100644 +--- a/src/cowboy_http_req.erl ++++ b/src/cowboy_http_req.erl +@@ -478,8 +478,6 @@ set_resp_cookie(Name, Value, Options, Req) -> + set_resp_header(HeaderName, HeaderValue, Req). + + %% @doc Add a header to the response. +--spec set_resp_header(cowboy_http:header(), iodata(), #http_req{}) +- -> {ok, #http_req{}}. + set_resp_header(Name, Value, Req=#http_req{resp_headers=RespHeaders}) -> + NameBin = header_to_binary(Name), + {ok, Req#http_req{resp_headers=[{NameBin, Value}|RespHeaders]}}. +@@ -489,7 +487,6 @@ set_resp_header(Name, Value, Req=#http_req{resp_headers=RespHeaders}) -> + %% The body set here is ignored if the response is later sent using + %% anything other than reply/2 or reply/3. The response body is expected + %% to be a binary or an iolist. +--spec set_resp_body(iodata(), #http_req{}) -> {ok, #http_req{}}. + set_resp_body(Body, Req) -> + {ok, Req#http_req{resp_body=Body}}. + +@@ -537,8 +534,6 @@ reply(Status, Headers, Req=#http_req{resp_body=Body}) -> + reply(Status, Headers, Body, Req). + + %% @doc Send a reply to the client. +--spec reply(cowboy_http:status(), cowboy_http:headers(), iodata(), #http_req{}) +- -> {ok, #http_req{}}. + reply(Status, Headers, Body, Req=#http_req{socket=Socket, + transport=Transport, connection=Connection, pid=ReqPid, + method=Method, resp_state=waiting, resp_headers=RespHeaders}) -> +@@ -586,7 +581,6 @@ chunked_reply(Status, Headers, Req=#http_req{socket=Socket, + %% @doc Send a chunk of data. + %% + %% A chunked reply must have been initiated before calling this function. +--spec chunk(iodata(), #http_req{}) -> ok | {error, atom()}. + chunk(_Data, #http_req{socket=_Socket, transport=_Transport, method='HEAD'}) -> + ok; + chunk(Data, #http_req{socket=Socket, transport=Transport, resp_state=chunks}) -> +-- +1.7.0.4 + diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0005-R12-drop-references-to-Default-any-type.patch b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0005-R12-drop-references-to-Default-any-type.patch new file mode 100644 index 0000000..5fc06fd --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0005-R12-drop-references-to-Default-any-type.patch @@ -0,0 +1,52 @@ +From c7aef1d044a1e83fcd6be7a83b2c763c0366d4f8 Mon Sep 17 00:00:00 2001 +From: Marek Majkowski +Date: Thu, 26 Jan 2012 12:53:36 +0000 +Subject: [PATCH 5/7] R12: drop references to Default:any() type + +--- + src/cowboy_http_req.erl | 8 -------- + 1 files changed, 0 insertions(+), 8 deletions(-) + +diff --git a/src/cowboy_http_req.erl b/src/cowboy_http_req.erl +index 64e757c..c884f5a 100644 +--- a/src/cowboy_http_req.erl ++++ b/src/cowboy_http_req.erl +@@ -147,8 +147,6 @@ qs_val(Name, Req) when is_binary(Name) -> + + %% @doc Return the query string value for the given key, or a default if + %% missing. +--spec qs_val(binary(), #http_req{}, Default) +- -> {binary() | true | Default, #http_req{}} when Default::any(). + qs_val(Name, Req=#http_req{raw_qs=RawQs, qs_vals=undefined, + urldecode={URLDecFun, URLDecArg}}, Default) when is_binary(Name) -> + QsVals = parse_qs(RawQs, fun(Bin) -> URLDecFun(Bin, URLDecArg) end), +@@ -180,8 +178,6 @@ binding(Name, Req) when is_atom(Name) -> + + %% @doc Return the binding value for the given key obtained when matching + %% the host and path against the dispatch list, or a default if missing. +--spec binding(atom(), #http_req{}, Default) +- -> {binary() | Default, #http_req{}} when Default::any(). + binding(Name, Req, Default) when is_atom(Name) -> + case lists:keyfind(Name, 1, Req#http_req.bindings) of + {Name, Value} -> {Value, Req}; +@@ -200,8 +196,6 @@ header(Name, Req) when is_atom(Name) orelse is_binary(Name) -> + header(Name, Req, undefined). + + %% @doc Return the header value for the given key, or a default if missing. +--spec header(atom() | binary(), #http_req{}, Default) +- -> {binary() | Default, #http_req{}} when Default::any(). + header(Name, Req, Default) when is_atom(Name) orelse is_binary(Name) -> + case lists:keyfind(Name, 1, Req#http_req.headers) of + {Name, Value} -> {Value, Req}; +@@ -313,8 +307,6 @@ cookie(Name, Req) when is_binary(Name) -> + + %% @doc Return the cookie value for the given key, or a default if + %% missing. +--spec cookie(binary(), #http_req{}, Default) +- -> {binary() | true | Default, #http_req{}} when Default::any(). + cookie(Name, Req=#http_req{cookies=undefined}, Default) when is_binary(Name) -> + case header('Cookie', Req) of + {undefined, Req2} -> +-- +1.7.0.4 + diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0006-Use-erlang-integer_to_list-and-lists-max-instead-of-.patch b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0006-Use-erlang-integer_to_list-and-lists-max-instead-of-.patch new file mode 100644 index 0000000..183ebd2 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0006-Use-erlang-integer_to_list-and-lists-max-instead-of-.patch @@ -0,0 +1,62 @@ +From 81106c53b80f5d0fa441b893048bbdc6c9e2c4f0 Mon Sep 17 00:00:00 2001 +From: Marek Majkowski +Date: Thu, 26 Jan 2012 12:54:31 +0000 +Subject: [PATCH 6/7] Use erlang:integer_to_list and lists:max instead of bifs + +--- + src/cowboy_http_req.erl | 2 +- + src/cowboy_http_static.erl | 2 +- + src/cowboy_multipart.erl | 4 ++-- + 3 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/src/cowboy_http_req.erl b/src/cowboy_http_req.erl +index c884f5a..bf4ac7a 100644 +--- a/src/cowboy_http_req.erl ++++ b/src/cowboy_http_req.erl +@@ -576,7 +576,7 @@ chunked_reply(Status, Headers, Req=#http_req{socket=Socket, + chunk(_Data, #http_req{socket=_Socket, transport=_Transport, method='HEAD'}) -> + ok; + chunk(Data, #http_req{socket=Socket, transport=Transport, resp_state=chunks}) -> +- Transport:send(Socket, [integer_to_list(iolist_size(Data), 16), ++ Transport:send(Socket, [erlang:integer_to_list(iolist_size(Data), 16), + <<"\r\n">>, Data, <<"\r\n">>]). + + %% @doc Send an upgrade reply. +diff --git a/src/cowboy_http_static.erl b/src/cowboy_http_static.erl +index d370046..da3bd33 100644 +--- a/src/cowboy_http_static.erl ++++ b/src/cowboy_http_static.erl +@@ -412,7 +412,7 @@ attr_etag_function(Args, Attrs) -> + + -spec attr_etag_function([etagarg()], [fileattr()], [binary()]) -> binary(). + attr_etag_function(_Args, [], Acc) -> +- list_to_binary(integer_to_list(erlang:crc32(Acc), 16)); ++ list_to_binary(erlang:integer_to_list(erlang:crc32(Acc), 16)); + attr_etag_function(Args, [H|T], Acc) -> + {_, Value} = lists:keyfind(H, 1, Args), + attr_etag_function(Args, T, [term_to_binary(Value)|Acc]). +diff --git a/src/cowboy_multipart.erl b/src/cowboy_multipart.erl +index b7aeb54..c9b5b6c 100644 +--- a/src/cowboy_multipart.erl ++++ b/src/cowboy_multipart.erl +@@ -105,7 +105,7 @@ parse_boundary_eol(Bin, Pattern) -> + cowboy_http:whitespace(Rest, Fun); + nomatch -> + % CRLF not found in the given binary. +- RestStart = max(byte_size(Bin) - 1, 0), ++ RestStart = lists:max([byte_size(Bin) - 1, 0]), + <<_:RestStart/binary, Rest/binary>> = Bin, + more(Rest, fun (NewBin) -> parse_boundary_eol(NewBin, Pattern) end) + end. +@@ -175,7 +175,7 @@ skip(Bin, Pattern = {P, PSize}) -> + parse_boundary_tail(Rest, Pattern); + nomatch -> + % Boundary not found, need more data. +- RestStart = max(byte_size(Bin) - PSize + 1, 0), ++ RestStart = lists:max([byte_size(Bin) - PSize + 1, 0]), + <<_:RestStart/binary, Rest/binary>> = Bin, + more(Rest, fun (NewBin) -> skip(NewBin, Pattern) end) + end. +-- +1.7.0.4 + diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0007-R12-type-definitions-must-be-ordered.patch b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0007-R12-type-definitions-must-be-ordered.patch new file mode 100644 index 0000000..1b1f3de --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0007-R12-type-definitions-must-be-ordered.patch @@ -0,0 +1,37 @@ +From 547731d5490b36f1239a99e6c4acc1964e724a6e Mon Sep 17 00:00:00 2001 +From: Marek Majkowski +Date: Thu, 26 Jan 2012 12:54:49 +0000 +Subject: [PATCH 7/7] R12 - type definitions must be ordered + +--- + src/cowboy_multipart.erl | 10 +++++----- + 1 files changed, 5 insertions(+), 5 deletions(-) + +diff --git a/src/cowboy_multipart.erl b/src/cowboy_multipart.erl +index c9b5b6c..0bd123a 100644 +--- a/src/cowboy_multipart.erl ++++ b/src/cowboy_multipart.erl +@@ -15,15 +15,15 @@ + %% @doc Multipart parser. + -module(cowboy_multipart). + +--type part_parser() :: parser(more(part_result())). ++-type part_parser() :: any(). + -type parser(T) :: fun((binary()) -> T). + -type more(T) :: T | {more, parser(T)}. +--type part_result() :: headers() | eof. +--type headers() :: {headers, http_headers(), body_cont()}. ++-type part_result() :: any(). ++-type headers() :: any(). + -type http_headers() :: [{atom() | binary(), binary()}]. +--type body_cont() :: cont(more(body_result())). ++-type body_cont() :: any(). + -type cont(T) :: fun(() -> T). +--type body_result() :: {body, binary(), body_cont()} | end_of_part(). ++-type body_result() :: any(). + -type end_of_part() :: {end_of_part, cont(more(part_result()))}. + -type disposition() :: {binary(), [{binary(), binary()}]}. + +-- +1.7.0.4 + diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0008-sec-websocket-protocol.patch b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0008-sec-websocket-protocol.patch new file mode 100644 index 0000000..494c6b8 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/0008-sec-websocket-protocol.patch @@ -0,0 +1,16 @@ +diff --git a/src/cowboy_http_req.erl b/src/cowboy_http_req.erl +index 92d96ad..dd772df 100644 +--- a/src/cowboy_http_req.erl ++++ b/src/cowboy_http_req.erl +@@ -288,6 +282,11 @@ parse_header(Name, Req, Default) when Name =:= 'Upgrade' -> + fun (Value) -> + cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2) + end); ++parse_header(Name, Req, Default) when Name =:= <<"sec-websocket-protocol">> -> ++ parse_header(Name, Req, Default, ++ fun (Value) -> ++ cowboy_http:nonempty_list(Value, fun cowboy_http:token/2) ++ end); + parse_header(Name, Req, Default) -> + {Value, Req2} = header(Name, Req, Default), + {undefined, Value, Req2}. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/Makefile b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/Makefile new file mode 100644 index 0000000..482105a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/Makefile @@ -0,0 +1 @@ +include ../umbrella.mk diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/README.md b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/README.md new file mode 100644 index 0000000..e1f1d5e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/README.md @@ -0,0 +1 @@ +Cowboy requires R14 diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/.done b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/.done new file mode 100644 index 0000000..e69de29 diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/.travis.yml b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/.travis.yml new file mode 100644 index 0000000..f04becf --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/.travis.yml @@ -0,0 +1,7 @@ +language: erlang +otp_release: + - R15B + - R14B04 + - R14B03 + - R14B02 +script: "make tests" diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/AUTHORS b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/AUTHORS new file mode 100644 index 0000000..a07a69d --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/AUTHORS @@ -0,0 +1,18 @@ +Cowboy is available thanks to the work of: + +Loïc Hoguin +Anthony Ramine +Magnus Klaar +Paul Oliver +Steven Gravell +Tom Burdick +Hunter Morris +Yurii Rashkovskii +Ali Sabil +Hans Ulrich Niedermann +Jesper Louis Andersen +Mathieu Lecarme +Max Lapshin +Michiel Hakvoort +Ori Bar +Alisdair Sullivan diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/CHANGELOG.md b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/CHANGELOG.md new file mode 100644 index 0000000..a4b815b --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/CHANGELOG.md @@ -0,0 +1,213 @@ +CHANGELOG +========= + +0.4.0 +----- + +* Set the cowboy_listener process priority to high + + As it is the central process used by all incoming requests + we need to set its priority to high to avoid timeouts that + would happen otherwise when reaching a huge number of + concurrent requests. + +* Add cowboy:child_spec/6 for embedding in other applications + +* Add cowboy_http_rest, an experimental REST protocol support + + Based on the Webmachine diagram and documentation. It is a + new implementation, not a port, therefore a few changes have + been made. However all the callback names are the same and + should behave similarly to Webmachine. + + There is currently no documentation other than the Webmachine + resource documentation and the comments found in cowboy_http_rest, + which itself should be fairly easy to read and understand. + +* Add cowboy_http_static, an experimental static file handler + + Makes use of the aforementioned REST protocol support to + deliver files with proper content type and cache headers. + + Note that this uses the new file:sendfile support when + appropriate, which currently requires the VM to be started + with the +A option defined, else errors may randomly appear. + +* Add cowboy_bstr module for binary strings related functions + +* Add cowboy_http module for HTTP parsing functions + + This module so far contains various functions for HTTP header + parsing along with URL encoding and decoding. + +* Remove quoted from the default dependencies + + This should make Cowboy much easier to compile and use by default. + It is of course still possible to use quoted as your URL decoding + library in Cowboy thanks to the newly added urldecode option. + +* Fix supervisor spec for non dynamic modules to allow upgrades to complete + +* Add cowboy:accept_ack/1 for a cleaner handling of the shoot message + + Before, when the listener accepted a connection, the newly created + process was waiting for a message containing the atom 'shoot' before + proceeding. This has been replaced by the cowboy:accept_ack/1 function. + + This function should be used where 'shoot' was received because the + contents of the message have changed (and could change again in the + distant future). + +* Update binary parsing expressions to avoid hype crashes + + More specifically, /bits was replaced by /binary. + +* Rename the type cowboy_dispatcher:path_tokens/0 to tokens/0 + +* Remove the cowboy_clock:date/0, time/0 and datetime/0 types + + The calendar module exports those same types properly since R14B04. + +* Add cacertfile configuration option to cowboy_ssl_transport + +* Add cowboy_protocol behaviour + +* Remove -Wbehaviours dialyzer option unavailable in R15B + +* Many tests and specs improvements + +### cowboy_http_req + +* Fix a crash when reading the request body + +* Add parse_header/2 and parse_header/3 + + The following headers can now be semantically parsed: Connection, Accept, + Accept-Charset, Accept-Encoding, Accept-Language, Content-Length, + Content-Type, If-Match, If-None-Match, If-Modified-Since, + If-Unmodified-Since, Upgrade + +* Add set_resp_header/3, set_resp_cookie/4 and set_resp_body/2 + + These functions allow handlers to set response headers and body + without having to reply directly. + +* Add set_resp_body_fun/3 + + This function allows handlers to stream the body of the response + using the given fun. The size of the response must be known beforehand. + +* Add transport/1 to obtain the transport and socket for the request + + This allows handlers to have low-level socket access in those cases + where they do need it, like when streaming a response body with + set_resp_body_fun/3. + +* Add peer_addr/1 + + This function tries to guess the real peer IP based on the HTTP + headers received. + +* Add meta/2 and meta/3 to save useful protocol information + + Currently used to save the Websocket protocol version currently used, + and to save request information in the REST protocol handler. + +* Add reply/2 and reply/3 aliases to reply/4 + +* Add upgrade_reply/3 for protocol upgrades + +### cowboy_http_protocol + +* Add the {urldecode, fun urldecode/2} option + + Added when quoted was removed from the default build. Can be used to + tell Cowboy to use quoted or any other URL decoding routine. + +* Add the max_keepalive option + +* Add the max_line_length option + +* Allow HTTP handlers to stop during init/3 + + To do so they can return {shutdown, Req, State}. + +* Add loops support in HTTP handlers for proper long-polling support + + A loop can be entered by returning either of {loop, Req, State}, + {loop, Req, State, hibernate}, {loop, Req, State, Timeout} or + {loop, Req, State, Timeout, hibernate} from init/3. + + Loops are useful when we cannot reply immediately and instead + are waiting for an Erlang message to be able to complete the request, + as would typically be done for long-polling. + + Loop support in the protocol means that timeouts and hibernating + are well tested and handled so you can use those options without + worrying. It is recommended to set the timeout option. + + When a loop is started, handle/2 will never be called so it does + not need to be defined. When the request process receives an Erlang + message, it will call the info/3 function with the message as the + first argument. + + Like in OTP, you do need to set timeout and hibernate again when + returning from info/3 to enable them until the next call. + +* Fix the sending of 500 errors when handlers crash + + Now we send an error response when no response has been sent, + and do nothing more than close the connection if anything + did get sent. + +* Fix a crash when the server is sent HTTP responses + +* Fix HTTP timeouts handling when the Request-Line wasn't received + +* Fix the handling of the max number of empty lines between requests + +* Fix the handling of HEAD requests + +* Fix HTTP/1.0 Host header handling + +* Reply status 400 if we receive an unexpected value or error for headers + +* Properly close when the application sends "Connection: close" header + +* Close HTTP connections on all errors + +* Improve the error message for HTTP handlers + +### cowboy_http_websocket + +* Add websocket support for all versions up to RFC 6455 + + Support isn't perfect yet according to the specifications, but + is working against all currently known client implementations. + +* Allow websocket_init/3 to return with the hibernate option set + +* Add {shutdown, Req} return value to websocket_init/3 to fail an upgrade + +* Fix websocket timeout handling + +* Fix error messages: wrong callback name was reported on error + +* Fix byte-by-byte websocket handling + +* Fix an issue when using hixie-76 with certain proxies + +* Fix a crash in the hixie-76 handshake + +* Fix the handshake when SSL is used on port 443 + +* Fix a crash in the handshake when cowboy_http_req:compact/1 is used + +* Fix handshake when a query string is present + +* Fix a crash when the Upgrade header contains more than one token + +0.2.0 +----- + +* Initial release. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/LICENSE b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/LICENSE new file mode 100644 index 0000000..7de99bb --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2011, Loïc Hoguin + +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. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/Makefile b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/Makefile new file mode 100644 index 0000000..e5524f4 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/Makefile @@ -0,0 +1,36 @@ +# See LICENSE for licensing information. + +DIALYZER = dialyzer +REBAR = rebar + +all: app + +app: deps + @$(REBAR) compile + +deps: + @$(REBAR) get-deps + +clean: + @$(REBAR) clean + rm -f test/*.beam + rm -f erl_crash.dump + +tests: clean app eunit ct + +eunit: + @$(REBAR) eunit skip_deps=true + +ct: + @$(REBAR) ct skip_deps=true + +build-plt: + @$(DIALYZER) --build_plt --output_plt .cowboy_dialyzer.plt \ + --apps kernel stdlib sasl inets crypto public_key ssl + +dialyze: + @$(DIALYZER) --src src --plt .cowboy_dialyzer.plt -Werror_handling \ + -Wrace_conditions -Wunmatched_returns # -Wunderspecs + +docs: + @$(REBAR) doc skip_deps=true diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/README.md b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/README.md new file mode 100644 index 0000000..d5950b9 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/README.md @@ -0,0 +1,290 @@ +Cowboy +====== + +Cowboy is a small, fast and modular HTTP server written in Erlang. + +Cowboy is also a socket acceptor pool, able to accept connections +for any kind of TCP protocol. + +Goals +----- + +Cowboy aims to provide the following advantages: + +* **Small** code base. +* Damn **fast**. +* **Modular**: transport and protocol handlers are replaceable. +* **Binary HTTP** for greater speed and lower memory usage. +* Easy to **embed** inside another application. +* Selectively **dispatch** requests to handlers, allowing you to send some + requests to your embedded code and others to a FastCGI application in + PHP or Ruby. +* No parameterized module. No process dictionary. **Clean** Erlang code. + +The server is currently in early development. Comments and suggestions are +more than welcome. To contribute, either open bug reports, or fork the project +and send us pull requests with new or improved functionality. You should +discuss your plans with us before doing any serious work, though, to avoid +duplicating efforts. + +Quick start +----------- + +* Add Cowboy as a rebar or agner dependency to your application. +* Start Cowboy and add one or more listeners. +* Write handlers for your application. +* Check out [examples](https://github.com/extend/cowboy_examples)! + +Getting Started +--------------- + +At heart, Cowboy is nothing more than an TCP acceptor pool. All it does is +accept connections received on a given port and using a given transport, +like TCP or SSL, and forward them to a request handler for the given +protocol. Acceptors and request handlers are of course supervised +automatically. + +It just so happens that Cowboy also includes an HTTP protocol handler. +But Cowboy does nothing by default. You need to explicitly ask Cowboy +to listen on a port with your chosen transport and protocol handlers. +To do so, you must start a listener. + +A listener is a special kind of supervisor that manages both the +acceptor pool and the request processes. It is named and can thus be +started and stopped at will. + +An acceptor pool is a pool of processes whose only role is to accept +new connections. It's good practice to have many of these processes +as they are very cheap and allow much quicker response when you get +many connections. Of course, as with everything else, you should +**benchmark** before you decide what's best for you. + +Cowboy includes a TCP transport handler for HTTP and an SSL transport +handler for HTTPS. The transport handlers can of course be reused for +other protocols like FTP or IRC. + +The HTTP protocol requires one last thing to continue: dispatching rules. +Don't worry about it right now though and continue reading, it'll all +be explained. + +You can start and stop listeners by calling `cowboy:start_listener/6` and +`cowboy:stop_listener/1` respectively. + +The following example demonstrates the startup of a very simple listener. + +``` erlang +application:start(cowboy), +Dispatch = [ + %% {Host, list({Path, Handler, Opts})} + {'_', [{'_', my_handler, []}]} +], +%% Name, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts +cowboy:start_listener(my_http_listener, 100, + cowboy_tcp_transport, [{port, 8080}], + cowboy_http_protocol, [{dispatch, Dispatch}] +). +``` + +This is not enough though, you must also write the my_handler module +to process the incoming HTTP requests. Of course Cowboy comes with +predefined handlers for specific tasks but most of the time you'll +want to write your own handlers for your application. + +Following is an example of a "Hello World!" HTTP handler. + +``` erlang +-module(my_handler). +-export([init/3, handle/2, terminate/2]). + +init({tcp, http}, Req, Opts) -> + {ok, Req, undefined_state}. + +handle(Req, State) -> + {ok, Req2} = cowboy_http_req:reply(200, [], <<"Hello World!">>, Req), + {ok, Req2, State}. + +terminate(Req, State) -> + ok. +``` + +You can also write handlers that do not reply directly. Instead, such handlers +will wait for an Erlang message from another process and only reply when +receiving such message, or timeout if it didn't arrive in time. + +This is especially useful for long-polling functionality, as Cowboy will handle +process hibernation and timeouts properly, preventing mistakes if you were to +write the code yourself. An handler of that kind can be defined like this: + +``` erlang +-module(my_loop_handler). +-export([init/3, info/3, terminate/2]). + +-define(TIMEOUT, 60000). + +init({tcp, http}, Req, Opts) -> + {loop, Req, undefined_state, ?TIMEOUT, hibernate}. + +info({reply, Body}, Req, State) -> + {ok, Req2} = cowboy_http_req:reply(200, [], Body, Req), + {ok, Req2, State}; +info(Message, Req, State) -> + {loop, Req, State, hibernate}. + +terminate(Req, State) -> + ok. +``` + +It is of course possible to combine both type of handlers together as long as +you return the proper tuple from init/3. + +**Note**: versions prior to `0.4.0` used the +[quoted](https://github.com/klaar/quoted.erl) library instead of the built in +`cowboy_http:urldecode/2` function. If you want to retain this you must add it +as a dependency to your application and add the following cowboy_http_protocol +option: + +``` erlang + {urldecode, {fun quoted:from_url/2, quoted:make([])}} +``` + +Continue reading to learn how to dispatch rules and handle requests. + +Dispatch rules +-------------- + +Cowboy allows you to dispatch HTTP requests directly to a specific handler +based on the hostname and path information from the request. It also lets +you define static options for the handler directly in the rules. + +To match the hostname and path, Cowboy requires a list of tokens. For +example, to match the "dev-extend.eu" domain name, you must specify +`[<<"dev-extend">>, <<"eu">>]`. Or, to match the "/path/to/my/resource" +you must use `[<<"path">>, <<"to">>, <<"my">>, <<"resource">>]`. All the +tokens must be given as binary. + +You can use the special token `'_'` (the atom underscore) to indicate that +you accept anything in that position. For example if you have both +"dev-extend.eu" and "dev-extend.fr" domains, you can use the match spec +`[<<"dev-extend">>, '_']` to match any top level extension. + +Finally, you can also match multiple leading segments of the domain name and +multiple trailing segments of the request path using the atom `'...'` (the atom +ellipsis) respectively as the first host token or the last path token. For +example, host rule `['...', <<"dev-extend">>, <<"eu">>]` can match both +"cowboy.bugs.dev-extend.eu" and "dev-extend.eu" and path rule +`[<<"projects">>, '...']` can match both "/projects" and +"/projects/cowboy/issues/42". The host leading segments and the path trailing +segments can later be retrieved through `cowboy_http_req:host_info/1` and +`cowboy_http_req:path_info/1`. + +Any other atom used as a token will bind the value to this atom when +matching. To follow on our hostnames example, `[<<"dev-extend">>, ext]` +would bind the values `<<"eu">>` and `<<"fr">>` to the ext atom, that you +can later retrieve in your handler by calling `cowboy_http_req:binding/{2,3}`. + +You can also accept any match spec by using the atom `'_'` directly instead of +a list of tokens. Our hello world example above uses this to forward all +requests to a single handler. + +There is currently no way to match multiple tokens at once. + +Requests handling +----------------- + +Requests are passed around in the Request variable. Although they are +defined as a record, it is recommended to access them only through the +cowboy_http_req module API. + +You can retrieve the HTTP method, HTTP version, peer address and port, +host tokens, raw host, used port, path tokens, raw path, query string +values, bound values from the dispatch step, header values from the +request. You can also read the request body, if any, optionally parsing +it as a query string. Finally, the request allows you to send a response +to the client. + +See the cowboy_http_req module for more information. + +Websockets +---------- + +The Websocket protocol is built upon the HTTP protocol. It first sends +an HTTP request for an handshake, performs it and then switches +to Websocket. Therefore you need to write a standard HTTP handler to +confirm the handshake should be completed and then the Websocket-specific +callbacks. + +A simple handler doing nothing but sending a repetitive message using +Websocket would look like this: + +``` erlang +-module(my_ws_handler). +-export([init/3]). +-export([websocket_init/3, websocket_handle/3, + websocket_info/3, websocket_terminate/3]). + +init({tcp, http}, Req, Opts) -> + {upgrade, protocol, cowboy_http_websocket}. + +websocket_init(TransportName, Req, _Opts) -> + erlang:start_timer(1000, self(), <<"Hello!">>), + {ok, Req, undefined_state}. + +websocket_handle({text, Msg}, Req, State) -> + {reply, {text, << "That's what she said! ", Msg/binary >>}, Req, State}; +websocket_handle(_Data, Req, State) -> + {ok, Req, State}. + +websocket_info({timeout, _Ref, Msg}, Req, State) -> + erlang:start_timer(1000, self(), <<"How' you doin'?">>), + {reply, {text, Msg}, Req, State}; +websocket_info(_Info, Req, State) -> + {ok, Req, State}. + +websocket_terminate(_Reason, _Req, _State) -> + ok. +``` + +Of course you can have an HTTP handler doing both HTTP and Websocket +handling, but for the sake of this example we're ignoring the HTTP +part entirely. + +As the Websocket protocol is still a draft the API is subject to change +regularly when support to the most recent drafts gets added. Features may +be added, changed or removed before the protocol gets finalized. Cowboy +tries to implement all drafts transparently and give a single interface to +handle them all, however. + +Using Cowboy with other protocols +--------------------------------- + +One of the strengths of Cowboy is of course that you can use it with any +protocol you want. The only downside is that if it's not HTTP, you'll +probably have to write the protocol handler yourself. + +The only exported function a protocol handler needs is the start_link/4 +function, with arguments ListenerPid, Socket, Transport and Opts. ListenerPid +is the pid to the listener's gen_server, managing the connections. Socket is of +course the client socket; Transport is the module name of the chosen transport +handler and Opts is protocol options defined when starting the listener. + +After initializing your protocol, it is recommended to call the +function cowboy:accept_ack/1 with the ListenerPid as argument, +as it will ensure Cowboy has been able to fully initialize the socket. +Anything you do past this point is up to you! + +If you need to change some socket options, like enabling raw mode for example, +you can call the Transport:setopts/2 function. It is the protocol's +responsability to manage the socket usage, there should be no need for an user +to specify that kind of options while starting a listener. + +You should definitely look at the cowboy_http_protocol module for a great +example of fast request handling if you need to. Otherwise it's probably +safe to use `{active, once}` mode and handle everything as it comes. + +Note that while you technically can run a protocol handler directly as a +gen_server or a gen_fsm, it's probably not a good idea, as the only call +you'll ever receive from Cowboy is the start_link/4 call. On the other +hand, feel free to write a very basic protocol handler which then forwards +requests to a gen_server or gen_fsm. By doing so however you must take +care to supervise their processes as Cowboy only knows about the protocol +handler itself. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/cover.spec b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/cover.spec new file mode 100644 index 0000000..9dba11c --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/cover.spec @@ -0,0 +1 @@ +{incl_app, cowboy, details}. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/doc/overview.edoc b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/doc/overview.edoc new file mode 100644 index 0000000..56648c4 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/doc/overview.edoc @@ -0,0 +1,4 @@ +@author Loïc Hoguin +@copyright 2011 Loïc Hoguin +@version HEAD +@title Small, fast, modular HTTP server. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/include/http.hrl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/include/http.hrl new file mode 100644 index 0000000..c98f873 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/include/http.hrl @@ -0,0 +1,55 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% Copyright (c) 2011, Anthony Ramine +%% +%% 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. + +-record(http_req, { + %% Transport. + socket = undefined :: undefined | inet:socket(), + transport = undefined :: undefined | module(), + connection = keepalive :: keepalive | close, + + %% Request. + pid = undefined :: pid(), + method = 'GET' :: cowboy_http:method(), + version = {1, 1} :: cowboy_http:version(), + peer = undefined :: undefined | {inet:ip_address(), inet:ip_port()}, + host = undefined :: undefined | cowboy_dispatcher:tokens(), + host_info = undefined :: undefined | cowboy_dispatcher:tokens(), + raw_host = undefined :: undefined | binary(), + port = undefined :: undefined | inet:ip_port(), + path = undefined :: undefined | '*' | cowboy_dispatcher:tokens(), + path_info = undefined :: undefined | cowboy_dispatcher:tokens(), + raw_path = undefined :: undefined | binary(), + qs_vals = undefined :: undefined | list({binary(), binary() | true}), + raw_qs = undefined :: undefined | binary(), + bindings = undefined :: undefined | cowboy_dispatcher:bindings(), + headers = [] :: cowboy_http:headers(), + p_headers = [] :: [any()], %% @todo Improve those specs. + cookies = undefined :: undefined | [{binary(), binary()}], + meta = [] :: [{atom(), any()}], + + %% Request body. + body_state = waiting :: waiting | done | + {multipart, non_neg_integer(), fun()}, + buffer = <<>> :: binary(), + + %% Response. + resp_state = waiting :: locked | waiting | chunks | done, + resp_headers = [] :: cowboy_http:headers(), + resp_body = <<>> :: cowboy_http:fake_iodata() | {non_neg_integer(), + fun(() -> {sent, non_neg_integer()})}, + + %% Functions. + urldecode :: {fun((binary(), T) -> binary()), T} +}). diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/rebar.config b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/rebar.config new file mode 100644 index 0000000..82d1fca --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/rebar.config @@ -0,0 +1,12 @@ +{cover_enabled, true}. +{deps, [ + {proper, "1.0", + {git, "git://github.com/manopapad/proper.git", {tag, "v1.0"}}} +]}. +{eunit_opts, [verbose, {report, {eunit_surefire, [{dir, "."}]}}]}. +{erl_opts, [ +%% bin_opt_info, +%% warn_missing_spec, + warnings_as_errors, + warn_export_all +]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy.app.src b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy.app.src new file mode 100644 index 0000000..9b3ee50 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy.app.src @@ -0,0 +1,26 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% +%% 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. + +{application, cowboy, [ + {description, "Small, fast, modular HTTP server."}, + {vsn, "0.5.0"}, + {modules, []}, + {registered, [cowboy_clock, cowboy_sup]}, + {applications, [ + kernel, + stdlib + ]}, + {mod, {cowboy_app, []}}, + {env, []} +]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy.erl new file mode 100644 index 0000000..6defeea --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy.erl @@ -0,0 +1,85 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% +%% 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. + +%% @doc Cowboy API to start and stop listeners. +-module(cowboy). + +-export([start_listener/6, stop_listener/1, child_spec/6, accept_ack/1]). + +%% @doc Start a listener for the given transport and protocol. +%% +%% A listener is effectively a pool of NbAcceptors acceptors. +%% Acceptors accept connections on the given Transport and forward +%% requests to the given Protocol handler. Both transport and protocol +%% modules can be given options through the TransOpts and the +%% ProtoOpts arguments. Available options are documented in the +%% listen transport function and in the protocol module of your choice. +%% +%% All acceptor and request processes are supervised by the listener. +%% +%% It is recommended to set a large enough number of acceptors to improve +%% performance. The exact number depends of course on your hardware, on the +%% protocol used and on the number of expected simultaneous connections. +%% +%% The Transport option max_connections allows you to define +%% the maximum number of simultaneous connections for this listener. It defaults +%% to 1024. See cowboy_listener for more details on limiting the number +%% of connections. +%% +%% Although Cowboy includes a cowboy_http_protocol handler, other +%% handlers can be created for different protocols like IRC, FTP and more. +%% +%% Ref can be used to stop the listener later on. +-spec start_listener(any(), non_neg_integer(), module(), any(), module(), any()) + -> {ok, pid()}. +start_listener(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts) + when is_integer(NbAcceptors) andalso is_atom(Transport) + andalso is_atom(Protocol) -> + supervisor:start_child(cowboy_sup, child_spec(Ref, NbAcceptors, + Transport, TransOpts, Protocol, ProtoOpts)). + +%% @doc Stop a listener identified by Ref. +%% @todo Currently request processes aren't terminated with the listener. +-spec stop_listener(any()) -> ok | {error, not_found}. +stop_listener(Ref) -> + case supervisor:terminate_child(cowboy_sup, {cowboy_listener_sup, Ref}) of + ok -> + supervisor:delete_child(cowboy_sup, {cowboy_listener_sup, Ref}); + {error, Reason} -> + {error, Reason} + end. + +%% @doc Return a child spec suitable for embedding. +%% +%% When you want to embed cowboy in another application, you can use this +%% function to create a ChildSpec suitable for use in a supervisor. +%% The parameters are the same as in start_listener/6 but rather +%% than hooking the listener to the cowboy internal supervisor, it just returns +%% the spec. +-spec child_spec(any(), non_neg_integer(), module(), any(), module(), any()) + -> supervisor:child_spec(). +child_spec(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts) + when is_integer(NbAcceptors) andalso is_atom(Transport) + andalso is_atom(Protocol) -> + {{cowboy_listener_sup, Ref}, {cowboy_listener_sup, start_link, [ + NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts + ]}, permanent, 5000, supervisor, [cowboy_listener_sup]}. + +%% @doc Acknowledge the accepted connection. +%% +%% Effectively used to make sure the socket control has been given to +%% the protocol process before starting to use it. +-spec accept_ack(pid()) -> ok. +accept_ack(ListenerPid) -> + receive {shoot, ListenerPid} -> ok end. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_acceptor.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_acceptor.erl new file mode 100644 index 0000000..4cb9fa7 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_acceptor.erl @@ -0,0 +1,59 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% +%% 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. + +%% @private +-module(cowboy_acceptor). + +-export([start_link/7]). %% API. +-export([acceptor/7]). %% Internal. + +%% API. + +-spec start_link(inet:socket(), module(), module(), any(), + non_neg_integer(), pid(), pid()) -> {ok, pid()}. +start_link(LSocket, Transport, Protocol, Opts, + MaxConns, ListenerPid, ReqsSup) -> + Pid = spawn_link(?MODULE, acceptor, + [LSocket, Transport, Protocol, Opts, MaxConns, ListenerPid, ReqsSup]), + {ok, Pid}. + +%% Internal. + +-spec acceptor(inet:socket(), module(), module(), any(), + non_neg_integer(), pid(), pid()) -> no_return(). +acceptor(LSocket, Transport, Protocol, Opts, MaxConns, ListenerPid, ReqsSup) -> + case Transport:accept(LSocket, 2000) of + {ok, CSocket} -> + {ok, Pid} = supervisor:start_child(ReqsSup, + [ListenerPid, CSocket, Transport, Protocol, Opts]), + Transport:controlling_process(CSocket, Pid), + {ok, NbConns} = cowboy_listener:add_connection(ListenerPid, + default, Pid), + Pid ! {shoot, ListenerPid}, + limit_reqs(ListenerPid, NbConns, MaxConns); + {error, timeout} -> + ignore; + {error, _Reason} -> + %% @todo Probably do something here. If the socket was closed, + %% we may want to try and listen again on the port? + ignore + end, + ?MODULE:acceptor(LSocket, Transport, Protocol, Opts, + MaxConns, ListenerPid, ReqsSup). + +-spec limit_reqs(pid(), non_neg_integer(), non_neg_integer()) -> ok. +limit_reqs(_ListenerPid, NbConns, MaxConns) when NbConns =< MaxConns -> + ok; +limit_reqs(ListenerPid, _NbConns, MaxConns) -> + cowboy_listener:wait(ListenerPid, default, MaxConns). diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_acceptors_sup.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_acceptors_sup.erl new file mode 100644 index 0000000..17849a6 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_acceptors_sup.erl @@ -0,0 +1,43 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% +%% 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. + +%% @private +-module(cowboy_acceptors_sup). +-behaviour(supervisor). + +-export([start_link/7]). %% API. +-export([init/1]). %% supervisor. + +%% API. + +-spec start_link(non_neg_integer(), module(), any(), + module(), any(), pid(), pid()) -> {ok, pid()}. +start_link(NbAcceptors, Transport, TransOpts, + Protocol, ProtoOpts, ListenerPid, ReqsPid) -> + supervisor:start_link(?MODULE, [NbAcceptors, Transport, TransOpts, + Protocol, ProtoOpts, ListenerPid, ReqsPid]). + +%% supervisor. + +-spec init(list()) -> {ok, {{one_for_one, 10, 10}, list()}}. +init([NbAcceptors, Transport, TransOpts, + Protocol, ProtoOpts, ListenerPid, ReqsPid]) -> + {ok, LSocket} = Transport:listen(TransOpts), + MaxConns = proplists:get_value(max_connections, TransOpts, 1024), + Procs = [{{acceptor, self(), N}, {cowboy_acceptor, start_link, [ + LSocket, Transport, Protocol, ProtoOpts, + MaxConns, ListenerPid, ReqsPid + ]}, permanent, brutal_kill, worker, []} + || N <- lists:seq(1, NbAcceptors)], + {ok, {{one_for_one, 10, 10}, Procs}}. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_app.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_app.erl new file mode 100644 index 0000000..c7cefe4 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_app.erl @@ -0,0 +1,53 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% +%% 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. + +%% @private +-module(cowboy_app). +-behaviour(application). + +-export([start/2, stop/1, profile_output/0]). %% API. + +-type application_start_type() :: normal + | {takeover, node()} | {failover, node()}. + +%% API. + +-spec start(application_start_type(), any()) -> {ok, pid()}. +start(_Type, _Args) -> + consider_profiling(), + cowboy_sup:start_link(). + +-spec stop(any()) -> ok. +stop(_State) -> + ok. + +-spec profile_output() -> ok. +profile_output() -> + eprof:stop_profiling(), + eprof:log("procs.profile"), + eprof:analyze(procs), + eprof:log("total.profile"), + eprof:analyze(total). + +%% Internal. + +-spec consider_profiling() -> profiling | not_profiling. +consider_profiling() -> + case application:get_env(profile) of + {ok, true} -> + {ok, _Pid} = eprof:start(), + eprof:start_profiling([self()]); + _ -> + not_profiling + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_bstr.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_bstr.erl new file mode 100644 index 0000000..1c702ef --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_bstr.erl @@ -0,0 +1,86 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% +%% 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. + +%% @doc Binary string manipulation. +-module(cowboy_bstr). + +-export([to_lower/1]). %% Binary strings. +-export([char_to_lower/1, char_to_upper/1]). %% Characters. + +%% @doc Convert a binary string to lowercase. +-spec to_lower(binary()) -> binary(). +to_lower(L) -> + << << (char_to_lower(C)) >> || << C >> <= L >>. + +%% @doc Convert [A-Z] characters to lowercase. +%% @end +%% We gain noticeable speed by matching each value directly. +-spec char_to_lower(char()) -> char(). +char_to_lower($A) -> $a; +char_to_lower($B) -> $b; +char_to_lower($C) -> $c; +char_to_lower($D) -> $d; +char_to_lower($E) -> $e; +char_to_lower($F) -> $f; +char_to_lower($G) -> $g; +char_to_lower($H) -> $h; +char_to_lower($I) -> $i; +char_to_lower($J) -> $j; +char_to_lower($K) -> $k; +char_to_lower($L) -> $l; +char_to_lower($M) -> $m; +char_to_lower($N) -> $n; +char_to_lower($O) -> $o; +char_to_lower($P) -> $p; +char_to_lower($Q) -> $q; +char_to_lower($R) -> $r; +char_to_lower($S) -> $s; +char_to_lower($T) -> $t; +char_to_lower($U) -> $u; +char_to_lower($V) -> $v; +char_to_lower($W) -> $w; +char_to_lower($X) -> $x; +char_to_lower($Y) -> $y; +char_to_lower($Z) -> $z; +char_to_lower(Ch) -> Ch. + +%% @doc Convert [a-z] characters to uppercase. +-spec char_to_upper(char()) -> char(). +char_to_upper($a) -> $A; +char_to_upper($b) -> $B; +char_to_upper($c) -> $C; +char_to_upper($d) -> $D; +char_to_upper($e) -> $E; +char_to_upper($f) -> $F; +char_to_upper($g) -> $G; +char_to_upper($h) -> $H; +char_to_upper($i) -> $I; +char_to_upper($j) -> $J; +char_to_upper($k) -> $K; +char_to_upper($l) -> $L; +char_to_upper($m) -> $M; +char_to_upper($n) -> $N; +char_to_upper($o) -> $O; +char_to_upper($p) -> $P; +char_to_upper($q) -> $Q; +char_to_upper($r) -> $R; +char_to_upper($s) -> $S; +char_to_upper($t) -> $T; +char_to_upper($u) -> $U; +char_to_upper($v) -> $V; +char_to_upper($w) -> $W; +char_to_upper($x) -> $X; +char_to_upper($y) -> $Y; +char_to_upper($z) -> $Z; +char_to_upper(Ch) -> Ch. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_clock.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_clock.erl new file mode 100644 index 0000000..c699f4f --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_clock.erl @@ -0,0 +1,241 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% +%% 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. + +%% @doc Date and time related functions. +%% +%% While a gen_server process runs in the background to update +%% the cache of formatted dates every second, all API calls are +%% local and directly read from the ETS cache table, providing +%% fast time and date computations. +-module(cowboy_clock). +-behaviour(gen_server). + +-export([start_link/0, stop/0, rfc1123/0, rfc2109/1]). %% API. +-export([init/1, handle_call/3, handle_cast/2, + handle_info/2, terminate/2, code_change/3]). %% gen_server. + +-record(state, { + universaltime = undefined :: undefined | calendar:datetime(), + rfc1123 = <<>> :: binary(), + tref = undefined :: undefined | timer:tref() +}). + +-define(SERVER, ?MODULE). +-define(TABLE, ?MODULE). + +-include_lib("eunit/include/eunit.hrl"). + +%% API. + +%% @private +-spec start_link() -> {ok, pid()}. +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +%% @private +-spec stop() -> stopped. +stop() -> + gen_server:call(?SERVER, stop). + +%% @doc Return the current date and time formatted according to RFC-1123. +%% +%% This format is used in the 'Date' header sent with HTTP responses. +-spec rfc1123() -> binary(). +rfc1123() -> + ets:lookup_element(?TABLE, rfc1123, 2). + +%% @doc Return the current date and time formatted according to RFC-2109. +%% +%% This format is used in the 'Set-Cookie' header sent with +%% HTTP responses. +-spec rfc2109(calendar:datetime()) -> binary(). +rfc2109(LocalTime) -> + {{YYYY,MM,DD},{Hour,Min,Sec}} = + case calendar:local_time_to_universal_time_dst(LocalTime) of + [Gmt] -> Gmt; + [_,Gmt] -> Gmt + end, + Wday = calendar:day_of_the_week({YYYY,MM,DD}), + DayBin = pad_int(DD), + YearBin = list_to_binary(integer_to_list(YYYY)), + HourBin = pad_int(Hour), + MinBin = pad_int(Min), + SecBin = pad_int(Sec), + WeekDay = weekday(Wday), + Month = month(MM), + <>. + +%% gen_server. + +%% @private +-spec init([]) -> {ok, #state{}}. +init([]) -> + ?TABLE = ets:new(?TABLE, [set, protected, + named_table, {read_concurrency, true}]), + T = erlang:universaltime(), + B = update_rfc1123(<<>>, undefined, T), + {ok, TRef} = timer:send_interval(1000, update), + ets:insert(?TABLE, {rfc1123, B}), + {ok, #state{universaltime=T, rfc1123=B, tref=TRef}}. + +%% @private +-spec handle_call(_, _, State) + -> {reply, ignored, State} | {stop, normal, stopped, State}. +handle_call(stop, _From, State=#state{tref=TRef}) -> + {ok, cancel} = timer:cancel(TRef), + {stop, normal, stopped, State}; +handle_call(_Request, _From, State) -> + {reply, ignored, State}. + +%% @private +-spec handle_cast(_, State) -> {noreply, State}. +handle_cast(_Msg, State) -> + {noreply, State}. + +%% @private +-spec handle_info(_, State) -> {noreply, State}. +handle_info(update, #state{universaltime=Prev, rfc1123=B1, tref=TRef}) -> + T = erlang:universaltime(), + B2 = update_rfc1123(B1, Prev, T), + ets:insert(?TABLE, {rfc1123, B2}), + {noreply, #state{universaltime=T, rfc1123=B2, tref=TRef}}; +handle_info(_Info, State) -> + {noreply, State}. + +%% @private +-spec terminate(_, _) -> ok. +terminate(_Reason, _State) -> + ok. + +%% @private +-spec code_change(_, State, _) -> {ok, State}. +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%% Internal. + +-spec update_rfc1123(binary(), undefined | calendar:datetime(), + calendar:datetime()) -> binary(). +update_rfc1123(Bin, Now, Now) -> + Bin; +update_rfc1123(<< Keep:23/binary, _/bits >>, + {Date, {H, M, _}}, {Date, {H, M, S}}) -> + << Keep/binary, (pad_int(S))/binary, " GMT" >>; +update_rfc1123(<< Keep:20/binary, _/bits >>, + {Date, {H, _, _}}, {Date, {H, M, S}}) -> + << Keep/binary, (pad_int(M))/binary, $:, (pad_int(S))/binary, " GMT" >>; +update_rfc1123(<< Keep:17/binary, _/bits >>, {Date, _}, {Date, {H, M, S}}) -> + << Keep/binary, (pad_int(H))/binary, $:, (pad_int(M))/binary, + $:, (pad_int(S))/binary, " GMT" >>; +update_rfc1123(<< _:7/binary, Keep:10/binary, _/bits >>, + {{Y, Mo, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) -> + Wday = calendar:day_of_the_week(Date), + << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, Keep/binary, + (pad_int(H))/binary, $:, (pad_int(M))/binary, + $:, (pad_int(S))/binary, " GMT" >>; +update_rfc1123(<< _:11/binary, Keep:6/binary, _/bits >>, + {{Y, _, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) -> + Wday = calendar:day_of_the_week(Date), + << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ", + (month(Mo))/binary, Keep/binary, + (pad_int(H))/binary, $:, (pad_int(M))/binary, + $:, (pad_int(S))/binary, " GMT" >>; +update_rfc1123(_, _, {Date = {Y, Mo, D}, {H, M, S}}) -> + Wday = calendar:day_of_the_week(Date), + << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ", + (month(Mo))/binary, " ", (list_to_binary(integer_to_list(Y)))/binary, + " ", (pad_int(H))/binary, $:, (pad_int(M))/binary, + $:, (pad_int(S))/binary, " GMT" >>. + +%% Following suggestion by MononcQc on #erlounge. +-spec pad_int(0..59) -> binary(). +pad_int(X) when X < 10 -> + << $0, ($0 + X) >>; +pad_int(X) -> + list_to_binary(integer_to_list(X)). + +-spec weekday(1..7) -> <<_:24>>. +weekday(1) -> <<"Mon">>; +weekday(2) -> <<"Tue">>; +weekday(3) -> <<"Wed">>; +weekday(4) -> <<"Thu">>; +weekday(5) -> <<"Fri">>; +weekday(6) -> <<"Sat">>; +weekday(7) -> <<"Sun">>. + +-spec month(1..12) -> <<_:24>>. +month( 1) -> <<"Jan">>; +month( 2) -> <<"Feb">>; +month( 3) -> <<"Mar">>; +month( 4) -> <<"Apr">>; +month( 5) -> <<"May">>; +month( 6) -> <<"Jun">>; +month( 7) -> <<"Jul">>; +month( 8) -> <<"Aug">>; +month( 9) -> <<"Sep">>; +month(10) -> <<"Oct">>; +month(11) -> <<"Nov">>; +month(12) -> <<"Dec">>. + +%% Tests. + +-ifdef(TEST). + +update_rfc1123_test_() -> + Tests = [ + {<<"Sat, 14 May 2011 14:25:33 GMT">>, undefined, + {{2011, 5, 14}, {14, 25, 33}}, <<>>}, + {<<"Sat, 14 May 2011 14:25:33 GMT">>, {{2011, 5, 14}, {14, 25, 33}}, + {{2011, 5, 14}, {14, 25, 33}}, <<"Sat, 14 May 2011 14:25:33 GMT">>}, + {<<"Sat, 14 May 2011 14:25:34 GMT">>, {{2011, 5, 14}, {14, 25, 33}}, + {{2011, 5, 14}, {14, 25, 34}}, <<"Sat, 14 May 2011 14:25:33 GMT">>}, + {<<"Sat, 14 May 2011 14:26:00 GMT">>, {{2011, 5, 14}, {14, 25, 59}}, + {{2011, 5, 14}, {14, 26, 0}}, <<"Sat, 14 May 2011 14:25:59 GMT">>}, + {<<"Sat, 14 May 2011 15:00:00 GMT">>, {{2011, 5, 14}, {14, 59, 59}}, + {{2011, 5, 14}, {15, 0, 0}}, <<"Sat, 14 May 2011 14:59:59 GMT">>}, + {<<"Sun, 15 May 2011 00:00:00 GMT">>, {{2011, 5, 14}, {23, 59, 59}}, + {{2011, 5, 15}, { 0, 0, 0}}, <<"Sat, 14 May 2011 23:59:59 GMT">>}, + {<<"Wed, 01 Jun 2011 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}}, + {{2011, 6, 1}, { 0, 0, 0}}, <<"Tue, 31 May 2011 23:59:59 GMT">>}, + {<<"Sun, 01 Jan 2012 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}}, + {{2012, 1, 1}, { 0, 0, 0}}, <<"Sat, 31 Dec 2011 23:59:59 GMT">>} + ], + [{R, fun() -> R = update_rfc1123(B, P, N) end} || {R, P, N, B} <- Tests]. + +pad_int_test_() -> + Tests = [ + { 0, <<"00">>}, { 1, <<"01">>}, { 2, <<"02">>}, { 3, <<"03">>}, + { 4, <<"04">>}, { 5, <<"05">>}, { 6, <<"06">>}, { 7, <<"07">>}, + { 8, <<"08">>}, { 9, <<"09">>}, {10, <<"10">>}, {11, <<"11">>}, + {12, <<"12">>}, {13, <<"13">>}, {14, <<"14">>}, {15, <<"15">>}, + {16, <<"16">>}, {17, <<"17">>}, {18, <<"18">>}, {19, <<"19">>}, + {20, <<"20">>}, {21, <<"21">>}, {22, <<"22">>}, {23, <<"23">>}, + {24, <<"24">>}, {25, <<"25">>}, {26, <<"26">>}, {27, <<"27">>}, + {28, <<"28">>}, {29, <<"29">>}, {30, <<"30">>}, {31, <<"31">>}, + {32, <<"32">>}, {33, <<"33">>}, {34, <<"34">>}, {35, <<"35">>}, + {36, <<"36">>}, {37, <<"37">>}, {38, <<"38">>}, {39, <<"39">>}, + {40, <<"40">>}, {41, <<"41">>}, {42, <<"42">>}, {43, <<"43">>}, + {44, <<"44">>}, {45, <<"45">>}, {46, <<"46">>}, {47, <<"47">>}, + {48, <<"48">>}, {49, <<"49">>}, {50, <<"50">>}, {51, <<"51">>}, + {52, <<"52">>}, {53, <<"53">>}, {54, <<"54">>}, {55, <<"55">>}, + {56, <<"56">>}, {57, <<"57">>}, {58, <<"58">>}, {59, <<"59">>} + ], + [{I, fun() -> O = pad_int(I) end} || {I, O} <- Tests]. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_cookies.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_cookies.erl new file mode 100644 index 0000000..7f5ab60 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_cookies.erl @@ -0,0 +1,392 @@ +%% Copyright 2007 Mochi Media, Inc. +%% Copyright 2011 Thomas Burdick +%% +%% 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. + +%% @doc HTTP Cookie parsing and generating (RFC 2965). + +-module(cowboy_cookies). + +-export([parse_cookie/1, cookie/3, cookie/2]). %% API. + +%% Types. +-type kv() :: {Name::binary(), Value::binary()}. +-type kvlist() :: [kv()]. +-type cookie_option() :: {max_age, integer()} + | {local_time, calendar:datetime()} + | {domain, binary()} | {path, binary()} + | {secure, true | false} | {http_only, true | false}. +-export_type([kv/0, kvlist/0, cookie_option/0]). + +-define(QUOTE, $\"). + +-include_lib("eunit/include/eunit.hrl"). + +%% API. + +%% @doc Parse the contents of a Cookie header field, ignoring cookie +%% attributes, and return a simple property list. +-spec parse_cookie(binary()) -> kvlist(). +parse_cookie(<<>>) -> + []; +parse_cookie(Cookie) when is_binary(Cookie) -> + parse_cookie(Cookie, []). + +%% @equiv cookie(Key, Value, []) +-spec cookie(binary(), binary()) -> kv(). +cookie(Key, Value) when is_binary(Key) andalso is_binary(Value) -> + cookie(Key, Value, []). + +%% @doc Generate a Set-Cookie header field tuple. +-spec cookie(binary(), binary(), [cookie_option()]) -> kv(). +cookie(Key, Value, Options) when is_binary(Key) + andalso is_binary(Value) andalso is_list(Options) -> + Cookie = <<(any_to_binary(Key))/binary, "=", + (quote(Value))/binary, "; Version=1">>, + %% Set-Cookie: + %% Comment, Domain, Max-Age, Path, Secure, Version + ExpiresPart = + case proplists:get_value(max_age, Options) of + undefined -> + <<"">>; + RawAge -> + When = case proplists:get_value(local_time, Options) of + undefined -> + calendar:local_time(); + LocalTime -> + LocalTime + end, + Age = case RawAge < 0 of + true -> + 0; + false -> + RawAge + end, + AgeBinary = quote(Age), + CookieDate = age_to_cookie_date(Age, When), + <<"; Expires=", CookieDate/binary, + "; Max-Age=", AgeBinary/binary>> + end, + SecurePart = + case proplists:get_value(secure, Options) of + true -> + <<"; Secure">>; + _ -> + <<"">> + end, + DomainPart = + case proplists:get_value(domain, Options) of + undefined -> + <<"">>; + Domain -> + <<"; Domain=", (quote(Domain))/binary>> + end, + PathPart = + case proplists:get_value(path, Options) of + undefined -> + <<"">>; + Path -> + <<"; Path=", (quote(Path))/binary>> + end, + HttpOnlyPart = + case proplists:get_value(http_only, Options) of + true -> + <<"; HttpOnly">>; + _ -> + <<"">> + end, + CookieParts = <>, + {<<"Set-Cookie">>, CookieParts}. + +%% Internal. + +%% @doc Check if a character is a white space character. +is_whitespace($\s) -> true; +is_whitespace($\t) -> true; +is_whitespace($\r) -> true; +is_whitespace($\n) -> true; +is_whitespace(_) -> false. + +%% @doc Check if a character is a seperator. +is_separator(C) when C < 32 -> true; +is_separator($\s) -> true; +is_separator($\t) -> true; +is_separator($() -> true; +is_separator($)) -> true; +is_separator($<) -> true; +is_separator($>) -> true; +is_separator($@) -> true; +is_separator($,) -> true; +is_separator($;) -> true; +is_separator($:) -> true; +is_separator($\\) -> true; +is_separator(?QUOTE) -> true; +is_separator($/) -> true; +is_separator($[) -> true; +is_separator($]) -> true; +is_separator($?) -> true; +is_separator($=) -> true; +is_separator(${) -> true; +is_separator($}) -> true; +is_separator(_) -> false. + +%% @doc Check if a binary has an ASCII seperator character. +has_seperator(<<>>) -> + false; +has_seperator(<<$/, Rest/binary>>) -> + has_seperator(Rest); +has_seperator(<>) -> + case is_separator(C) of + true -> + true; + false -> + has_seperator(Rest) + end. + +%% @doc Convert to a binary and raise an error if quoting is required. Quoting +%% is broken in different ways for different browsers. Its better to simply +%% avoiding doing it at all. +%% @end +-spec quote(term()) -> binary(). +quote(V0) -> + V = any_to_binary(V0), + case has_seperator(V) of + true -> + erlang:error({cookie_quoting_required, V}); + false -> + V + end. + +-spec add_seconds(integer(), calendar:datetime()) -> calendar:datetime(). +add_seconds(Secs, LocalTime) -> + Greg = calendar:datetime_to_gregorian_seconds(LocalTime), + calendar:gregorian_seconds_to_datetime(Greg + Secs). + +-spec age_to_cookie_date(integer(), calendar:datetime()) -> binary(). +age_to_cookie_date(Age, LocalTime) -> + cowboy_clock:rfc2109(add_seconds(Age, LocalTime)). + +-spec parse_cookie(binary(), kvlist()) -> kvlist(). +parse_cookie(<<>>, Acc) -> + lists:reverse(Acc); +parse_cookie(String, Acc) -> + {{Token, Value}, Rest} = read_pair(String), + Acc1 = case Token of + <<"">> -> + Acc; + <<"$", _R/binary>> -> + Acc; + _ -> + [{Token, Value} | Acc] + end, + parse_cookie(Rest, Acc1). + +-spec read_pair(binary()) -> {{binary(), binary()}, binary()}. +read_pair(String) -> + {Token, Rest} = read_token(skip_whitespace(String)), + {Value, Rest1} = read_value(skip_whitespace(Rest)), + {{Token, Value}, skip_past_separator(Rest1)}. + +-spec read_value(binary()) -> {binary(), binary()}. +read_value(<<"=", Value/binary>>) -> + Value1 = skip_whitespace(Value), + case Value1 of + <> -> + read_quoted(Value1); + _ -> + read_token(Value1) + end; +read_value(String) -> + {<<"">>, String}. + +-spec read_quoted(binary()) -> {binary(), binary()}. +read_quoted(<>) -> + read_quoted(String, <<"">>). + +-spec read_quoted(binary(), binary()) -> {binary(), binary()}. +read_quoted(<<"">>, Acc) -> + {Acc, <<"">>}; +read_quoted(<>, Acc) -> + {Acc, Rest}; +read_quoted(<<$\\, Any, Rest/binary>>, Acc) -> + read_quoted(Rest, <>); +read_quoted(<>, Acc) -> + read_quoted(Rest, <>). + +%% @doc Drop characters while a function returns true. +binary_dropwhile(_F, <<"">>) -> + <<"">>; +binary_dropwhile(F, String) -> + <> = String, + case F(C) of + true -> + binary_dropwhile(F, Rest); + false -> + String + end. + +%% @doc Remove leading whitespace. +-spec skip_whitespace(binary()) -> binary(). +skip_whitespace(String) -> + binary_dropwhile(fun is_whitespace/1, String). + +%% @doc Split a binary when the current character causes F to return true. +binary_splitwith(_F, Head, <<>>) -> + {Head, <<>>}; +binary_splitwith(F, Head, Tail) -> + <> = Tail, + case F(C) of + true -> + {Head, Tail}; + false -> + binary_splitwith(F, <>, NTail) + end. + +%% @doc Split a binary with a function returning true or false on each char. +binary_splitwith(F, String) -> + binary_splitwith(F, <<>>, String). + +%% @doc Split the binary when the next seperator is found. +-spec read_token(binary()) -> {binary(), binary()}. +read_token(String) -> + binary_splitwith(fun is_separator/1, String). + +%% @doc Return string after ; or , characters. +-spec skip_past_separator(binary()) -> binary(). +skip_past_separator(<<"">>) -> + <<"">>; +skip_past_separator(<<";", Rest/binary>>) -> + Rest; +skip_past_separator(<<",", Rest/binary>>) -> + Rest; +skip_past_separator(<<_C, Rest/binary>>) -> + skip_past_separator(Rest). + +-spec any_to_binary(binary() | string() | atom() | integer()) -> binary(). +any_to_binary(V) when is_binary(V) -> + V; +any_to_binary(V) when is_list(V) -> + erlang:list_to_binary(V); +any_to_binary(V) when is_atom(V) -> + erlang:atom_to_binary(V, latin1); +any_to_binary(V) when is_integer(V) -> + list_to_binary(integer_to_list(V)). + +%% Tests. + +-ifdef(TEST). + +quote_test() -> + %% ?assertError eunit macro is not compatible with coverage module + _ = try quote(<<":wq">>) + catch error:{cookie_quoting_required, <<":wq">>} -> ok + end, + ?assertEqual(<<"foo">>,quote(foo)), + ok. + +parse_cookie_test() -> + %% RFC example + C1 = <<"$Version=\"1\"; Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\"; + Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\"; + Shipping=\"FedEx\"; $Path=\"/acme\"">>, + ?assertEqual( + [{<<"Customer">>,<<"WILE_E_COYOTE">>}, + {<<"Part_Number">>,<<"Rocket_Launcher_0001">>}, + {<<"Shipping">>,<<"FedEx">>}], + parse_cookie(C1)), + %% Potential edge cases + ?assertEqual( + [{<<"foo">>, <<"x">>}], + parse_cookie(<<"foo=\"\\x\"">>)), + ?assertEqual( + [], + parse_cookie(<<"=">>)), + ?assertEqual( + [{<<"foo">>, <<"">>}, {<<"bar">>, <<"">>}], + parse_cookie(<<" foo ; bar ">>)), + ?assertEqual( + [{<<"foo">>, <<"">>}, {<<"bar">>, <<"">>}], + parse_cookie(<<"foo=;bar=">>)), + ?assertEqual( + [{<<"foo">>, <<"\";">>}, {<<"bar">>, <<"">>}], + parse_cookie(<<"foo = \"\\\";\";bar ">>)), + ?assertEqual( + [{<<"foo">>, <<"\";bar">>}], + parse_cookie(<<"foo=\"\\\";bar">>)), + ?assertEqual( + [], + parse_cookie(<<"">>)), + ?assertEqual( + [{<<"foo">>, <<"bar">>}, {<<"baz">>, <<"wibble">>}], + parse_cookie(<<"foo=bar , baz=wibble ">>)), + ok. + +domain_test() -> + ?assertEqual( + {<<"Set-Cookie">>, + <<"Customer=WILE_E_COYOTE; " + "Version=1; " + "Domain=acme.com; " + "HttpOnly">>}, + cookie(<<"Customer">>, <<"WILE_E_COYOTE">>, + [{http_only, true}, {domain, <<"acme.com">>}])), + ok. + +local_time_test() -> + {<<"Set-Cookie">>, B} = cookie(<<"Customer">>, <<"WILE_E_COYOTE">>, + [{max_age, 111}, {secure, true}]), + + ?assertMatch( + [<<"Customer=WILE_E_COYOTE">>, + <<" Version=1">>, + <<" Expires=", _R/binary>>, + <<" Max-Age=111">>, + <<" Secure">>], + binary:split(B, <<";">>, [global])), + ok. + +-spec cookie_test() -> no_return(). %% Not actually true, just a bad option. +cookie_test() -> + C1 = {<<"Set-Cookie">>, + <<"Customer=WILE_E_COYOTE; " + "Version=1; " + "Path=/acme">>}, + C1 = cookie(<<"Customer">>, <<"WILE_E_COYOTE">>, [{path, <<"/acme">>}]), + + C1 = cookie(<<"Customer">>, <<"WILE_E_COYOTE">>, + [{path, <<"/acme">>}, {badoption, <<"negatory">>}]), + + {<<"Set-Cookie">>,<<"=NoKey; Version=1">>} + = cookie(<<"">>, <<"NoKey">>, []), + {<<"Set-Cookie">>,<<"=NoKey; Version=1">>} + = cookie(<<"">>, <<"NoKey">>), + LocalTime = calendar:universal_time_to_local_time( + {{2007, 5, 15}, {13, 45, 33}}), + C2 = {<<"Set-Cookie">>, + <<"Customer=WILE_E_COYOTE; " + "Version=1; " + "Expires=Tue, 15 May 2007 13:45:33 GMT; " + "Max-Age=0">>}, + C2 = cookie(<<"Customer">>, <<"WILE_E_COYOTE">>, + [{max_age, -111}, {local_time, LocalTime}]), + C3 = {<<"Set-Cookie">>, + <<"Customer=WILE_E_COYOTE; " + "Version=1; " + "Expires=Wed, 16 May 2007 13:45:50 GMT; " + "Max-Age=86417">>}, + C3 = cookie(<<"Customer">>, <<"WILE_E_COYOTE">>, + [{max_age, 86417}, {local_time, LocalTime}]), + ok. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_dispatcher.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_dispatcher.erl new file mode 100644 index 0000000..22f6e1e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_dispatcher.erl @@ -0,0 +1,309 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% Copyright (c) 2011, Anthony Ramine +%% +%% 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. + +%% @doc Dispatch requests according to a hostname and path. +-module(cowboy_dispatcher). + +-export([split_host/1, split_path/2, match/3]). %% API. + +-type bindings() :: list({atom(), binary()}). +-type tokens() :: list(binary()). +-type match_rule() :: '_' | '*' | list(binary() | '_' | '...' | atom()). +-type dispatch_path() :: list({match_rule(), module(), any()}). +-type dispatch_rule() :: {Host::match_rule(), Path::dispatch_path()}. +-type dispatch_rules() :: list(dispatch_rule()). + +-export_type([bindings/0, tokens/0, dispatch_rules/0]). + +-include_lib("eunit/include/eunit.hrl"). + +%% API. + +%% @doc Split a hostname into a list of tokens. +-spec split_host(binary()) + -> {tokens(), binary(), undefined | inet:ip_port()}. +split_host(<<>>) -> + {[], <<>>, undefined}; +split_host(Host) -> + case binary:split(Host, <<":">>) of + [Host] -> + {binary:split(Host, <<".">>, [global, trim]), Host, undefined}; + [Host2, Port] -> + {binary:split(Host2, <<".">>, [global, trim]), Host2, + list_to_integer(binary_to_list(Port))} + end. + +%% @doc Split a path into a list of path segments. +%% +%% Following RFC2396, this function may return path segments containing any +%% character, including / if, and only if, a / was escaped +%% and part of a path segment. +-spec split_path(binary(), fun((binary()) -> binary())) -> + {tokens(), binary(), binary()}. +split_path(Path, URLDec) -> + case binary:split(Path, <<"?">>) of + [Path] -> {do_split_path(Path, <<"/">>, URLDec), Path, <<>>}; + [<<>>, Qs] -> {[], <<>>, Qs}; + [Path2, Qs] -> {do_split_path(Path2, <<"/">>, URLDec), Path2, Qs} + end. + +-spec do_split_path(binary(), <<_:8>>, fun((binary()) -> binary())) -> tokens(). +do_split_path(RawPath, Separator, URLDec) -> + EncodedPath = case binary:split(RawPath, Separator, [global, trim]) of + [<<>>|Path] -> Path; + Path -> Path + end, + [URLDec(Token) || Token <- EncodedPath]. + +%% @doc Match hostname tokens and path tokens against dispatch rules. +%% +%% It is typically used for matching tokens for the hostname and path of +%% the request against a global dispatch rule for your listener. +%% +%% Dispatch rules are a list of {Hostname, PathRules} tuples, with +%% PathRules being a list of {Path, HandlerMod, HandlerOpts}. +%% +%% Hostname and Path are match rules and can be either the +%% atom '_', which matches everything for a single token, the atom +%% '*', which matches everything for the rest of the tokens, or a +%% list of tokens. Each token can be either a binary, the atom '_', +%% the atom '...' or a named atom. A binary token must match exactly, +%% '_' matches everything for a single token, '...' matches +%% everything for the rest of the tokens and a named atom will bind the +%% corresponding token value and return it. +%% +%% The list of hostname tokens is reversed before matching. For example, if +%% we were to match "www.dev-extend.eu", we would first match "eu", then +%% "dev-extend", then "www". This means that in the context of hostnames, +%% the '...' atom matches properly the lower levels of the domain +%% as would be expected. +%% +%% When a result is found, this function will return the handler module and +%% options found in the dispatch list, a key-value list of bindings and +%% the tokens that were matched by the '...' atom for both the +%% hostname and path. +-spec match(Host::tokens(), Path::tokens(), dispatch_rules()) + -> {ok, module(), any(), bindings(), + HostInfo::undefined | tokens(), + PathInfo::undefined | tokens()} + | {error, notfound, host} | {error, notfound, path}. +match(_Host, _Path, []) -> + {error, notfound, host}; +match(_Host, Path, [{'_', PathMatchs}|_Tail]) -> + match_path(Path, PathMatchs, [], undefined); +match(Host, Path, [{HostMatch, PathMatchs}|Tail]) -> + case try_match(host, Host, HostMatch) of + false -> + match(Host, Path, Tail); + {true, HostBinds, undefined} -> + match_path(Path, PathMatchs, HostBinds, undefined); + {true, HostBinds, HostInfo} -> + match_path(Path, PathMatchs, HostBinds, lists:reverse(HostInfo)) + end. + +-spec match_path(tokens(), dispatch_path(), bindings(), + HostInfo::undefined | tokens()) + -> {ok, module(), any(), bindings(), + HostInfo::undefined | tokens(), + PathInfo::undefined | tokens()} + | {error, notfound, path}. +match_path(_Path, [], _HostBinds, _HostInfo) -> + {error, notfound, path}; +match_path(_Path, [{'_', Handler, Opts}|_Tail], HostBinds, HostInfo) -> + {ok, Handler, Opts, HostBinds, HostInfo, undefined}; +match_path('*', [{'*', Handler, Opts}|_Tail], HostBinds, HostInfo) -> + {ok, Handler, Opts, HostBinds, HostInfo, undefined}; +match_path(Path, [{PathMatch, Handler, Opts}|Tail], HostBinds, HostInfo) -> + case try_match(path, Path, PathMatch) of + false -> + match_path(Path, Tail, HostBinds, HostInfo); + {true, PathBinds, PathInfo} -> + {ok, Handler, Opts, HostBinds ++ PathBinds, HostInfo, PathInfo} + end. + +%% Internal. + +-spec try_match(host | path, tokens(), match_rule()) + -> {true, bindings(), undefined | tokens()} | false. +try_match(host, List, Match) -> + list_match(lists:reverse(List), lists:reverse(Match), []); +try_match(path, List, Match) -> + list_match(List, Match, []). + +-spec list_match(tokens(), match_rule(), bindings()) + -> {true, bindings(), undefined | tokens()} | false. +%% Atom '...' matches any trailing path, stop right now. +list_match(List, ['...'], Binds) -> + {true, Binds, List}; +%% Atom '_' matches anything, continue. +list_match([_E|Tail], ['_'|TailMatch], Binds) -> + list_match(Tail, TailMatch, Binds); +%% Both values match, continue. +list_match([E|Tail], [E|TailMatch], Binds) -> + list_match(Tail, TailMatch, Binds); +%% Bind E to the variable name V and continue. +list_match([E|Tail], [V|TailMatch], Binds) when is_atom(V) -> + list_match(Tail, TailMatch, [{V, E}|Binds]); +%% Match complete. +list_match([], [], Binds) -> + {true, Binds, undefined}; +%% Values don't match, stop. +list_match(_List, _Match, _Binds) -> + false. + +%% Tests. + +-ifdef(TEST). + +split_host_test_() -> + %% {Host, Result} + Tests = [ + {<<"">>, {[], <<"">>, undefined}}, + {<<".........">>, {[], <<".........">>, undefined}}, + {<<"*">>, {[<<"*">>], <<"*">>, undefined}}, + {<<"cowboy.dev-extend.eu">>, + {[<<"cowboy">>, <<"dev-extend">>, <<"eu">>], + <<"cowboy.dev-extend.eu">>, undefined}}, + {<<"dev-extend..eu">>, + {[<<"dev-extend">>, <<>>, <<"eu">>], + <<"dev-extend..eu">>, undefined}}, + {<<"dev-extend.eu">>, + {[<<"dev-extend">>, <<"eu">>], <<"dev-extend.eu">>, undefined}}, + {<<"dev-extend.eu:8080">>, + {[<<"dev-extend">>, <<"eu">>], <<"dev-extend.eu">>, 8080}}, + {<<"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">>, + {[<<"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">>], + <<"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">>, + undefined}} + ], + [{H, fun() -> R = split_host(H) end} || {H, R} <- Tests]. + +split_host_fail_test_() -> + Tests = [ + <<"dev-extend.eu:owns">>, + <<"dev-extend.eu: owns">>, + <<"dev-extend.eu:42fun">>, + <<"dev-extend.eu: 42fun">>, + <<"dev-extend.eu:42 fun">>, + <<"dev-extend.eu:fun 42">>, + <<"dev-extend.eu: 42">>, + <<":owns">>, + <<":42 fun">> + ], + [{H, fun() -> case catch split_host(H) of + {'EXIT', _Reason} -> ok + end end} || H <- Tests]. + +split_path_test_() -> + %% {Path, Result, QueryString} + Tests = [ + {<<"?">>, [], <<"">>, <<"">>}, + {<<"???">>, [], <<"">>, <<"??">>}, + {<<"/">>, [], <<"/">>, <<"">>}, + {<<"/users">>, [<<"users">>], <<"/users">>, <<"">>}, + {<<"/users?">>, [<<"users">>], <<"/users">>, <<"">>}, + {<<"/users?a">>, [<<"users">>], <<"/users">>, <<"a">>}, + {<<"/users/42/friends?a=b&c=d&e=notsure?whatever">>, + [<<"users">>, <<"42">>, <<"friends">>], + <<"/users/42/friends">>, <<"a=b&c=d&e=notsure?whatever">>}, + {<<"/users/a+b/c%21d?e+f=g+h">>, + [<<"users">>, <<"a b">>, <<"c!d">>], + <<"/users/a+b/c%21d">>, <<"e+f=g+h">>} + ], + URLDecode = fun(Bin) -> cowboy_http:urldecode(Bin, crash) end, + [{P, fun() -> {R, RawP, Qs} = split_path(P, URLDecode) end} + || {P, R, RawP, Qs} <- Tests]. + +match_test_() -> + Dispatch = [ + {[<<"www">>, '_', <<"dev-extend">>, <<"eu">>], [ + {[<<"users">>, '_', <<"mails">>], match_any_subdomain_users, []} + ]}, + {[<<"dev-extend">>, <<"eu">>], [ + {[<<"users">>, id, <<"friends">>], match_extend_users_friends, []}, + {'_', match_extend, []} + ]}, + {[<<"dev-extend">>, var], [ + {[<<"threads">>, var], match_duplicate_vars, + [we, {expect, two}, var, here]} + ]}, + {[<<"erlang">>, ext], [ + {'_', match_erlang_ext, []} + ]}, + {'_', [ + {[<<"users">>, id, <<"friends">>], match_users_friends, []}, + {'_', match_any, []} + ]} + ], + %% {Host, Path, Result} + Tests = [ + {[<<"any">>], [], {ok, match_any, [], []}}, + {[<<"www">>, <<"any">>, <<"dev-extend">>, <<"eu">>], + [<<"users">>, <<"42">>, <<"mails">>], + {ok, match_any_subdomain_users, [], []}}, + {[<<"www">>, <<"dev-extend">>, <<"eu">>], + [<<"users">>, <<"42">>, <<"mails">>], {ok, match_any, [], []}}, + {[<<"www">>, <<"dev-extend">>, <<"eu">>], [], {ok, match_any, [], []}}, + {[<<"www">>, <<"any">>, <<"dev-extend">>, <<"eu">>], + [<<"not_users">>, <<"42">>, <<"mails">>], {error, notfound, path}}, + {[<<"dev-extend">>, <<"eu">>], [], {ok, match_extend, [], []}}, + {[<<"dev-extend">>, <<"eu">>], [<<"users">>, <<"42">>, <<"friends">>], + {ok, match_extend_users_friends, [], [{id, <<"42">>}]}}, + {[<<"erlang">>, <<"fr">>], '_', + {ok, match_erlang_ext, [], [{ext, <<"fr">>}]}}, + {[<<"any">>], [<<"users">>, <<"444">>, <<"friends">>], + {ok, match_users_friends, [], [{id, <<"444">>}]}}, + {[<<"dev-extend">>, <<"fr">>], [<<"threads">>, <<"987">>], + {ok, match_duplicate_vars, [we, {expect, two}, var, here], + [{var, <<"fr">>}, {var, <<"987">>}]}} + ], + [{lists:flatten(io_lib:format("~p, ~p", [H, P])), fun() -> + {ok, Handler, Opts, Binds, undefined, undefined} = match(H, P, Dispatch) + end} || {H, P, {ok, Handler, Opts, Binds}} <- Tests]. + +match_info_test_() -> + Dispatch = [ + {[<<"www">>, <<"dev-extend">>, <<"eu">>], [ + {[<<"pathinfo">>, <<"is">>, <<"next">>, '...'], match_path, []} + ]}, + {['...', <<"dev-extend">>, <<"eu">>], [ + {'_', match_any, []} + ]} + ], + Tests = [ + {[<<"dev-extend">>, <<"eu">>], [], + {ok, match_any, [], [], [], undefined}}, + {[<<"bugs">>, <<"dev-extend">>, <<"eu">>], [], + {ok, match_any, [], [], [<<"bugs">>], undefined}}, + {[<<"cowboy">>, <<"bugs">>, <<"dev-extend">>, <<"eu">>], [], + {ok, match_any, [], [], [<<"cowboy">>, <<"bugs">>], undefined}}, + {[<<"www">>, <<"dev-extend">>, <<"eu">>], + [<<"pathinfo">>, <<"is">>, <<"next">>], + {ok, match_path, [], [], undefined, []}}, + {[<<"www">>, <<"dev-extend">>, <<"eu">>], + [<<"pathinfo">>, <<"is">>, <<"next">>, <<"path_info">>], + {ok, match_path, [], [], undefined, [<<"path_info">>]}}, + {[<<"www">>, <<"dev-extend">>, <<"eu">>], + [<<"pathinfo">>, <<"is">>, <<"next">>, <<"foo">>, <<"bar">>], + {ok, match_path, [], [], undefined, [<<"foo">>, <<"bar">>]}} + ], + [{lists:flatten(io_lib:format("~p, ~p", [H, P])), fun() -> + R = match(H, P, Dispatch) + end} || {H, P, R} <- Tests]. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http.erl new file mode 100644 index 0000000..d7261c8 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http.erl @@ -0,0 +1,974 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% Copyright (c) 2011, Anthony Ramine +%% +%% 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. + +%% @doc Core HTTP parsing API. +-module(cowboy_http). + +%% Parsing. +-export([list/2, nonempty_list/2, content_type/1, content_type_params/3, + media_range/2, conneg/2, language_range/2, entity_tag_match/1, + http_date/1, rfc1123_date/1, rfc850_date/1, asctime_date/1, + whitespace/2, digits/1, token/2, token_ci/2, quoted_string/2]). + +%% Interpretation. +-export([connection_to_atom/1, urldecode/1, urldecode/2, urlencode/1, + urlencode/2]). + +-type method() :: 'OPTIONS' | 'GET' | 'HEAD' + | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | binary(). +-type uri() :: '*' | {absoluteURI, http | https, Host::binary(), + Port::integer() | undefined, Path::binary()} + | {scheme, Scheme::binary(), binary()} + | {abs_path, binary()} | binary(). +-type version() :: {Major::non_neg_integer(), Minor::non_neg_integer()}. +-type header() :: 'Cache-Control' | 'Connection' | 'Date' | 'Pragma' + | 'Transfer-Encoding' | 'Upgrade' | 'Via' | 'Accept' | 'Accept-Charset' + | 'Accept-Encoding' | 'Accept-Language' | 'Authorization' | 'From' | 'Host' + | 'If-Modified-Since' | 'If-Match' | 'If-None-Match' | 'If-Range' + | 'If-Unmodified-Since' | 'Max-Forwards' | 'Proxy-Authorization' | 'Range' + | 'Referer' | 'User-Agent' | 'Age' | 'Location' | 'Proxy-Authenticate' + | 'Public' | 'Retry-After' | 'Server' | 'Vary' | 'Warning' + | 'Www-Authenticate' | 'Allow' | 'Content-Base' | 'Content-Encoding' + | 'Content-Language' | 'Content-Length' | 'Content-Location' + | 'Content-Md5' | 'Content-Range' | 'Content-Type' | 'Etag' + | 'Expires' | 'Last-Modified' | 'Accept-Ranges' | 'Set-Cookie' + | 'Set-Cookie2' | 'X-Forwarded-For' | 'Cookie' | 'Keep-Alive' + | 'Proxy-Connection' | binary(). +-type fake_iodata() :: iolist() | binary(). +-type headers() :: [{header(), fake_iodata()}]. +-type status() :: non_neg_integer() | binary(). + +-export_type([method/0, uri/0, version/0, header/0, headers/0, status/0]). + +-include("include/http.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +%% Parsing. + +%% @doc Parse a non-empty list of the given type. +-spec nonempty_list(binary(), fun()) -> [any(), ...] | {error, badarg}. +nonempty_list(Data, Fun) -> + case list(Data, Fun, []) of + {error, badarg} -> {error, badarg}; + [] -> {error, badarg}; + L -> lists:reverse(L) + end. + +%% @doc Parse a list of the given type. +-spec list(binary(), fun()) -> list() | {error, badarg}. +list(Data, Fun) -> + case list(Data, Fun, []) of + {error, badarg} -> {error, badarg}; + L -> lists:reverse(L) + end. + +-spec list(binary(), fun(), [binary()]) -> [any()] | {error, badarg}. +%% From the RFC: +%%
Wherever this construct is used, null elements are allowed, +%% but do not contribute to the count of elements present. +%% That is, "(element), , (element) " is permitted, but counts +%% as only two elements. Therefore, where at least one element is required, +%% at least one non-null element MUST be present.
+list(Data, Fun, Acc) -> + whitespace(Data, + fun (<<>>) -> Acc; + (<< $,, Rest/binary >>) -> list(Rest, Fun, Acc); + (Rest) -> Fun(Rest, + fun (D, I) -> whitespace(D, + fun (<<>>) -> [I|Acc]; + (<< $,, R/binary >>) -> list(R, Fun, [I|Acc]); + (_Any) -> {error, badarg} + end) + end) + end). + +%% @doc Parse a content type. +-spec content_type(binary()) -> any(). +content_type(Data) -> + media_type(Data, + fun (Rest, Type, SubType) -> + content_type_params(Rest, + fun (Params) -> {Type, SubType, Params} end, []) + end). + +-spec content_type_params(binary(), fun(), list({binary(), binary()})) + -> any(). +content_type_params(Data, Fun, Acc) -> + whitespace(Data, + fun (<< $;, Rest/binary >>) -> content_type_param(Rest, Fun, Acc); + (<<>>) -> Fun(lists:reverse(Acc)); + (_Rest) -> {error, badarg} + end). + +-spec content_type_param(binary(), fun(), list({binary(), binary()})) + -> any(). +content_type_param(Data, Fun, Acc) -> + whitespace(Data, + fun (Rest) -> + token_ci(Rest, + fun (_Rest2, <<>>) -> {error, badarg}; + (<< $=, Rest2/binary >>, Attr) -> + word(Rest2, + fun (Rest3, Value) -> + content_type_params(Rest3, Fun, + [{Attr, Value}|Acc]) + end); + (_Rest2, _Attr) -> {error, badarg} + end) + end). + +%% @doc Parse a media range. +-spec media_range(binary(), fun()) -> any(). +media_range(Data, Fun) -> + media_type(Data, + fun (Rest, Type, SubType) -> + media_range_params(Rest, Fun, Type, SubType, []) + end). + +-spec media_range_params(binary(), fun(), binary(), binary(), + [{binary(), binary()}]) -> any(). +media_range_params(Data, Fun, Type, SubType, Acc) -> + whitespace(Data, + fun (<< $;, Rest/binary >>) -> + whitespace(Rest, + fun (Rest2) -> + media_range_param_attr(Rest2, Fun, Type, SubType, Acc) + end); + (Rest) -> Fun(Rest, {{Type, SubType, lists:reverse(Acc)}, 1000, []}) + end). + +-spec media_range_param_attr(binary(), fun(), binary(), binary(), + [{binary(), binary()}]) -> any(). +media_range_param_attr(Data, Fun, Type, SubType, Acc) -> + token_ci(Data, + fun (_Rest, <<>>) -> {error, badarg}; + (<< $=, Rest/binary >>, Attr) -> + media_range_param_value(Rest, Fun, Type, SubType, Acc, Attr) + end). + +-spec media_range_param_value(binary(), fun(), binary(), binary(), + [{binary(), binary()}], binary()) -> any(). +media_range_param_value(Data, Fun, Type, SubType, Acc, <<"q">>) -> + qvalue(Data, + fun (Rest, Quality) -> + accept_ext(Rest, Fun, Type, SubType, Acc, Quality, []) + end); +media_range_param_value(Data, Fun, Type, SubType, Acc, Attr) -> + word(Data, + fun (Rest, Value) -> + media_range_params(Rest, Fun, + Type, SubType, [{Attr, Value}|Acc]) + end). + +%% @doc Parse a media type. +-spec media_type(binary(), fun()) -> any(). +media_type(Data, Fun) -> + token_ci(Data, + fun (_Rest, <<>>) -> {error, badarg}; + (<< $/, Rest/binary >>, Type) -> + token_ci(Rest, + fun (_Rest2, <<>>) -> {error, badarg}; + (Rest2, SubType) -> Fun(Rest2, Type, SubType) + end); + (_Rest, _Type) -> {error, badarg} + end). + +-spec accept_ext(binary(), fun(), binary(), binary(), + [{binary(), binary()}], 0..1000, + [{binary(), binary()} | binary()]) -> any(). +accept_ext(Data, Fun, Type, SubType, Params, Quality, Acc) -> + whitespace(Data, + fun (<< $;, Rest/binary >>) -> + whitespace(Rest, + fun (Rest2) -> + accept_ext_attr(Rest2, Fun, + Type, SubType, Params, Quality, Acc) + end); + (Rest) -> + Fun(Rest, {{Type, SubType, lists:reverse(Params)}, + Quality, lists:reverse(Acc)}) + end). + +-spec accept_ext_attr(binary(), fun(), binary(), binary(), + [{binary(), binary()}], 0..1000, + [{binary(), binary()} | binary()]) -> any(). +accept_ext_attr(Data, Fun, Type, SubType, Params, Quality, Acc) -> + token_ci(Data, + fun (_Rest, <<>>) -> {error, badarg}; + (<< $=, Rest/binary >>, Attr) -> + accept_ext_value(Rest, Fun, Type, SubType, Params, + Quality, Acc, Attr); + (Rest, Attr) -> + accept_ext(Rest, Fun, Type, SubType, Params, + Quality, [Attr|Acc]) + end). + +-spec accept_ext_value(binary(), fun(), binary(), binary(), + [{binary(), binary()}], 0..1000, + [{binary(), binary()} | binary()], binary()) -> any(). +accept_ext_value(Data, Fun, Type, SubType, Params, Quality, Acc, Attr) -> + word(Data, + fun (Rest, Value) -> + accept_ext(Rest, Fun, + Type, SubType, Params, Quality, [{Attr, Value}|Acc]) + end). + +%% @doc Parse a conneg header (Accept-Charset, Accept-Encoding), +%% followed by an optional quality value. +-spec conneg(binary(), fun()) -> any(). +conneg(Data, Fun) -> + token_ci(Data, + fun (_Rest, <<>>) -> {error, badarg}; + (Rest, Conneg) -> + maybe_qparam(Rest, + fun (Rest2, Quality) -> + Fun(Rest2, {Conneg, Quality}) + end) + end). + +%% @doc Parse a language range, followed by an optional quality value. +-spec language_range(binary(), fun()) -> any(). +language_range(<< $*, Rest/binary >>, Fun) -> + language_range_ret(Rest, Fun, '*'); +language_range(Data, Fun) -> + language_tag(Data, + fun (Rest, LanguageTag) -> + language_range_ret(Rest, Fun, LanguageTag) + end). + +-spec language_range_ret(binary(), fun(), '*' | {binary(), [binary()]}) -> any(). +language_range_ret(Data, Fun, LanguageTag) -> + maybe_qparam(Data, + fun (Rest, Quality) -> + Fun(Rest, {LanguageTag, Quality}) + end). + +-spec language_tag(binary(), fun()) -> any(). +language_tag(Data, Fun) -> + alpha(Data, + fun (_Rest, Tag) when byte_size(Tag) =:= 0; byte_size(Tag) > 8 -> + {error, badarg}; + (<< $-, Rest/binary >>, Tag) -> + language_subtag(Rest, Fun, Tag, []); + (Rest, Tag) -> + Fun(Rest, Tag) + end). + +-spec language_subtag(binary(), fun(), binary(), [binary()]) -> any(). +language_subtag(Data, Fun, Tag, Acc) -> + alpha(Data, + fun (_Rest, SubTag) when byte_size(SubTag) =:= 0; + byte_size(SubTag) > 8 -> {error, badarg}; + (<< $-, Rest/binary >>, SubTag) -> + language_subtag(Rest, Fun, Tag, [SubTag|Acc]); + (Rest, SubTag) -> + %% Rebuild the full tag now that we know it's correct + Sub = << << $-, S/binary >> || S <- lists:reverse([SubTag|Acc]) >>, + Fun(Rest, << Tag/binary, Sub/binary >>) + end). + +-spec maybe_qparam(binary(), fun()) -> any(). +maybe_qparam(Data, Fun) -> + whitespace(Data, + fun (<< $;, Rest/binary >>) -> + whitespace(Rest, + fun (Rest2) -> + qparam(Rest2, Fun) + end); + (Rest) -> + Fun(Rest, 1000) + end). + +%% @doc Parse a quality parameter string (for example q=0.500). +-spec qparam(binary(), fun()) -> any(). +qparam(<< Q, $=, Data/binary >>, Fun) when Q =:= $q; Q =:= $Q -> + qvalue(Data, Fun). + +%% @doc Parse either a list of entity tags or a "*". +-spec entity_tag_match(binary()) -> any(). +entity_tag_match(<< $*, Rest/binary >>) -> + whitespace(Rest, + fun (<<>>) -> '*'; + (_Any) -> {error, badarg} + end); +entity_tag_match(Data) -> + nonempty_list(Data, fun entity_tag/2). + +%% @doc Parse an entity-tag. +-spec entity_tag(binary(), fun()) -> any(). +entity_tag(<< "W/", Rest/binary >>, Fun) -> + opaque_tag(Rest, Fun, weak); +entity_tag(Data, Fun) -> + opaque_tag(Data, Fun, strong). + +-spec opaque_tag(binary(), fun(), weak | strong) -> any(). +opaque_tag(Data, Fun, Strength) -> + quoted_string(Data, + fun (_Rest, <<>>) -> {error, badarg}; + (Rest, OpaqueTag) -> Fun(Rest, {Strength, OpaqueTag}) + end). + +%% @doc Parse an HTTP date (RFC1123, RFC850 or asctime date). +%% @end +%% +%% While this may not be the most efficient date parsing we can do, +%% it should work fine for our purposes because all HTTP dates should +%% be sent as RFC1123 dates in HTTP/1.1. +-spec http_date(binary()) -> any(). +http_date(Data) -> + case rfc1123_date(Data) of + {error, badarg} -> + case rfc850_date(Data) of + {error, badarg} -> + case asctime_date(Data) of + {error, badarg} -> + {error, badarg}; + HTTPDate -> + HTTPDate + end; + HTTPDate -> + HTTPDate + end; + HTTPDate -> + HTTPDate + end. + +%% @doc Parse an RFC1123 date. +-spec rfc1123_date(binary()) -> any(). +rfc1123_date(Data) -> + wkday(Data, + fun (<< ", ", Rest/binary >>, _WkDay) -> + date1(Rest, + fun (<< " ", Rest2/binary >>, Date) -> + time(Rest2, + fun (<< " GMT", Rest3/binary >>, Time) -> + http_date_ret(Rest3, {Date, Time}); + (_Any, _Time) -> + {error, badarg} + end); + (_Any, _Date) -> + {error, badarg} + end); + (_Any, _WkDay) -> + {error, badarg} + end). + +%% @doc Parse an RFC850 date. +-spec rfc850_date(binary()) -> any(). +%% From the RFC: +%% HTTP/1.1 clients and caches SHOULD assume that an RFC-850 date +%% which appears to be more than 50 years in the future is in fact +%% in the past (this helps solve the "year 2000" problem). +rfc850_date(Data) -> + weekday(Data, + fun (<< ", ", Rest/binary >>, _WeekDay) -> + date2(Rest, + fun (<< " ", Rest2/binary >>, Date) -> + time(Rest2, + fun (<< " GMT", Rest3/binary >>, Time) -> + http_date_ret(Rest3, {Date, Time}); + (_Any, _Time) -> + {error, badarg} + end); + (_Any, _Date) -> + {error, badarg} + end); + (_Any, _WeekDay) -> + {error, badarg} + end). + +%% @doc Parse an asctime date. +-spec asctime_date(binary()) -> any(). +asctime_date(Data) -> + wkday(Data, + fun (<< " ", Rest/binary >>, _WkDay) -> + date3(Rest, + fun (<< " ", Rest2/binary >>, PartialDate) -> + time(Rest2, + fun (<< " ", Rest3/binary >>, Time) -> + asctime_year(Rest3, + PartialDate, Time); + (_Any, _Time) -> + {error, badarg} + end); + (_Any, _PartialDate) -> + {error, badarg} + end); + (_Any, _WkDay) -> + {error, badarg1} + end). + +-spec asctime_year(binary(), tuple(), tuple()) -> any(). +asctime_year(<< Y1, Y2, Y3, Y4, Rest/binary >>, {Month, Day}, Time) + when Y1 >= $0, Y1 =< $9, Y2 >= $0, Y2 =< $9, + Y3 >= $0, Y3 =< $9, Y4 >= $0, Y4 =< $9 -> + Year = (Y1 - $0) * 1000 + (Y2 - $0) * 100 + (Y3 - $0) * 10 + (Y4 - $0), + http_date_ret(Rest, {{Year, Month, Day}, Time}). + +-spec http_date_ret(binary(), tuple()) -> any(). +http_date_ret(Data, DateTime = {Date, _Time}) -> + whitespace(Data, + fun (<<>>) -> + case calendar:valid_date(Date) of + true -> DateTime; + false -> {error, badarg} + end; + (_Any) -> + {error, badarg} + end). + +%% We never use it, pretty much just checks the wkday is right. +-spec wkday(binary(), fun()) -> any(). +wkday(<< WkDay:3/binary, Rest/binary >>, Fun) + when WkDay =:= <<"Mon">>; WkDay =:= <<"Tue">>; WkDay =:= <<"Wed">>; + WkDay =:= <<"Thu">>; WkDay =:= <<"Fri">>; WkDay =:= <<"Sat">>; + WkDay =:= <<"Sun">> -> + Fun(Rest, WkDay); +wkday(_Any, _Fun) -> + {error, badarg}. + +%% We never use it, pretty much just checks the weekday is right. +-spec weekday(binary(), fun()) -> any(). +weekday(<< "Monday", Rest/binary >>, Fun) -> + Fun(Rest, <<"Monday">>); +weekday(<< "Tuesday", Rest/binary >>, Fun) -> + Fun(Rest, <<"Tuesday">>); +weekday(<< "Wednesday", Rest/binary >>, Fun) -> + Fun(Rest, <<"Wednesday">>); +weekday(<< "Thursday", Rest/binary >>, Fun) -> + Fun(Rest, <<"Thursday">>); +weekday(<< "Friday", Rest/binary >>, Fun) -> + Fun(Rest, <<"Friday">>); +weekday(<< "Saturday", Rest/binary >>, Fun) -> + Fun(Rest, <<"Saturday">>); +weekday(<< "Sunday", Rest/binary >>, Fun) -> + Fun(Rest, <<"Sunday">>); +weekday(_Any, _Fun) -> + {error, badarg}. + +-spec date1(binary(), fun()) -> any(). +date1(<< D1, D2, " ", M:3/binary, " ", Y1, Y2, Y3, Y4, Rest/binary >>, Fun) + when D1 >= $0, D1 =< $9, D2 >= $0, D2 =< $9, + Y1 >= $0, Y1 =< $9, Y2 >= $0, Y2 =< $9, + Y3 >= $0, Y3 =< $9, Y4 >= $0, Y4 =< $9 -> + case month(M) of + {error, badarg} -> + {error, badarg}; + Month -> + Fun(Rest, { + (Y1 - $0) * 1000 + (Y2 - $0) * 100 + (Y3 - $0) * 10 + (Y4 - $0), + Month, + (D1 - $0) * 10 + (D2 - $0) + }) + end; +date1(_Data, _Fun) -> + {error, badarg}. + +-spec date2(binary(), fun()) -> any(). +date2(<< D1, D2, "-", M:3/binary, "-", Y1, Y2, Rest/binary >>, Fun) + when D1 >= $0, D1 =< $9, D2 >= $0, D2 =< $9, + Y1 >= $0, Y1 =< $9, Y2 >= $0, Y2 =< $9 -> + case month(M) of + {error, badarg} -> + {error, badarg}; + Month -> + Year = (Y1 - $0) * 10 + (Y2 - $0), + Year2 = case Year > 50 of + true -> Year + 1900; + false -> Year + 2000 + end, + Fun(Rest, { + Year2, + Month, + (D1 - $0) * 10 + (D2 - $0) + }) + end; +date2(_Data, _Fun) -> + {error, badarg}. + +-spec date3(binary(), fun()) -> any(). +date3(<< M:3/binary, " ", D1, D2, Rest/binary >>, Fun) + when (D1 >= $0 andalso D1 =< $3) orelse D1 =:= $\s, + D2 >= $0, D2 =< $9 -> + case month(M) of + {error, badarg} -> + {error, badarg}; + Month -> + Day = case D1 of + $\s -> D2 - $0; + D1 -> (D1 - $0) * 10 + (D2 - $0) + end, + Fun(Rest, {Month, Day}) + end; +date3(_Data, _Fun) -> + {error, badarg}. + +-spec month(<< _:24 >>) -> 1..12 | {error, badarg}. +month(<<"Jan">>) -> 1; +month(<<"Feb">>) -> 2; +month(<<"Mar">>) -> 3; +month(<<"Apr">>) -> 4; +month(<<"May">>) -> 5; +month(<<"Jun">>) -> 6; +month(<<"Jul">>) -> 7; +month(<<"Aug">>) -> 8; +month(<<"Sep">>) -> 9; +month(<<"Oct">>) -> 10; +month(<<"Nov">>) -> 11; +month(<<"Dec">>) -> 12; +month(_Any) -> {error, badarg}. + +-spec time(binary(), fun()) -> any(). +time(<< H1, H2, ":", M1, M2, ":", S1, S2, Rest/binary >>, Fun) + when H1 >= $0, H1 =< $2, H2 >= $0, H2 =< $9, + M1 >= $0, M1 =< $5, M2 >= $0, M2 =< $9, + S1 >= $0, S1 =< $5, S2 >= $0, S2 =< $9 -> + Hour = (H1 - $0) * 10 + (H2 - $0), + case Hour < 24 of + true -> + Time = { + Hour, + (M1 - $0) * 10 + (M2 - $0), + (S1 - $0) * 10 + (S2 - $0) + }, + Fun(Rest, Time); + false -> + {error, badarg} + end. + +%% @doc Skip whitespace. +-spec whitespace(binary(), fun()) -> any(). +whitespace(<< C, Rest/binary >>, Fun) + when C =:= $\s; C =:= $\t -> + whitespace(Rest, Fun); +whitespace(Data, Fun) -> + Fun(Data). + +%% @doc Parse a list of digits as a non negative integer. +-spec digits(binary()) -> non_neg_integer() | {error, badarg}. +digits(Data) -> + digits(Data, + fun (Rest, I) -> + whitespace(Rest, + fun (<<>>) -> + I; + (_Rest2) -> + {error, badarg} + end) + end). + +-spec digits(binary(), fun()) -> any(). +digits(<< C, Rest/binary >>, Fun) + when C >= $0, C =< $9 -> + digits(Rest, Fun, C - $0); +digits(_Data, _Fun) -> + {error, badarg}. + +-spec digits(binary(), fun(), non_neg_integer()) -> any(). +digits(<< C, Rest/binary >>, Fun, Acc) + when C >= $0, C =< $9 -> + digits(Rest, Fun, Acc * 10 + (C - $0)); +digits(Data, Fun, Acc) -> + Fun(Data, Acc). + +%% @doc Parse a list of case-insensitive alpha characters. +%% +%% Changes all characters to lowercase. +-spec alpha(binary(), fun()) -> any(). +alpha(Data, Fun) -> + alpha(Data, Fun, <<>>). + +-spec alpha(binary(), fun(), binary()) -> any(). +alpha(<<>>, Fun, Acc) -> + Fun(<<>>, Acc); +alpha(<< C, Rest/binary >>, Fun, Acc) + when C >= $a andalso C =< $z; + C >= $A andalso C =< $Z -> + C2 = cowboy_bstr:char_to_lower(C), + alpha(Rest, Fun, << Acc/binary, C2 >>); +alpha(Data, Fun, Acc) -> + Fun(Data, Acc). + +%% @doc Parse either a token or a quoted string. +-spec word(binary(), fun()) -> any(). +word(Data = << $", _/binary >>, Fun) -> + quoted_string(Data, Fun); +word(Data, Fun) -> + token(Data, + fun (_Rest, <<>>) -> {error, badarg}; + (Rest, Token) -> Fun(Rest, Token) + end). + +%% @doc Parse a case-insensitive token. +%% +%% Changes all characters to lowercase. +-spec token_ci(binary(), fun()) -> any(). +token_ci(Data, Fun) -> + token(Data, Fun, ci, <<>>). + +%% @doc Parse a token. +-spec token(binary(), fun()) -> any(). +token(Data, Fun) -> + token(Data, Fun, cs, <<>>). + +-spec token(binary(), fun(), ci | cs, binary()) -> any(). +token(<<>>, Fun, _Case, Acc) -> + Fun(<<>>, Acc); +token(Data = << C, _Rest/binary >>, Fun, _Case, Acc) + when C =:= $(; C =:= $); C =:= $<; C =:= $>; C =:= $@; + C =:= $,; C =:= $;; C =:= $:; C =:= $\\; C =:= $"; + C =:= $/; C =:= $[; C =:= $]; C =:= $?; C =:= $=; + C =:= ${; C =:= $}; C =:= $\s; C =:= $\t; + C < 32; C =:= 127 -> + Fun(Data, Acc); +token(<< C, Rest/binary >>, Fun, Case = ci, Acc) -> + C2 = cowboy_bstr:char_to_lower(C), + token(Rest, Fun, Case, << Acc/binary, C2 >>); +token(<< C, Rest/binary >>, Fun, Case, Acc) -> + token(Rest, Fun, Case, << Acc/binary, C >>). + +%% @doc Parse a quoted string. +-spec quoted_string(binary(), fun()) -> any(). +quoted_string(<< $", Rest/binary >>, Fun) -> + quoted_string(Rest, Fun, <<>>). + +-spec quoted_string(binary(), fun(), binary()) -> any(). +quoted_string(<<>>, _Fun, _Acc) -> + {error, badarg}; +quoted_string(<< $", Rest/binary >>, Fun, Acc) -> + Fun(Rest, Acc); +quoted_string(<< $\\, C, Rest/binary >>, Fun, Acc) -> + quoted_string(Rest, Fun, << Acc/binary, C >>); +quoted_string(<< C, Rest/binary >>, Fun, Acc) -> + quoted_string(Rest, Fun, << Acc/binary, C >>). + +%% @doc Parse a quality value. +-spec qvalue(binary(), fun()) -> any(). +qvalue(<< $0, $., Rest/binary >>, Fun) -> + qvalue(Rest, Fun, 0, 100); +qvalue(<< $0, Rest/binary >>, Fun) -> + Fun(Rest, 0); +qvalue(<< $1, $., $0, $0, $0, Rest/binary >>, Fun) -> + Fun(Rest, 1000); +qvalue(<< $1, $., $0, $0, Rest/binary >>, Fun) -> + Fun(Rest, 1000); +qvalue(<< $1, $., $0, Rest/binary >>, Fun) -> + Fun(Rest, 1000); +qvalue(<< $1, Rest/binary >>, Fun) -> + Fun(Rest, 1000); +qvalue(_Data, _Fun) -> + {error, badarg}. + +-spec qvalue(binary(), fun(), integer(), 1 | 10 | 100) -> any(). +qvalue(Data, Fun, Q, 0) -> + Fun(Data, Q); +qvalue(<< C, Rest/binary >>, Fun, Q, M) + when C >= $0, C =< $9 -> + qvalue(Rest, Fun, Q + (C - $0) * M, M div 10); +qvalue(Data, Fun, Q, _M) -> + Fun(Data, Q). + + +%% Interpretation. + +%% @doc Walk through a tokens list and return whether +%% the connection is keepalive or closed. +%% +%% The connection token is expected to be lower-case. +-spec connection_to_atom([binary()]) -> keepalive | close. +connection_to_atom([]) -> + keepalive; +connection_to_atom([<<"keep-alive">>|_Tail]) -> + keepalive; +connection_to_atom([<<"close">>|_Tail]) -> + close; +connection_to_atom([_Any|Tail]) -> + connection_to_atom(Tail). + +%% @doc Decode a URL encoded binary. +%% @equiv urldecode(Bin, crash) +-spec urldecode(binary()) -> binary(). +urldecode(Bin) when is_binary(Bin) -> + urldecode(Bin, <<>>, crash). + +%% @doc Decode a URL encoded binary. +%% The second argument specifies how to handle percent characters that are not +%% followed by two valid hex characters. Use `skip' to ignore such errors, +%% if `crash' is used the function will fail with the reason `badarg'. +-spec urldecode(binary(), crash | skip) -> binary(). +urldecode(Bin, OnError) when is_binary(Bin) -> + urldecode(Bin, <<>>, OnError). + +-spec urldecode(binary(), binary(), crash | skip) -> binary(). +urldecode(<<$%, H, L, Rest/binary>>, Acc, OnError) -> + G = unhex(H), + M = unhex(L), + if G =:= error; M =:= error -> + case OnError of skip -> ok; crash -> erlang:error(badarg) end, + urldecode(<>, <>, OnError); + true -> + urldecode(Rest, <>, OnError) + end; +urldecode(<<$%, Rest/binary>>, Acc, OnError) -> + case OnError of skip -> ok; crash -> erlang:error(badarg) end, + urldecode(Rest, <>, OnError); +urldecode(<<$+, Rest/binary>>, Acc, OnError) -> + urldecode(Rest, <>, OnError); +urldecode(<>, Acc, OnError) -> + urldecode(Rest, <>, OnError); +urldecode(<<>>, Acc, _OnError) -> + Acc. + +-spec unhex(byte()) -> byte() | error. +unhex(C) when C >= $0, C =< $9 -> C - $0; +unhex(C) when C >= $A, C =< $F -> C - $A + 10; +unhex(C) when C >= $a, C =< $f -> C - $a + 10; +unhex(_) -> error. + + +%% @doc URL encode a string binary. +%% @equiv urlencode(Bin, []) +-spec urlencode(binary()) -> binary(). +urlencode(Bin) -> + urlencode(Bin, []). + +%% @doc URL encode a string binary. +%% The `noplus' option disables the default behaviour of quoting space +%% characters, `\s', as `+'. The `upper' option overrides the default behaviour +%% of writing hex numbers using lowecase letters to using uppercase letters +%% instead. +-spec urlencode(binary(), [noplus|upper]) -> binary(). +urlencode(Bin, Opts) -> + Plus = not proplists:get_value(noplus, Opts, false), + Upper = proplists:get_value(upper, Opts, false), + urlencode(Bin, <<>>, Plus, Upper). + +urlencode(<>, Acc, P=Plus, U=Upper) -> + if C >= $0, C =< $9 -> urlencode(Rest, <>, P, U); + C >= $A, C =< $Z -> urlencode(Rest, <>, P, U); + C >= $a, C =< $z -> urlencode(Rest, <>, P, U); + C =:= $.; C =:= $-; C =:= $~; C =:= $_ -> + urlencode(Rest, <>, P, U); + C =:= $ , Plus -> + urlencode(Rest, <>, P, U); + true -> + H = C band 16#F0 bsr 4, L = C band 16#0F, + H1 = if Upper -> tohexu(H); true -> tohexl(H) end, + L1 = if Upper -> tohexu(L); true -> tohexl(L) end, + urlencode(Rest, <>, P, U) + end; +urlencode(<<>>, Acc, _Plus, _Upper) -> + Acc. + +-spec tohexu(byte()) -> byte(). +tohexu(C) when C < 10 -> $0 + C; +tohexu(C) when C < 17 -> $A + C - 10. + +-spec tohexl(byte()) -> byte(). +tohexl(C) when C < 10 -> $0 + C; +tohexl(C) when C < 17 -> $a + C - 10. + + +%% Tests. + +-ifdef(TEST). + +nonempty_charset_list_test_() -> + %% {Value, Result} + Tests = [ + {<<>>, {error, badarg}}, + {<<"iso-8859-5, unicode-1-1;q=0.8">>, [ + {<<"iso-8859-5">>, 1000}, + {<<"unicode-1-1">>, 800} + ]} + ], + [{V, fun() -> R = nonempty_list(V, fun conneg/2) end} || {V, R} <- Tests]. + +nonempty_language_range_list_test_() -> + %% {Value, Result} + Tests = [ + {<<"da, en-gb;q=0.8, en;q=0.7">>, [ + {<<"da">>, 1000}, + {<<"en-gb">>, 800}, + {<<"en">>, 700} + ]}, + {<<"en, en-US, en-cockney, i-cherokee, x-pig-latin">>, [ + {<<"en">>, 1000}, + {<<"en-us">>, 1000}, + {<<"en-cockney">>, 1000}, + {<<"i-cherokee">>, 1000}, + {<<"x-pig-latin">>, 1000} + ]} + ], + [{V, fun() -> R = nonempty_list(V, fun language_range/2) end} + || {V, R} <- Tests]. + +nonempty_token_list_test_() -> + %% {Value, Result} + Tests = [ + {<<>>, {error, badarg}}, + {<<" ">>, {error, badarg}}, + {<<" , ">>, {error, badarg}}, + {<<",,,">>, {error, badarg}}, + {<<"a b">>, {error, badarg}}, + {<<"a , , , ">>, [<<"a">>]}, + {<<" , , , a">>, [<<"a">>]}, + {<<"a, , b">>, [<<"a">>, <<"b">>]}, + {<<"close">>, [<<"close">>]}, + {<<"keep-alive, upgrade">>, [<<"keep-alive">>, <<"upgrade">>]} + ], + [{V, fun() -> R = nonempty_list(V, fun token/2) end} || {V, R} <- Tests]. + +media_range_list_test_() -> + %% {Tokens, Result} + Tests = [ + {<<"audio/*; q=0.2, audio/basic">>, [ + {{<<"audio">>, <<"*">>, []}, 200, []}, + {{<<"audio">>, <<"basic">>, []}, 1000, []} + ]}, + {<<"text/plain; q=0.5, text/html, " + "text/x-dvi; q=0.8, text/x-c">>, [ + {{<<"text">>, <<"plain">>, []}, 500, []}, + {{<<"text">>, <<"html">>, []}, 1000, []}, + {{<<"text">>, <<"x-dvi">>, []}, 800, []}, + {{<<"text">>, <<"x-c">>, []}, 1000, []} + ]}, + {<<"text/*, text/html, text/html;level=1, */*">>, [ + {{<<"text">>, <<"*">>, []}, 1000, []}, + {{<<"text">>, <<"html">>, []}, 1000, []}, + {{<<"text">>, <<"html">>, [{<<"level">>, <<"1">>}]}, 1000, []}, + {{<<"*">>, <<"*">>, []}, 1000, []} + ]}, + {<<"text/*;q=0.3, text/html;q=0.7, text/html;level=1, " + "text/html;level=2;q=0.4, */*;q=0.5">>, [ + {{<<"text">>, <<"*">>, []}, 300, []}, + {{<<"text">>, <<"html">>, []}, 700, []}, + {{<<"text">>, <<"html">>, [{<<"level">>, <<"1">>}]}, 1000, []}, + {{<<"text">>, <<"html">>, [{<<"level">>, <<"2">>}]}, 400, []}, + {{<<"*">>, <<"*">>, []}, 500, []} + ]}, + {<<"text/html;level=1;quoted=\"hi hi hi\";" + "q=0.123;standalone;complex=gits, text/plain">>, [ + {{<<"text">>, <<"html">>, + [{<<"level">>, <<"1">>}, {<<"quoted">>, <<"hi hi hi">>}]}, 123, + [<<"standalone">>, {<<"complex">>, <<"gits">>}]}, + {{<<"text">>, <<"plain">>, []}, 1000, []} + ]} + ], + [{V, fun() -> R = list(V, fun media_range/2) end} || {V, R} <- Tests]. + +entity_tag_match_test_() -> + %% {Tokens, Result} + Tests = [ + {<<"\"xyzzy\"">>, [{strong, <<"xyzzy">>}]}, + {<<"\"xyzzy\", W/\"r2d2xxxx\", \"c3piozzzz\"">>, + [{strong, <<"xyzzy">>}, + {weak, <<"r2d2xxxx">>}, + {strong, <<"c3piozzzz">>}]}, + {<<"*">>, '*'} + ], + [{V, fun() -> R = entity_tag_match(V) end} || {V, R} <- Tests]. + +http_date_test_() -> + %% {Tokens, Result} + Tests = [ + {<<"Sun, 06 Nov 1994 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}}, + {<<"Sunday, 06-Nov-94 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}}, + {<<"Sun Nov 6 08:49:37 1994">>, {{1994, 11, 6}, {8, 49, 37}}} + ], + [{V, fun() -> R = http_date(V) end} || {V, R} <- Tests]. + +rfc1123_date_test_() -> + %% {Tokens, Result} + Tests = [ + {<<"Sun, 06 Nov 1994 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}} + ], + [{V, fun() -> R = rfc1123_date(V) end} || {V, R} <- Tests]. + +rfc850_date_test_() -> + %% {Tokens, Result} + Tests = [ + {<<"Sunday, 06-Nov-94 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}} + ], + [{V, fun() -> R = rfc850_date(V) end} || {V, R} <- Tests]. + +asctime_date_test_() -> + %% {Tokens, Result} + Tests = [ + {<<"Sun Nov 6 08:49:37 1994">>, {{1994, 11, 6}, {8, 49, 37}}} + ], + [{V, fun() -> R = asctime_date(V) end} || {V, R} <- Tests]. + +connection_to_atom_test_() -> + %% {Tokens, Result} + Tests = [ + {[<<"close">>], close}, + {[<<"keep-alive">>], keepalive}, + {[<<"keep-alive">>, <<"upgrade">>], keepalive} + ], + [{lists:flatten(io_lib:format("~p", [T])), + fun() -> R = connection_to_atom(T) end} || {T, R} <- Tests]. + +content_type_test_() -> + %% {ContentType, Result} + Tests = [ + {<<"text/plain; charset=iso-8859-4">>, + {<<"text">>, <<"plain">>, [{<<"charset">>, <<"iso-8859-4">>}]}}, + {<<"multipart/form-data \t;Boundary=\"MultipartIsUgly\"">>, + {<<"multipart">>, <<"form-data">>, [ + {<<"boundary">>, <<"MultipartIsUgly">>} + ]}}, + {<<"foo/bar; one=FirstParam; two=SecondParam">>, + {<<"foo">>, <<"bar">>, [ + {<<"one">>, <<"FirstParam">>}, + {<<"two">>, <<"SecondParam">>} + ]}} + ], + [{V, fun () -> R = content_type(V) end} || {V, R} <- Tests]. + +digits_test_() -> + %% {Digits, Result} + Tests = [ + {<<"42 ">>, 42}, + {<<"69\t">>, 69}, + {<<"1337">>, 1337} + ], + [{V, fun() -> R = digits(V) end} || {V, R} <- Tests]. + +urldecode_test_() -> + U = fun urldecode/2, + [?_assertEqual(<<" ">>, U(<<"%20">>, crash)), + ?_assertEqual(<<" ">>, U(<<"+">>, crash)), + ?_assertEqual(<<0>>, U(<<"%00">>, crash)), + ?_assertEqual(<<255>>, U(<<"%fF">>, crash)), + ?_assertEqual(<<"123">>, U(<<"123">>, crash)), + ?_assertEqual(<<"%i5">>, U(<<"%i5">>, skip)), + ?_assertEqual(<<"%5">>, U(<<"%5">>, skip)), + ?_assertError(badarg, U(<<"%i5">>, crash)), + ?_assertError(badarg, U(<<"%5">>, crash)) + ]. + +urlencode_test_() -> + U = fun urlencode/2, + [?_assertEqual(<<"%ff%00">>, U(<<255,0>>, [])), + ?_assertEqual(<<"%FF%00">>, U(<<255,0>>, [upper])), + ?_assertEqual(<<"+">>, U(<<" ">>, [])), + ?_assertEqual(<<"%20">>, U(<<" ">>, [noplus])), + ?_assertEqual(<<"aBc">>, U(<<"aBc">>, [])), + ?_assertEqual(<<".-~_">>, U(<<".-~_">>, [])), + ?_assertEqual(<<"%ff+">>, urlencode(<<255, " ">>)) + ]. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_handler.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_handler.erl new file mode 100644 index 0000000..b220b09 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_handler.erl @@ -0,0 +1,48 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% +%% 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. + +%% @doc Handler for HTTP requests. +%% +%% HTTP handlers must implement three callbacks: init/3, +%% handle/2 and terminate/2, called one after another in +%% that order. +%% +%% init/3 is meant for initialization. It receives information about +%% the transport and protocol used, along with the handler options from the +%% dispatch list, and allows you to upgrade the protocol if needed. You can +%% define a request-wide state here. +%% +%% handle/2 is meant for handling the request. It receives the +%% request and the state previously defined. +%% +%% terminate/2 is meant for cleaning up. It also receives the +%% request and the state previously defined. +%% +%% You do not have to read the request body or even send a reply if you do +%% not need to. Cowboy will properly handle these cases and clean-up afterwards. +%% In doubt it'll simply close the connection. +%% +%% Note that when upgrading the connection to WebSocket you do not need to +%% define the handle/2 and terminate/2 callbacks. +-module(cowboy_http_handler). + +-export([behaviour_info/1]). + +%% @private +-spec behaviour_info(_) + -> undefined | [{handle, 2} | {init, 3} | {terminate, 2}, ...]. +behaviour_info(callbacks) -> + [{init, 3}, {handle, 2}, {terminate, 2}]; +behaviour_info(_Other) -> + undefined. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_protocol.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_protocol.erl new file mode 100644 index 0000000..0183785 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_protocol.erl @@ -0,0 +1,472 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% Copyright (c) 2011, Anthony Ramine +%% +%% 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. + +%% @doc HTTP protocol handler. +%% +%% The available options are: +%%
+%%
dispatch
The dispatch list for this protocol.
+%%
max_empty_lines
Max number of empty lines before a request. +%% Defaults to 5.
+%%
timeout
Time in milliseconds before an idle +%% connection is closed. Defaults to 5000 milliseconds.
+%%
urldecode
Function and options argument to use when decoding +%% URL encoded strings. Defaults to `{fun cowboy_http:urldecode/2, crash}'. +%%
+%%
+%% +%% Note that there is no need to monitor these processes when using Cowboy as +%% an application as it already supervises them under the listener supervisor. +%% +%% @see cowboy_dispatcher +%% @see cowboy_http_handler +-module(cowboy_http_protocol). +-behaviour(cowboy_protocol). + +-export([start_link/4]). %% API. +-export([init/4, parse_request/1, handler_loop/3]). %% FSM. + +-include("include/http.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-record(state, { + listener :: pid(), + socket :: inet:socket(), + transport :: module(), + dispatch :: cowboy_dispatcher:dispatch_rules(), + handler :: {module(), any()}, + urldecode :: {fun((binary(), T) -> binary()), T}, + req_empty_lines = 0 :: integer(), + max_empty_lines :: integer(), + req_keepalive = 1 :: integer(), + max_keepalive :: integer(), + max_line_length :: integer(), + timeout :: timeout(), + buffer = <<>> :: binary(), + hibernate = false, + loop_timeout = infinity :: timeout(), + loop_timeout_ref +}). + +%% API. + +%% @doc Start an HTTP protocol process. +-spec start_link(pid(), inet:socket(), module(), any()) -> {ok, pid()}. +start_link(ListenerPid, Socket, Transport, Opts) -> + Pid = spawn_link(?MODULE, init, [ListenerPid, Socket, Transport, Opts]), + {ok, Pid}. + +%% FSM. + +%% @private +-spec init(pid(), inet:socket(), module(), any()) -> ok | none(). +init(ListenerPid, Socket, Transport, Opts) -> + Dispatch = proplists:get_value(dispatch, Opts, []), + MaxEmptyLines = proplists:get_value(max_empty_lines, Opts, 5), + MaxKeepalive = proplists:get_value(max_keepalive, Opts, infinity), + MaxLineLength = proplists:get_value(max_line_length, Opts, 4096), + Timeout = proplists:get_value(timeout, Opts, 5000), + URLDecDefault = {fun cowboy_http:urldecode/2, crash}, + URLDec = proplists:get_value(urldecode, Opts, URLDecDefault), + ok = cowboy:accept_ack(ListenerPid), + wait_request(#state{listener=ListenerPid, socket=Socket, transport=Transport, + dispatch=Dispatch, max_empty_lines=MaxEmptyLines, + max_keepalive=MaxKeepalive, max_line_length=MaxLineLength, + timeout=Timeout, urldecode=URLDec}). + +%% @private +-spec parse_request(#state{}) -> ok | none(). +%% We limit the length of the Request-line to MaxLength to avoid endlessly +%% reading from the socket and eventually crashing. +parse_request(State=#state{buffer=Buffer, max_line_length=MaxLength}) -> + case erlang:decode_packet(http_bin, Buffer, []) of + {ok, Request, Rest} -> request(Request, State#state{buffer=Rest}); + {more, _Length} when byte_size(Buffer) > MaxLength -> + error_terminate(413, State); + {more, _Length} -> wait_request(State); + {error, _Reason} -> error_terminate(400, State) + end. + +-spec wait_request(#state{}) -> ok | none(). +wait_request(State=#state{socket=Socket, transport=Transport, + timeout=T, buffer=Buffer}) -> + case Transport:recv(Socket, 0, T) of + {ok, Data} -> parse_request(State#state{ + buffer= << Buffer/binary, Data/binary >>}); + {error, _Reason} -> terminate(State) + end. + +-spec request({http_request, cowboy_http:method(), cowboy_http:uri(), + cowboy_http:version()}, #state{}) -> ok | none(). +request({http_request, _Method, _URI, Version}, State) + when Version =/= {1, 0}, Version =/= {1, 1} -> + error_terminate(505, State); +request({http_request, Method, {abs_path, AbsPath}, Version}, + State=#state{socket=Socket, transport=Transport, + urldecode={URLDecFun, URLDecArg}=URLDec}) -> + URLDecode = fun(Bin) -> URLDecFun(Bin, URLDecArg) end, + {Path, RawPath, Qs} = cowboy_dispatcher:split_path(AbsPath, URLDecode), + ConnAtom = version_to_connection(Version), + parse_header(#http_req{socket=Socket, transport=Transport, + connection=ConnAtom, pid=self(), method=Method, version=Version, + path=Path, raw_path=RawPath, raw_qs=Qs, urldecode=URLDec}, State); +request({http_request, Method, '*', Version}, + State=#state{socket=Socket, transport=Transport, urldecode=URLDec}) -> + ConnAtom = version_to_connection(Version), + parse_header(#http_req{socket=Socket, transport=Transport, + connection=ConnAtom, pid=self(), method=Method, version=Version, + path='*', raw_path= <<"*">>, raw_qs= <<>>, urldecode=URLDec}, State); +request({http_request, _Method, _URI, _Version}, State) -> + error_terminate(501, State); +request({http_error, <<"\r\n">>}, + State=#state{req_empty_lines=N, max_empty_lines=N}) -> + error_terminate(400, State); +request({http_error, <<"\r\n">>}, State=#state{req_empty_lines=N}) -> + parse_request(State#state{req_empty_lines=N + 1}); +request(_Any, State) -> + error_terminate(400, State). + +-spec parse_header(#http_req{}, #state{}) -> ok | none(). +parse_header(Req, State=#state{buffer=Buffer, max_line_length=MaxLength}) -> + case erlang:decode_packet(httph_bin, Buffer, []) of + {ok, Header, Rest} -> header(Header, Req, State#state{buffer=Rest}); + {more, _Length} when byte_size(Buffer) > MaxLength -> + error_terminate(413, State); + {more, _Length} -> wait_header(Req, State); + {error, _Reason} -> error_terminate(400, State) + end. + +-spec wait_header(#http_req{}, #state{}) -> ok | none(). +wait_header(Req, State=#state{socket=Socket, + transport=Transport, timeout=T, buffer=Buffer}) -> + case Transport:recv(Socket, 0, T) of + {ok, Data} -> parse_header(Req, State#state{ + buffer= << Buffer/binary, Data/binary >>}); + {error, timeout} -> error_terminate(408, State); + {error, closed} -> terminate(State) + end. + +-spec header({http_header, integer(), cowboy_http:header(), any(), binary()} + | http_eoh, #http_req{}, #state{}) -> ok | none(). +header({http_header, _I, 'Host', _R, RawHost}, Req=#http_req{ + transport=Transport, host=undefined}, State) -> + RawHost2 = cowboy_bstr:to_lower(RawHost), + case catch cowboy_dispatcher:split_host(RawHost2) of + {Host, RawHost3, undefined} -> + Port = default_port(Transport:name()), + dispatch(fun parse_header/2, Req#http_req{ + host=Host, raw_host=RawHost3, port=Port, + headers=[{'Host', RawHost3}|Req#http_req.headers]}, State); + {Host, RawHost3, Port} -> + dispatch(fun parse_header/2, Req#http_req{ + host=Host, raw_host=RawHost3, port=Port, + headers=[{'Host', RawHost3}|Req#http_req.headers]}, State); + {'EXIT', _Reason} -> + error_terminate(400, State) + end; +%% Ignore Host headers if we already have it. +header({http_header, _I, 'Host', _R, _V}, Req, State) -> + parse_header(Req, State); +header({http_header, _I, 'Connection', _R, Connection}, + Req=#http_req{headers=Headers}, State) -> + Req2 = Req#http_req{headers=[{'Connection', Connection}|Headers]}, + {ConnTokens, Req3} + = cowboy_http_req:parse_header('Connection', Req2), + ConnAtom = cowboy_http:connection_to_atom(ConnTokens), + parse_header(Req3#http_req{connection=ConnAtom}, State); +header({http_header, _I, Field, _R, Value}, Req, State) -> + Field2 = format_header(Field), + parse_header(Req#http_req{headers=[{Field2, Value}|Req#http_req.headers]}, + State); +%% The Host header is required in HTTP/1.1. +header(http_eoh, #http_req{version={1, 1}, host=undefined}, State) -> + error_terminate(400, State); +%% It is however optional in HTTP/1.0. +header(http_eoh, Req=#http_req{version={1, 0}, transport=Transport, + host=undefined}, State=#state{buffer=Buffer}) -> + Port = default_port(Transport:name()), + dispatch(fun handler_init/2, Req#http_req{host=[], raw_host= <<>>, + port=Port, buffer=Buffer}, State#state{buffer= <<>>}); +header(http_eoh, Req, State=#state{buffer=Buffer}) -> + handler_init(Req#http_req{buffer=Buffer}, State#state{buffer= <<>>}); +header(_Any, _Req, State) -> + error_terminate(400, State). + +-spec dispatch(fun((#http_req{}, #state{}) -> ok), + #http_req{}, #state{}) -> ok | none(). +dispatch(Next, Req=#http_req{host=Host, path=Path}, + State=#state{dispatch=Dispatch}) -> + %% @todo We should allow a configurable chain of handlers here to + %% allow things like url rewriting, site-wide authentication, + %% optional dispatching, and more. It would default to what + %% we are doing so far. + case cowboy_dispatcher:match(Host, Path, Dispatch) of + {ok, Handler, Opts, Binds, HostInfo, PathInfo} -> + Next(Req#http_req{host_info=HostInfo, path_info=PathInfo, + bindings=Binds}, State#state{handler={Handler, Opts}}); + {error, notfound, host} -> + error_terminate(400, State); + {error, notfound, path} -> + error_terminate(404, State) + end. + +-spec handler_init(#http_req{}, #state{}) -> ok | none(). +handler_init(Req, State=#state{transport=Transport, + handler={Handler, Opts}}) -> + try Handler:init({Transport:name(), http}, Req, Opts) of + {ok, Req2, HandlerState} -> + handler_handle(HandlerState, Req2, State); + {loop, Req2, HandlerState} -> + handler_before_loop(HandlerState, Req2, State); + {loop, Req2, HandlerState, hibernate} -> + handler_before_loop(HandlerState, Req2, + State#state{hibernate=true}); + {loop, Req2, HandlerState, Timeout} -> + handler_before_loop(HandlerState, Req2, + State#state{loop_timeout=Timeout}); + {loop, Req2, HandlerState, Timeout, hibernate} -> + handler_before_loop(HandlerState, Req2, + State#state{hibernate=true, loop_timeout=Timeout}); + {shutdown, Req2, HandlerState} -> + handler_terminate(HandlerState, Req2, State); + %% @todo {upgrade, transport, Module} + {upgrade, protocol, Module} -> + upgrade_protocol(Req, State, Module) + catch Class:Reason -> + error_terminate(500, State), + error_logger:error_msg( + "** Handler ~p terminating in init/3~n" + " for the reason ~p:~p~n" + "** Options were ~p~n" + "** Request was ~p~n** Stacktrace: ~p~n~n", + [Handler, Class, Reason, Opts, Req, erlang:get_stacktrace()]) + end. + +-spec upgrade_protocol(#http_req{}, #state{}, atom()) -> ok | none(). +upgrade_protocol(Req, State=#state{listener=ListenerPid, + handler={Handler, Opts}}, Module) -> + case Module:upgrade(ListenerPid, Handler, Opts, Req) of + {UpgradeRes, Req2} -> next_request(Req2, State, UpgradeRes); + _Any -> terminate(State) + end. + +-spec handler_handle(any(), #http_req{}, #state{}) -> ok | none(). +handler_handle(HandlerState, Req, State=#state{handler={Handler, Opts}}) -> + try Handler:handle(Req, HandlerState) of + {ok, Req2, HandlerState2} -> + terminate_request(HandlerState2, Req2, State) + catch Class:Reason -> + error_logger:error_msg( + "** Handler ~p terminating in handle/2~n" + " for the reason ~p:~p~n" + "** Options were ~p~n** Handler state was ~p~n" + "** Request was ~p~n** Stacktrace: ~p~n~n", + [Handler, Class, Reason, Opts, + HandlerState, Req, erlang:get_stacktrace()]), + handler_terminate(HandlerState, Req, State), + error_terminate(500, State) + end. + +%% We don't listen for Transport closes because that would force us +%% to receive data and buffer it indefinitely. +-spec handler_before_loop(any(), #http_req{}, #state{}) -> ok | none(). +handler_before_loop(HandlerState, Req, State=#state{hibernate=true}) -> + State2 = handler_loop_timeout(State), + erlang:hibernate(?MODULE, handler_loop, + [HandlerState, Req, State2#state{hibernate=false}]); +handler_before_loop(HandlerState, Req, State) -> + State2 = handler_loop_timeout(State), + handler_loop(HandlerState, Req, State2). + +%% Almost the same code can be found in cowboy_http_websocket. +-spec handler_loop_timeout(#state{}) -> #state{}. +handler_loop_timeout(State=#state{loop_timeout=infinity}) -> + State#state{loop_timeout_ref=undefined}; +handler_loop_timeout(State=#state{loop_timeout=Timeout, + loop_timeout_ref=PrevRef}) -> + _ = case PrevRef of undefined -> ignore; PrevRef -> + erlang:cancel_timer(PrevRef) end, + TRef = make_ref(), + erlang:send_after(Timeout, self(), {?MODULE, timeout, TRef}), + State#state{loop_timeout_ref=TRef}. + +-spec handler_loop(any(), #http_req{}, #state{}) -> ok | none(). +handler_loop(HandlerState, Req, State=#state{loop_timeout_ref=TRef}) -> + receive + {?MODULE, timeout, TRef} -> + terminate_request(HandlerState, Req, State); + {?MODULE, timeout, OlderTRef} when is_reference(OlderTRef) -> + handler_loop(HandlerState, Req, State); + Message -> + handler_call(HandlerState, Req, State, Message) + end. + +-spec handler_call(any(), #http_req{}, #state{}, any()) -> ok | none(). +handler_call(HandlerState, Req, State=#state{handler={Handler, Opts}}, + Message) -> + try Handler:info(Message, Req, HandlerState) of + {ok, Req2, HandlerState2} -> + terminate_request(HandlerState2, Req2, State); + {loop, Req2, HandlerState2} -> + handler_before_loop(HandlerState2, Req2, State); + {loop, Req2, HandlerState2, hibernate} -> + handler_before_loop(HandlerState2, Req2, + State#state{hibernate=true}) + catch Class:Reason -> + error_logger:error_msg( + "** Handler ~p terminating in info/3~n" + " for the reason ~p:~p~n" + "** Options were ~p~n** Handler state was ~p~n" + "** Request was ~p~n** Stacktrace: ~p~n~n", + [Handler, Class, Reason, Opts, + HandlerState, Req, erlang:get_stacktrace()]), + handler_terminate(HandlerState, Req, State), + error_terminate(500, State) + end. + +-spec handler_terminate(any(), #http_req{}, #state{}) -> ok. +handler_terminate(HandlerState, Req, #state{handler={Handler, Opts}}) -> + try + Handler:terminate(Req#http_req{resp_state=locked}, HandlerState) + catch Class:Reason -> + error_logger:error_msg( + "** Handler ~p terminating in terminate/2~n" + " for the reason ~p:~p~n" + "** Options were ~p~n** Handler state was ~p~n" + "** Request was ~p~n** Stacktrace: ~p~n~n", + [Handler, Class, Reason, Opts, + HandlerState, Req, erlang:get_stacktrace()]) + end. + +-spec terminate_request(any(), #http_req{}, #state{}) -> ok | none(). +terminate_request(HandlerState, Req, State) -> + HandlerRes = handler_terminate(HandlerState, Req, State), + next_request(Req, State, HandlerRes). + +-spec next_request(#http_req{}, #state{}, any()) -> ok | none(). +next_request(Req=#http_req{connection=Conn}, + State=#state{req_keepalive=Keepalive, max_keepalive=MaxKeepalive}, + HandlerRes) -> + RespRes = ensure_response(Req), + {BodyRes, Buffer} = ensure_body_processed(Req), + %% Flush the resp_sent message before moving on. + receive {cowboy_http_req, resp_sent} -> ok after 0 -> ok end, + case {HandlerRes, BodyRes, RespRes, Conn} of + {ok, ok, ok, keepalive} when Keepalive < MaxKeepalive -> + ?MODULE:parse_request(State#state{ + buffer=Buffer, req_empty_lines=0, + req_keepalive=Keepalive + 1}); + _Closed -> + terminate(State) + end. + +-spec ensure_body_processed(#http_req{}) -> {ok | close, binary()}. +ensure_body_processed(#http_req{body_state=done, buffer=Buffer}) -> + {ok, Buffer}; +ensure_body_processed(Req=#http_req{body_state=waiting}) -> + case cowboy_http_req:body(Req) of + {error, badarg} -> {ok, Req#http_req.buffer}; %% No body. + {error, _Reason} -> {close, <<>>}; + {ok, _, Req2} -> {ok, Req2#http_req.buffer} + end; +ensure_body_processed(Req=#http_req{body_state={multipart, _, _}}) -> + {ok, Req2} = cowboy_http_req:multipart_skip(Req), + ensure_body_processed(Req2). + +-spec ensure_response(#http_req{}) -> ok. +%% The handler has already fully replied to the client. +ensure_response(#http_req{resp_state=done}) -> + ok; +%% No response has been sent but everything apparently went fine. +%% Reply with 204 No Content to indicate this. +ensure_response(Req=#http_req{resp_state=waiting}) -> + _ = cowboy_http_req:reply(204, [], [], Req), + ok; +%% Close the chunked reply. +ensure_response(#http_req{method='HEAD', resp_state=chunks}) -> + close; +ensure_response(#http_req{socket=Socket, transport=Transport, + resp_state=chunks}) -> + Transport:send(Socket, <<"0\r\n\r\n">>), + close. + +%% Only send an error reply if there is no resp_sent message. +-spec error_terminate(cowboy_http:status(), #state{}) -> ok. +error_terminate(Code, State=#state{socket=Socket, transport=Transport}) -> + receive + {cowboy_http_req, resp_sent} -> ok + after 0 -> + _ = cowboy_http_req:reply(Code, #http_req{ + socket=Socket, transport=Transport, + connection=close, pid=self(), resp_state=waiting}), + ok + end, + terminate(State). + +-spec terminate(#state{}) -> ok. +terminate(#state{socket=Socket, transport=Transport}) -> + Transport:close(Socket), + ok. + +%% Internal. + +-spec version_to_connection(cowboy_http:version()) -> keepalive | close. +version_to_connection({1, 1}) -> keepalive; +version_to_connection(_Any) -> close. + +-spec default_port(atom()) -> 80 | 443. +default_port(ssl) -> 443; +default_port(_) -> 80. + +%% @todo While 32 should be enough for everybody, we should probably make +%% this configurable or something. +-spec format_header(atom()) -> atom(); (binary()) -> binary(). +format_header(Field) when is_atom(Field) -> + Field; +format_header(Field) when byte_size(Field) =< 20; byte_size(Field) > 32 -> + Field; +format_header(Field) -> + format_header(Field, true, <<>>). + +format_header(<<>>, _Any, Acc) -> + Acc; +%% Replicate a bug in OTP for compatibility reasons when there's a - right +%% after another. Proper use should always be 'true' instead of 'not Bool'. +format_header(<< $-, Rest/bits >>, Bool, Acc) -> + format_header(Rest, not Bool, << Acc/binary, $- >>); +format_header(<< C, Rest/bits >>, true, Acc) -> + format_header(Rest, false, << Acc/binary, (cowboy_bstr:char_to_upper(C)) >>); +format_header(<< C, Rest/bits >>, false, Acc) -> + format_header(Rest, false, << Acc/binary, (cowboy_bstr:char_to_lower(C)) >>). + +%% Tests. + +-ifdef(TEST). + +format_header_test_() -> + %% {Header, Result} + Tests = [ + {<<"Sec-Websocket-Version">>, <<"Sec-Websocket-Version">>}, + {<<"Sec-WebSocket-Version">>, <<"Sec-Websocket-Version">>}, + {<<"sec-websocket-version">>, <<"Sec-Websocket-Version">>}, + {<<"SEC-WEBSOCKET-VERSION">>, <<"Sec-Websocket-Version">>}, + %% These last tests ensures we're formatting headers exactly like OTP. + %% Even though it's dumb, it's better for compatibility reasons. + {<<"Sec-WebSocket--Version">>, <<"Sec-Websocket--version">>}, + {<<"Sec-WebSocket---Version">>, <<"Sec-Websocket---Version">>} + ], + [{H, fun() -> R = format_header(H) end} || {H, R} <- Tests]. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_req.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_req.erl new file mode 100644 index 0000000..dd772df --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_req.erl @@ -0,0 +1,820 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% Copyright (c) 2011, Anthony Ramine +%% +%% 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. + +%% @doc HTTP request manipulation API. +%% +%% Almost all functions in this module return a new Req variable. +%% It should always be used instead of the one used in your function call +%% because it keeps the state of the request. It also allows Cowboy to do +%% some lazy evaluation and cache results where possible. +-module(cowboy_http_req). + +-export([ + method/1, version/1, peer/1, peer_addr/1, + host/1, host_info/1, raw_host/1, port/1, + path/1, path_info/1, raw_path/1, + qs_val/2, qs_val/3, qs_vals/1, raw_qs/1, + binding/2, binding/3, bindings/1, + header/2, header/3, headers/1, + parse_header/2, parse_header/3, + cookie/2, cookie/3, cookies/1, + meta/2, meta/3 +]). %% Request API. + +-export([ + body/1, body/2, body_qs/1, + multipart_data/1, multipart_skip/1 +]). %% Request Body API. + +-export([ + set_resp_cookie/4, set_resp_header/3, set_resp_body/2, + set_resp_body_fun/3, has_resp_header/2, has_resp_body/1, + reply/2, reply/3, reply/4, + chunked_reply/2, chunked_reply/3, chunk/2, + upgrade_reply/3 +]). %% Response API. + +-export([ + compact/1, transport/1 +]). %% Misc API. + +-include("include/http.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +%% Request API. + +%% @doc Return the HTTP method of the request. +-spec method(#http_req{}) -> {cowboy_http:method(), #http_req{}}. +method(Req) -> + {Req#http_req.method, Req}. + +%% @doc Return the HTTP version used for the request. +-spec version(#http_req{}) -> {cowboy_http:version(), #http_req{}}. +version(Req) -> + {Req#http_req.version, Req}. + +%% @doc Return the peer address and port number of the remote host. +-spec peer(#http_req{}) -> {{inet:ip_address(), inet:ip_port()}, #http_req{}}. +peer(Req=#http_req{socket=Socket, transport=Transport, peer=undefined}) -> + {ok, Peer} = Transport:peername(Socket), + {Peer, Req#http_req{peer=Peer}}; +peer(Req) -> + {Req#http_req.peer, Req}. + +%% @doc Returns the peer address calculated from headers. +-spec peer_addr(#http_req{}) -> {inet:ip_address(), #http_req{}}. +peer_addr(Req = #http_req{}) -> + {RealIp, Req1} = header(<<"X-Real-Ip">>, Req), + {ForwardedForRaw, Req2} = header(<<"X-Forwarded-For">>, Req1), + {{PeerIp, _PeerPort}, Req3} = peer(Req2), + ForwardedFor = case ForwardedForRaw of + undefined -> + undefined; + ForwardedForRaw -> + case re:run(ForwardedForRaw, "^(?[^\\,]+)", + [{capture, [first_ip], binary}]) of + {match, [FirstIp]} -> FirstIp; + _Any -> undefined + end + end, + {ok, PeerAddr} = if + is_binary(RealIp) -> inet_parse:address(binary_to_list(RealIp)); + is_binary(ForwardedFor) -> inet_parse:address(binary_to_list(ForwardedFor)); + true -> {ok, PeerIp} + end, + {PeerAddr, Req3}. + +%% @doc Return the tokens for the hostname requested. +-spec host(#http_req{}) -> {cowboy_dispatcher:tokens(), #http_req{}}. +host(Req) -> + {Req#http_req.host, Req}. + +%% @doc Return the extra host information obtained from partially matching +%% the hostname using '...'. +-spec host_info(#http_req{}) + -> {cowboy_dispatcher:tokens() | undefined, #http_req{}}. +host_info(Req) -> + {Req#http_req.host_info, Req}. + +%% @doc Return the raw host directly taken from the request. +-spec raw_host(#http_req{}) -> {binary(), #http_req{}}. +raw_host(Req) -> + {Req#http_req.raw_host, Req}. + +%% @doc Return the port used for this request. +-spec port(#http_req{}) -> {inet:ip_port(), #http_req{}}. +port(Req) -> + {Req#http_req.port, Req}. + +%% @doc Return the path segments for the path requested. +%% +%% Following RFC2396, this function may return path segments containing any +%% character, including / if, and only if, a / was escaped +%% and part of a path segment in the path requested. +-spec path(#http_req{}) -> {cowboy_dispatcher:tokens(), #http_req{}}. +path(Req) -> + {Req#http_req.path, Req}. + +%% @doc Return the extra path information obtained from partially matching +%% the patch using '...'. +-spec path_info(#http_req{}) + -> {cowboy_dispatcher:tokens() | undefined, #http_req{}}. +path_info(Req) -> + {Req#http_req.path_info, Req}. + +%% @doc Return the raw path directly taken from the request. +-spec raw_path(#http_req{}) -> {binary(), #http_req{}}. +raw_path(Req) -> + {Req#http_req.raw_path, Req}. + +%% @equiv qs_val(Name, Req, undefined) +-spec qs_val(binary(), #http_req{}) + -> {binary() | true | undefined, #http_req{}}. +qs_val(Name, Req) when is_binary(Name) -> + qs_val(Name, Req, undefined). + +%% @doc Return the query string value for the given key, or a default if +%% missing. +qs_val(Name, Req=#http_req{raw_qs=RawQs, qs_vals=undefined, + urldecode={URLDecFun, URLDecArg}}, Default) when is_binary(Name) -> + QsVals = parse_qs(RawQs, fun(Bin) -> URLDecFun(Bin, URLDecArg) end), + qs_val(Name, Req#http_req{qs_vals=QsVals}, Default); +qs_val(Name, Req, Default) -> + case lists:keyfind(Name, 1, Req#http_req.qs_vals) of + {Name, Value} -> {Value, Req}; + false -> {Default, Req} + end. + +%% @doc Return the full list of query string values. +-spec qs_vals(#http_req{}) -> {list({binary(), binary() | true}), #http_req{}}. +qs_vals(Req=#http_req{raw_qs=RawQs, qs_vals=undefined, + urldecode={URLDecFun, URLDecArg}}) -> + QsVals = parse_qs(RawQs, fun(Bin) -> URLDecFun(Bin, URLDecArg) end), + qs_vals(Req#http_req{qs_vals=QsVals}); +qs_vals(Req=#http_req{qs_vals=QsVals}) -> + {QsVals, Req}. + +%% @doc Return the raw query string directly taken from the request. +-spec raw_qs(#http_req{}) -> {binary(), #http_req{}}. +raw_qs(Req) -> + {Req#http_req.raw_qs, Req}. + +%% @equiv binding(Name, Req, undefined) +-spec binding(atom(), #http_req{}) -> {binary() | undefined, #http_req{}}. +binding(Name, Req) when is_atom(Name) -> + binding(Name, Req, undefined). + +%% @doc Return the binding value for the given key obtained when matching +%% the host and path against the dispatch list, or a default if missing. +binding(Name, Req, Default) when is_atom(Name) -> + case lists:keyfind(Name, 1, Req#http_req.bindings) of + {Name, Value} -> {Value, Req}; + false -> {Default, Req} + end. + +%% @doc Return the full list of binding values. +-spec bindings(#http_req{}) -> {list({atom(), binary()}), #http_req{}}. +bindings(Req) -> + {Req#http_req.bindings, Req}. + +%% @equiv header(Name, Req, undefined) +-spec header(atom() | binary(), #http_req{}) + -> {binary() | undefined, #http_req{}}. +header(Name, Req) when is_atom(Name) orelse is_binary(Name) -> + header(Name, Req, undefined). + +%% @doc Return the header value for the given key, or a default if missing. +header(Name, Req, Default) when is_atom(Name) orelse is_binary(Name) -> + case lists:keyfind(Name, 1, Req#http_req.headers) of + {Name, Value} -> {Value, Req}; + false -> {Default, Req} + end. + +%% @doc Return the full list of headers. +-spec headers(#http_req{}) -> {cowboy_http:headers(), #http_req{}}. +headers(Req) -> + {Req#http_req.headers, Req}. + +%% @doc Semantically parse headers. +%% +%% When the value isn't found, a proper default value for the type +%% returned is used as a return value. +%% @see parse_header/3 +-spec parse_header(cowboy_http:header(), #http_req{}) + -> {any(), #http_req{}} | {error, badarg}. +parse_header(Name, Req=#http_req{p_headers=PHeaders}) -> + case lists:keyfind(Name, 1, PHeaders) of + false -> parse_header(Name, Req, parse_header_default(Name)); + {Name, Value} -> {Value, Req} + end. + +%% @doc Default values for semantic header parsing. +-spec parse_header_default(cowboy_http:header()) -> any(). +parse_header_default('Connection') -> []; +parse_header_default(_Name) -> undefined. + +%% @doc Semantically parse headers. +%% +%% When the header is unknown, the value is returned directly without parsing. +-spec parse_header(cowboy_http:header(), #http_req{}, any()) + -> {any(), #http_req{}} | {error, badarg}. +parse_header(Name, Req, Default) when Name =:= 'Accept' -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:list(Value, fun cowboy_http:media_range/2) + end); +parse_header(Name, Req, Default) when Name =:= 'Accept-Charset' -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:nonempty_list(Value, fun cowboy_http:conneg/2) + end); +parse_header(Name, Req, Default) when Name =:= 'Accept-Encoding' -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:list(Value, fun cowboy_http:conneg/2) + end); +parse_header(Name, Req, Default) when Name =:= 'Accept-Language' -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:nonempty_list(Value, fun cowboy_http:language_range/2) + end); +parse_header(Name, Req, Default) when Name =:= 'Connection' -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2) + end); +parse_header(Name, Req, Default) when Name =:= 'Content-Length' -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:digits(Value) + end); +parse_header(Name, Req, Default) when Name =:= 'Content-Type' -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:content_type(Value) + end); +parse_header(Name, Req, Default) + when Name =:= 'If-Match'; Name =:= 'If-None-Match' -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:entity_tag_match(Value) + end); +parse_header(Name, Req, Default) + when Name =:= 'If-Modified-Since'; Name =:= 'If-Unmodified-Since' -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:http_date(Value) + end); +parse_header(Name, Req, Default) when Name =:= 'Upgrade' -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2) + end); +parse_header(Name, Req, Default) when Name =:= <<"sec-websocket-protocol">> -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:nonempty_list(Value, fun cowboy_http:token/2) + end); +parse_header(Name, Req, Default) -> + {Value, Req2} = header(Name, Req, Default), + {undefined, Value, Req2}. + +parse_header(Name, Req=#http_req{p_headers=PHeaders}, Default, Fun) -> + case header(Name, Req) of + {undefined, Req2} -> + {Default, Req2#http_req{p_headers=[{Name, Default}|PHeaders]}}; + {Value, Req2} -> + case Fun(Value) of + {error, badarg} -> + {error, badarg}; + P -> + {P, Req2#http_req{p_headers=[{Name, P}|PHeaders]}} + end + end. + +%% @equiv cookie(Name, Req, undefined) +-spec cookie(binary(), #http_req{}) + -> {binary() | true | undefined, #http_req{}}. +cookie(Name, Req) when is_binary(Name) -> + cookie(Name, Req, undefined). + +%% @doc Return the cookie value for the given key, or a default if +%% missing. +cookie(Name, Req=#http_req{cookies=undefined}, Default) when is_binary(Name) -> + case header('Cookie', Req) of + {undefined, Req2} -> + {Default, Req2#http_req{cookies=[]}}; + {RawCookie, Req2} -> + Cookies = cowboy_cookies:parse_cookie(RawCookie), + cookie(Name, Req2#http_req{cookies=Cookies}, Default) + end; +cookie(Name, Req, Default) -> + case lists:keyfind(Name, 1, Req#http_req.cookies) of + {Name, Value} -> {Value, Req}; + false -> {Default, Req} + end. + +%% @doc Return the full list of cookie values. +-spec cookies(#http_req{}) -> {list({binary(), binary() | true}), #http_req{}}. +cookies(Req=#http_req{cookies=undefined}) -> + case header('Cookie', Req) of + {undefined, Req2} -> + {[], Req2#http_req{cookies=[]}}; + {RawCookie, Req2} -> + Cookies = cowboy_cookies:parse_cookie(RawCookie), + cookies(Req2#http_req{cookies=Cookies}) + end; +cookies(Req=#http_req{cookies=Cookies}) -> + {Cookies, Req}. + +%% @equiv meta(Name, Req, undefined) +-spec meta(atom(), #http_req{}) -> {any() | undefined, #http_req{}}. +meta(Name, Req) -> + meta(Name, Req, undefined). + +%% @doc Return metadata information about the request. +%% +%% Metadata information varies from one protocol to another. Websockets +%% would define the protocol version here, while REST would use it to +%% indicate which media type, language and charset were retained. +-spec meta(atom(), #http_req{}, any()) -> {any(), #http_req{}}. +meta(Name, Req, Default) -> + case lists:keyfind(Name, 1, Req#http_req.meta) of + {Name, Value} -> {Value, Req}; + false -> {Default, Req} + end. + +%% Request Body API. + +%% @doc Return the full body sent with the request, or {error, badarg} +%% if no Content-Length is available. +%% @todo We probably want to allow a max length. +%% @todo Add multipart support to this function. +-spec body(#http_req{}) -> {ok, binary(), #http_req{}} | {error, atom()}. +body(Req) -> + {Length, Req2} = cowboy_http_req:parse_header('Content-Length', Req), + case Length of + undefined -> {error, badarg}; + {error, badarg} -> {error, badarg}; + _Any -> + body(Length, Req2) + end. + +%% @doc Return Length bytes of the request body. +%% +%% You probably shouldn't be calling this function directly, as it expects the +%% Length argument to be the full size of the body, and will consider +%% the body to be fully read from the socket. +%% @todo We probably want to configure the timeout. +-spec body(non_neg_integer(), #http_req{}) + -> {ok, binary(), #http_req{}} | {error, atom()}. +body(Length, Req=#http_req{body_state=waiting, buffer=Buffer}) + when is_integer(Length) andalso Length =< byte_size(Buffer) -> + << Body:Length/binary, Rest/bits >> = Buffer, + {ok, Body, Req#http_req{body_state=done, buffer=Rest}}; +body(Length, Req=#http_req{socket=Socket, transport=Transport, + body_state=waiting, buffer=Buffer}) -> + case Transport:recv(Socket, Length - byte_size(Buffer), 5000) of + {ok, Body} -> {ok, << Buffer/binary, Body/binary >>, + Req#http_req{body_state=done, buffer= <<>>}}; + {error, Reason} -> {error, Reason} + end. + +%% @doc Return the full body sent with the reqest, parsed as an +%% application/x-www-form-urlencoded string. Essentially a POST query string. +-spec body_qs(#http_req{}) -> {list({binary(), binary() | true}), #http_req{}}. +body_qs(Req=#http_req{urldecode={URLDecFun, URLDecArg}}) -> + {ok, Body, Req2} = body(Req), + {parse_qs(Body, fun(Bin) -> URLDecFun(Bin, URLDecArg) end), Req2}. + +%% Multipart Request API. + +%% @doc Return data from the multipart parser. +%% +%% Use this function for multipart streaming. For each part in the request, +%% this function returns {headers, Headers} followed by a sequence of +%% {data, Data} tuples and finally end_of_part. When there +%% is no part to parse anymore, eof is returned. +%% +%% If the request Content-Type is not a multipart one, {error, badarg} +%% is returned. +-spec multipart_data(#http_req{}) + -> {{headers, cowboy_http:headers()} + | {data, binary()} | end_of_part | eof, + #http_req{}}. +multipart_data(Req=#http_req{body_state=waiting}) -> + {{<<"multipart">>, _SubType, Params}, Req2} = + parse_header('Content-Type', Req), + {_, Boundary} = lists:keyfind(<<"boundary">>, 1, Params), + {Length, Req3=#http_req{buffer=Buffer}} = + parse_header('Content-Length', Req2), + multipart_data(Req3, Length, cowboy_multipart:parser(Boundary), Buffer); +multipart_data(Req=#http_req{body_state={multipart, Length, Cont}}) -> + multipart_data(Req, Length, Cont()); +multipart_data(Req=#http_req{body_state=done}) -> + {eof, Req}. + +multipart_data(Req, Length, Parser, Buffer) when byte_size(Buffer) >= Length -> + << Data:Length/binary, Rest/binary >> = Buffer, + multipart_data(Req#http_req{buffer=Rest}, 0, Parser(Data)); +multipart_data(Req, Length, Parser, Buffer) -> + NewLength = Length - byte_size(Buffer), + multipart_data(Req#http_req{buffer= <<>>}, NewLength, Parser(Buffer)). + +multipart_data(Req, Length, {headers, Headers, Cont}) -> + {{headers, Headers}, Req#http_req{body_state={multipart, Length, Cont}}}; +multipart_data(Req, Length, {body, Data, Cont}) -> + {{body, Data}, Req#http_req{body_state={multipart, Length, Cont}}}; +multipart_data(Req, Length, {end_of_part, Cont}) -> + {end_of_part, Req#http_req{body_state={multipart, Length, Cont}}}; +multipart_data(Req, 0, eof) -> + {eof, Req#http_req{body_state=done}}; +multipart_data(Req=#http_req{socket=Socket, transport=Transport}, + Length, eof) -> + {ok, _Data} = Transport:recv(Socket, Length, 5000), + {eof, Req#http_req{body_state=done}}; +multipart_data(Req=#http_req{socket=Socket, transport=Transport}, + Length, {more, Parser}) when Length > 0 -> + case Transport:recv(Socket, 0, 5000) of + {ok, << Data:Length/binary, Buffer/binary >>} -> + multipart_data(Req#http_req{buffer=Buffer}, 0, Parser(Data)); + {ok, Data} -> + multipart_data(Req, Length - byte_size(Data), Parser(Data)) + end. + +%% @doc Skip a part returned by the multipart parser. +%% +%% This function repeatedly calls multipart_data/1 until +%% end_of_part or eof is parsed. +multipart_skip(Req) -> + case multipart_data(Req) of + {end_of_part, Req2} -> {ok, Req2}; + {eof, Req2} -> {ok, Req2}; + {_Other, Req2} -> multipart_skip(Req2) + end. + +%% Response API. + +%% @doc Add a cookie header to the response. +-spec set_resp_cookie(binary(), binary(), [cowboy_cookies:cookie_option()], + #http_req{}) -> {ok, #http_req{}}. +set_resp_cookie(Name, Value, Options, Req) -> + {HeaderName, HeaderValue} = cowboy_cookies:cookie(Name, Value, Options), + set_resp_header(HeaderName, HeaderValue, Req). + +%% @doc Add a header to the response. +set_resp_header(Name, Value, Req=#http_req{resp_headers=RespHeaders}) -> + NameBin = header_to_binary(Name), + {ok, Req#http_req{resp_headers=[{NameBin, Value}|RespHeaders]}}. + +%% @doc Add a body to the response. +%% +%% The body set here is ignored if the response is later sent using +%% anything other than reply/2 or reply/3. The response body is expected +%% to be a binary or an iolist. +set_resp_body(Body, Req) -> + {ok, Req#http_req{resp_body=Body}}. + + +%% @doc Add a body function to the response. +%% +%% The response body may also be set to a content-length - stream-function pair. +%% If the response body is of this type normal response headers will be sent. +%% After the response headers has been sent the body function is applied. +%% The body function is expected to write the response body directly to the +%% socket using the transport module. +%% +%% If the body function crashes while writing the response body or writes fewer +%% bytes than declared the behaviour is undefined. The body set here is ignored +%% if the response is later sent using anything other than `reply/2' or +%% `reply/3'. +%% +%% @see cowboy_http_req:transport/1. +-spec set_resp_body_fun(non_neg_integer(), fun(() -> {sent, non_neg_integer()}), + #http_req{}) -> {ok, #http_req{}}. +set_resp_body_fun(StreamLen, StreamFun, Req) -> + {ok, Req#http_req{resp_body={StreamLen, StreamFun}}}. + + +%% @doc Return whether the given header has been set for the response. +has_resp_header(Name, #http_req{resp_headers=RespHeaders}) -> + NameBin = header_to_binary(Name), + lists:keymember(NameBin, 1, RespHeaders). + +%% @doc Return whether a body has been set for the response. +has_resp_body(#http_req{resp_body={Length, _}}) -> + Length > 0; +has_resp_body(#http_req{resp_body=RespBody}) -> + iolist_size(RespBody) > 0. + +%% @equiv reply(Status, [], [], Req) +-spec reply(cowboy_http:status(), #http_req{}) -> {ok, #http_req{}}. +reply(Status, Req=#http_req{resp_body=Body}) -> + reply(Status, [], Body, Req). + +%% @equiv reply(Status, Headers, [], Req) +-spec reply(cowboy_http:status(), cowboy_http:headers(), #http_req{}) + -> {ok, #http_req{}}. +reply(Status, Headers, Req=#http_req{resp_body=Body}) -> + reply(Status, Headers, Body, Req). + +%% @doc Send a reply to the client. +reply(Status, Headers, Body, Req=#http_req{socket=Socket, + transport=Transport, connection=Connection, pid=ReqPid, + method=Method, resp_state=waiting, resp_headers=RespHeaders}) -> + RespConn = response_connection(Headers, Connection), + ContentLen = case Body of {CL, _} -> CL; _ -> iolist_size(Body) end, + Head = response_head(Status, Headers, RespHeaders, [ + {<<"Connection">>, atom_to_connection(Connection)}, + {<<"Content-Length">>, integer_to_list(ContentLen)}, + {<<"Date">>, cowboy_clock:rfc1123()}, + {<<"Server">>, <<"Cowboy">>} + ]), + case {Method, Body} of + {'HEAD', _} -> Transport:send(Socket, Head); + {_, {_, StreamFun}} -> Transport:send(Socket, Head), StreamFun(); + {_, _} -> Transport:send(Socket, [Head, Body]) + end, + ReqPid ! {?MODULE, resp_sent}, + {ok, Req#http_req{connection=RespConn, resp_state=done, + resp_headers=[], resp_body= <<>>}}. + +%% @equiv chunked_reply(Status, [], Req) +-spec chunked_reply(cowboy_http:status(), #http_req{}) -> {ok, #http_req{}}. +chunked_reply(Status, Req) -> + chunked_reply(Status, [], Req). + +%% @doc Initiate the sending of a chunked reply to the client. +%% @see cowboy_http_req:chunk/2 +-spec chunked_reply(cowboy_http:status(), cowboy_http:headers(), #http_req{}) + -> {ok, #http_req{}}. +chunked_reply(Status, Headers, Req=#http_req{socket=Socket, + transport=Transport, connection=Connection, pid=ReqPid, + resp_state=waiting, resp_headers=RespHeaders}) -> + RespConn = response_connection(Headers, Connection), + Head = response_head(Status, Headers, RespHeaders, [ + {<<"Connection">>, atom_to_connection(Connection)}, + {<<"Transfer-Encoding">>, <<"chunked">>}, + {<<"Date">>, cowboy_clock:rfc1123()}, + {<<"Server">>, <<"Cowboy">>} + ]), + Transport:send(Socket, Head), + ReqPid ! {?MODULE, resp_sent}, + {ok, Req#http_req{connection=RespConn, resp_state=chunks, + resp_headers=[], resp_body= <<>>}}. + +%% @doc Send a chunk of data. +%% +%% A chunked reply must have been initiated before calling this function. +chunk(_Data, #http_req{socket=_Socket, transport=_Transport, method='HEAD'}) -> + ok; +chunk(Data, #http_req{socket=Socket, transport=Transport, resp_state=chunks}) -> + Transport:send(Socket, [erlang:integer_to_list(iolist_size(Data), 16), + <<"\r\n">>, Data, <<"\r\n">>]). + +%% @doc Send an upgrade reply. +%% @private +-spec upgrade_reply(cowboy_http:status(), cowboy_http:headers(), #http_req{}) + -> {ok, #http_req{}}. +upgrade_reply(Status, Headers, Req=#http_req{socket=Socket, transport=Transport, + pid=ReqPid, resp_state=waiting, resp_headers=RespHeaders}) -> + Head = response_head(Status, Headers, RespHeaders, [ + {<<"Connection">>, <<"Upgrade">>} + ]), + Transport:send(Socket, Head), + ReqPid ! {?MODULE, resp_sent}, + {ok, Req#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}}. + +%% Misc API. + +%% @doc Compact the request data by removing all non-system information. +%% +%% This essentially removes the host, path, query string, bindings and headers. +%% Use it when you really need to save up memory, for example when having +%% many concurrent long-running connections. +-spec compact(#http_req{}) -> #http_req{}. +compact(Req) -> + Req#http_req{host=undefined, host_info=undefined, path=undefined, + path_info=undefined, qs_vals=undefined, + bindings=undefined, headers=[], + p_headers=[], cookies=[]}. + +%% @doc Return the transport module and socket associated with a request. +%% +%% This exposes the same socket interface used internally by the HTTP protocol +%% implementation to developers that needs low level access to the socket. +%% +%% It is preferred to use this in conjuction with the stream function support +%% in `set_resp_body_fun/3' if this is used to write a response body directly +%% to the socket. This ensures that the response headers are set correctly. +-spec transport(#http_req{}) -> {ok, module(), inet:socket()}. +transport(#http_req{transport=Transport, socket=Socket}) -> + {ok, Transport, Socket}. + +%% Internal. + +-spec parse_qs(binary(), fun((binary()) -> binary())) -> + list({binary(), binary() | true}). +parse_qs(<<>>, _URLDecode) -> + []; +parse_qs(Qs, URLDecode) -> + Tokens = binary:split(Qs, <<"&">>, [global, trim]), + [case binary:split(Token, <<"=">>) of + [Token] -> {URLDecode(Token), true}; + [Name, Value] -> {URLDecode(Name), URLDecode(Value)} + end || Token <- Tokens]. + +-spec response_connection(cowboy_http:headers(), keepalive | close) + -> keepalive | close. +response_connection([], Connection) -> + Connection; +response_connection([{Name, Value}|Tail], Connection) -> + case Name of + 'Connection' -> response_connection_parse(Value); + Name when is_atom(Name) -> response_connection(Tail, Connection); + Name -> + Name2 = cowboy_bstr:to_lower(Name), + case Name2 of + <<"connection">> -> response_connection_parse(Value); + _Any -> response_connection(Tail, Connection) + end + end. + +-spec response_connection_parse(binary()) -> keepalive | close. +response_connection_parse(ReplyConn) -> + Tokens = cowboy_http:nonempty_list(ReplyConn, fun cowboy_http:token/2), + cowboy_http:connection_to_atom(Tokens). + +-spec response_head(cowboy_http:status(), cowboy_http:headers(), + cowboy_http:headers(), cowboy_http:headers()) -> iolist(). +response_head(Status, Headers, RespHeaders, DefaultHeaders) -> + StatusLine = <<"HTTP/1.1 ", (status(Status))/binary, "\r\n">>, + Headers2 = [{header_to_binary(Key), Value} || {Key, Value} <- Headers], + Headers3 = merge_headers( + merge_headers(Headers2, RespHeaders), + DefaultHeaders), + Headers4 = [[Key, <<": ">>, Value, <<"\r\n">>] + || {Key, Value} <- Headers3], + [StatusLine, Headers4, <<"\r\n">>]. + +-spec merge_headers(cowboy_http:headers(), cowboy_http:headers()) + -> cowboy_http:headers(). +merge_headers(Headers, []) -> + Headers; +merge_headers(Headers, [{Name, Value}|Tail]) -> + Headers2 = case lists:keymember(Name, 1, Headers) of + true -> Headers; + false -> Headers ++ [{Name, Value}] + end, + merge_headers(Headers2, Tail). + +-spec atom_to_connection(keepalive) -> <<_:80>>; + (close) -> <<_:40>>. +atom_to_connection(keepalive) -> + <<"keep-alive">>; +atom_to_connection(close) -> + <<"close">>. + +-spec status(cowboy_http:status()) -> binary(). +status(100) -> <<"100 Continue">>; +status(101) -> <<"101 Switching Protocols">>; +status(102) -> <<"102 Processing">>; +status(200) -> <<"200 OK">>; +status(201) -> <<"201 Created">>; +status(202) -> <<"202 Accepted">>; +status(203) -> <<"203 Non-Authoritative Information">>; +status(204) -> <<"204 No Content">>; +status(205) -> <<"205 Reset Content">>; +status(206) -> <<"206 Partial Content">>; +status(207) -> <<"207 Multi-Status">>; +status(226) -> <<"226 IM Used">>; +status(300) -> <<"300 Multiple Choices">>; +status(301) -> <<"301 Moved Permanently">>; +status(302) -> <<"302 Found">>; +status(303) -> <<"303 See Other">>; +status(304) -> <<"304 Not Modified">>; +status(305) -> <<"305 Use Proxy">>; +status(306) -> <<"306 Switch Proxy">>; +status(307) -> <<"307 Temporary Redirect">>; +status(400) -> <<"400 Bad Request">>; +status(401) -> <<"401 Unauthorized">>; +status(402) -> <<"402 Payment Required">>; +status(403) -> <<"403 Forbidden">>; +status(404) -> <<"404 Not Found">>; +status(405) -> <<"405 Method Not Allowed">>; +status(406) -> <<"406 Not Acceptable">>; +status(407) -> <<"407 Proxy Authentication Required">>; +status(408) -> <<"408 Request Timeout">>; +status(409) -> <<"409 Conflict">>; +status(410) -> <<"410 Gone">>; +status(411) -> <<"411 Length Required">>; +status(412) -> <<"412 Precondition Failed">>; +status(413) -> <<"413 Request Entity Too Large">>; +status(414) -> <<"414 Request-URI Too Long">>; +status(415) -> <<"415 Unsupported Media Type">>; +status(416) -> <<"416 Requested Range Not Satisfiable">>; +status(417) -> <<"417 Expectation Failed">>; +status(418) -> <<"418 I'm a teapot">>; +status(422) -> <<"422 Unprocessable Entity">>; +status(423) -> <<"423 Locked">>; +status(424) -> <<"424 Failed Dependency">>; +status(425) -> <<"425 Unordered Collection">>; +status(426) -> <<"426 Upgrade Required">>; +status(500) -> <<"500 Internal Server Error">>; +status(501) -> <<"501 Not Implemented">>; +status(502) -> <<"502 Bad Gateway">>; +status(503) -> <<"503 Service Unavailable">>; +status(504) -> <<"504 Gateway Timeout">>; +status(505) -> <<"505 HTTP Version Not Supported">>; +status(506) -> <<"506 Variant Also Negotiates">>; +status(507) -> <<"507 Insufficient Storage">>; +status(510) -> <<"510 Not Extended">>; +status(B) when is_binary(B) -> B. + +-spec header_to_binary(cowboy_http:header()) -> binary(). +header_to_binary('Cache-Control') -> <<"Cache-Control">>; +header_to_binary('Connection') -> <<"Connection">>; +header_to_binary('Date') -> <<"Date">>; +header_to_binary('Pragma') -> <<"Pragma">>; +header_to_binary('Transfer-Encoding') -> <<"Transfer-Encoding">>; +header_to_binary('Upgrade') -> <<"Upgrade">>; +header_to_binary('Via') -> <<"Via">>; +header_to_binary('Accept') -> <<"Accept">>; +header_to_binary('Accept-Charset') -> <<"Accept-Charset">>; +header_to_binary('Accept-Encoding') -> <<"Accept-Encoding">>; +header_to_binary('Accept-Language') -> <<"Accept-Language">>; +header_to_binary('Authorization') -> <<"Authorization">>; +header_to_binary('From') -> <<"From">>; +header_to_binary('Host') -> <<"Host">>; +header_to_binary('If-Modified-Since') -> <<"If-Modified-Since">>; +header_to_binary('If-Match') -> <<"If-Match">>; +header_to_binary('If-None-Match') -> <<"If-None-Match">>; +header_to_binary('If-Range') -> <<"If-Range">>; +header_to_binary('If-Unmodified-Since') -> <<"If-Unmodified-Since">>; +header_to_binary('Max-Forwards') -> <<"Max-Forwards">>; +header_to_binary('Proxy-Authorization') -> <<"Proxy-Authorization">>; +header_to_binary('Range') -> <<"Range">>; +header_to_binary('Referer') -> <<"Referer">>; +header_to_binary('User-Agent') -> <<"User-Agent">>; +header_to_binary('Age') -> <<"Age">>; +header_to_binary('Location') -> <<"Location">>; +header_to_binary('Proxy-Authenticate') -> <<"Proxy-Authenticate">>; +header_to_binary('Public') -> <<"Public">>; +header_to_binary('Retry-After') -> <<"Retry-After">>; +header_to_binary('Server') -> <<"Server">>; +header_to_binary('Vary') -> <<"Vary">>; +header_to_binary('Warning') -> <<"Warning">>; +header_to_binary('Www-Authenticate') -> <<"Www-Authenticate">>; +header_to_binary('Allow') -> <<"Allow">>; +header_to_binary('Content-Base') -> <<"Content-Base">>; +header_to_binary('Content-Encoding') -> <<"Content-Encoding">>; +header_to_binary('Content-Language') -> <<"Content-Language">>; +header_to_binary('Content-Length') -> <<"Content-Length">>; +header_to_binary('Content-Location') -> <<"Content-Location">>; +header_to_binary('Content-Md5') -> <<"Content-Md5">>; +header_to_binary('Content-Range') -> <<"Content-Range">>; +header_to_binary('Content-Type') -> <<"Content-Type">>; +header_to_binary('Etag') -> <<"Etag">>; +header_to_binary('Expires') -> <<"Expires">>; +header_to_binary('Last-Modified') -> <<"Last-Modified">>; +header_to_binary('Accept-Ranges') -> <<"Accept-Ranges">>; +header_to_binary('Set-Cookie') -> <<"Set-Cookie">>; +header_to_binary('Set-Cookie2') -> <<"Set-Cookie2">>; +header_to_binary('X-Forwarded-For') -> <<"X-Forwarded-For">>; +header_to_binary('Cookie') -> <<"Cookie">>; +header_to_binary('Keep-Alive') -> <<"Keep-Alive">>; +header_to_binary('Proxy-Connection') -> <<"Proxy-Connection">>; +header_to_binary(B) when is_binary(B) -> B. + +%% Tests. + +-ifdef(TEST). + +parse_qs_test_() -> + %% {Qs, Result} + Tests = [ + {<<"">>, []}, + {<<"a=b">>, [{<<"a">>, <<"b">>}]}, + {<<"aaa=bbb">>, [{<<"aaa">>, <<"bbb">>}]}, + {<<"a&b">>, [{<<"a">>, true}, {<<"b">>, true}]}, + {<<"a=b&c&d=e">>, [{<<"a">>, <<"b">>}, + {<<"c">>, true}, {<<"d">>, <<"e">>}]}, + {<<"a=b=c=d=e&f=g">>, [{<<"a">>, <<"b=c=d=e">>}, {<<"f">>, <<"g">>}]}, + {<<"a+b=c+d">>, [{<<"a b">>, <<"c d">>}]} + ], + URLDecode = fun cowboy_http:urldecode/1, + [{Qs, fun() -> R = parse_qs(Qs, URLDecode) end} || {Qs, R} <- Tests]. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_req.erl.orig b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_req.erl.orig new file mode 100644 index 0000000..bf4ac7a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_req.erl.orig @@ -0,0 +1,815 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% Copyright (c) 2011, Anthony Ramine +%% +%% 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. + +%% @doc HTTP request manipulation API. +%% +%% Almost all functions in this module return a new Req variable. +%% It should always be used instead of the one used in your function call +%% because it keeps the state of the request. It also allows Cowboy to do +%% some lazy evaluation and cache results where possible. +-module(cowboy_http_req). + +-export([ + method/1, version/1, peer/1, peer_addr/1, + host/1, host_info/1, raw_host/1, port/1, + path/1, path_info/1, raw_path/1, + qs_val/2, qs_val/3, qs_vals/1, raw_qs/1, + binding/2, binding/3, bindings/1, + header/2, header/3, headers/1, + parse_header/2, parse_header/3, + cookie/2, cookie/3, cookies/1, + meta/2, meta/3 +]). %% Request API. + +-export([ + body/1, body/2, body_qs/1, + multipart_data/1, multipart_skip/1 +]). %% Request Body API. + +-export([ + set_resp_cookie/4, set_resp_header/3, set_resp_body/2, + set_resp_body_fun/3, has_resp_header/2, has_resp_body/1, + reply/2, reply/3, reply/4, + chunked_reply/2, chunked_reply/3, chunk/2, + upgrade_reply/3 +]). %% Response API. + +-export([ + compact/1, transport/1 +]). %% Misc API. + +-include("include/http.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +%% Request API. + +%% @doc Return the HTTP method of the request. +-spec method(#http_req{}) -> {cowboy_http:method(), #http_req{}}. +method(Req) -> + {Req#http_req.method, Req}. + +%% @doc Return the HTTP version used for the request. +-spec version(#http_req{}) -> {cowboy_http:version(), #http_req{}}. +version(Req) -> + {Req#http_req.version, Req}. + +%% @doc Return the peer address and port number of the remote host. +-spec peer(#http_req{}) -> {{inet:ip_address(), inet:ip_port()}, #http_req{}}. +peer(Req=#http_req{socket=Socket, transport=Transport, peer=undefined}) -> + {ok, Peer} = Transport:peername(Socket), + {Peer, Req#http_req{peer=Peer}}; +peer(Req) -> + {Req#http_req.peer, Req}. + +%% @doc Returns the peer address calculated from headers. +-spec peer_addr(#http_req{}) -> {inet:ip_address(), #http_req{}}. +peer_addr(Req = #http_req{}) -> + {RealIp, Req1} = header(<<"X-Real-Ip">>, Req), + {ForwardedForRaw, Req2} = header(<<"X-Forwarded-For">>, Req1), + {{PeerIp, _PeerPort}, Req3} = peer(Req2), + ForwardedFor = case ForwardedForRaw of + undefined -> + undefined; + ForwardedForRaw -> + case re:run(ForwardedForRaw, "^(?[^\\,]+)", + [{capture, [first_ip], binary}]) of + {match, [FirstIp]} -> FirstIp; + _Any -> undefined + end + end, + {ok, PeerAddr} = if + is_binary(RealIp) -> inet_parse:address(binary_to_list(RealIp)); + is_binary(ForwardedFor) -> inet_parse:address(binary_to_list(ForwardedFor)); + true -> {ok, PeerIp} + end, + {PeerAddr, Req3}. + +%% @doc Return the tokens for the hostname requested. +-spec host(#http_req{}) -> {cowboy_dispatcher:tokens(), #http_req{}}. +host(Req) -> + {Req#http_req.host, Req}. + +%% @doc Return the extra host information obtained from partially matching +%% the hostname using '...'. +-spec host_info(#http_req{}) + -> {cowboy_dispatcher:tokens() | undefined, #http_req{}}. +host_info(Req) -> + {Req#http_req.host_info, Req}. + +%% @doc Return the raw host directly taken from the request. +-spec raw_host(#http_req{}) -> {binary(), #http_req{}}. +raw_host(Req) -> + {Req#http_req.raw_host, Req}. + +%% @doc Return the port used for this request. +-spec port(#http_req{}) -> {inet:ip_port(), #http_req{}}. +port(Req) -> + {Req#http_req.port, Req}. + +%% @doc Return the path segments for the path requested. +%% +%% Following RFC2396, this function may return path segments containing any +%% character, including / if, and only if, a / was escaped +%% and part of a path segment in the path requested. +-spec path(#http_req{}) -> {cowboy_dispatcher:tokens(), #http_req{}}. +path(Req) -> + {Req#http_req.path, Req}. + +%% @doc Return the extra path information obtained from partially matching +%% the patch using '...'. +-spec path_info(#http_req{}) + -> {cowboy_dispatcher:tokens() | undefined, #http_req{}}. +path_info(Req) -> + {Req#http_req.path_info, Req}. + +%% @doc Return the raw path directly taken from the request. +-spec raw_path(#http_req{}) -> {binary(), #http_req{}}. +raw_path(Req) -> + {Req#http_req.raw_path, Req}. + +%% @equiv qs_val(Name, Req, undefined) +-spec qs_val(binary(), #http_req{}) + -> {binary() | true | undefined, #http_req{}}. +qs_val(Name, Req) when is_binary(Name) -> + qs_val(Name, Req, undefined). + +%% @doc Return the query string value for the given key, or a default if +%% missing. +qs_val(Name, Req=#http_req{raw_qs=RawQs, qs_vals=undefined, + urldecode={URLDecFun, URLDecArg}}, Default) when is_binary(Name) -> + QsVals = parse_qs(RawQs, fun(Bin) -> URLDecFun(Bin, URLDecArg) end), + qs_val(Name, Req#http_req{qs_vals=QsVals}, Default); +qs_val(Name, Req, Default) -> + case lists:keyfind(Name, 1, Req#http_req.qs_vals) of + {Name, Value} -> {Value, Req}; + false -> {Default, Req} + end. + +%% @doc Return the full list of query string values. +-spec qs_vals(#http_req{}) -> {list({binary(), binary() | true}), #http_req{}}. +qs_vals(Req=#http_req{raw_qs=RawQs, qs_vals=undefined, + urldecode={URLDecFun, URLDecArg}}) -> + QsVals = parse_qs(RawQs, fun(Bin) -> URLDecFun(Bin, URLDecArg) end), + qs_vals(Req#http_req{qs_vals=QsVals}); +qs_vals(Req=#http_req{qs_vals=QsVals}) -> + {QsVals, Req}. + +%% @doc Return the raw query string directly taken from the request. +-spec raw_qs(#http_req{}) -> {binary(), #http_req{}}. +raw_qs(Req) -> + {Req#http_req.raw_qs, Req}. + +%% @equiv binding(Name, Req, undefined) +-spec binding(atom(), #http_req{}) -> {binary() | undefined, #http_req{}}. +binding(Name, Req) when is_atom(Name) -> + binding(Name, Req, undefined). + +%% @doc Return the binding value for the given key obtained when matching +%% the host and path against the dispatch list, or a default if missing. +binding(Name, Req, Default) when is_atom(Name) -> + case lists:keyfind(Name, 1, Req#http_req.bindings) of + {Name, Value} -> {Value, Req}; + false -> {Default, Req} + end. + +%% @doc Return the full list of binding values. +-spec bindings(#http_req{}) -> {list({atom(), binary()}), #http_req{}}. +bindings(Req) -> + {Req#http_req.bindings, Req}. + +%% @equiv header(Name, Req, undefined) +-spec header(atom() | binary(), #http_req{}) + -> {binary() | undefined, #http_req{}}. +header(Name, Req) when is_atom(Name) orelse is_binary(Name) -> + header(Name, Req, undefined). + +%% @doc Return the header value for the given key, or a default if missing. +header(Name, Req, Default) when is_atom(Name) orelse is_binary(Name) -> + case lists:keyfind(Name, 1, Req#http_req.headers) of + {Name, Value} -> {Value, Req}; + false -> {Default, Req} + end. + +%% @doc Return the full list of headers. +-spec headers(#http_req{}) -> {cowboy_http:headers(), #http_req{}}. +headers(Req) -> + {Req#http_req.headers, Req}. + +%% @doc Semantically parse headers. +%% +%% When the value isn't found, a proper default value for the type +%% returned is used as a return value. +%% @see parse_header/3 +-spec parse_header(cowboy_http:header(), #http_req{}) + -> {any(), #http_req{}} | {error, badarg}. +parse_header(Name, Req=#http_req{p_headers=PHeaders}) -> + case lists:keyfind(Name, 1, PHeaders) of + false -> parse_header(Name, Req, parse_header_default(Name)); + {Name, Value} -> {Value, Req} + end. + +%% @doc Default values for semantic header parsing. +-spec parse_header_default(cowboy_http:header()) -> any(). +parse_header_default('Connection') -> []; +parse_header_default(_Name) -> undefined. + +%% @doc Semantically parse headers. +%% +%% When the header is unknown, the value is returned directly without parsing. +-spec parse_header(cowboy_http:header(), #http_req{}, any()) + -> {any(), #http_req{}} | {error, badarg}. +parse_header(Name, Req, Default) when Name =:= 'Accept' -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:list(Value, fun cowboy_http:media_range/2) + end); +parse_header(Name, Req, Default) when Name =:= 'Accept-Charset' -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:nonempty_list(Value, fun cowboy_http:conneg/2) + end); +parse_header(Name, Req, Default) when Name =:= 'Accept-Encoding' -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:list(Value, fun cowboy_http:conneg/2) + end); +parse_header(Name, Req, Default) when Name =:= 'Accept-Language' -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:nonempty_list(Value, fun cowboy_http:language_range/2) + end); +parse_header(Name, Req, Default) when Name =:= 'Connection' -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2) + end); +parse_header(Name, Req, Default) when Name =:= 'Content-Length' -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:digits(Value) + end); +parse_header(Name, Req, Default) when Name =:= 'Content-Type' -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:content_type(Value) + end); +parse_header(Name, Req, Default) + when Name =:= 'If-Match'; Name =:= 'If-None-Match' -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:entity_tag_match(Value) + end); +parse_header(Name, Req, Default) + when Name =:= 'If-Modified-Since'; Name =:= 'If-Unmodified-Since' -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:http_date(Value) + end); +parse_header(Name, Req, Default) when Name =:= 'Upgrade' -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2) + end); +parse_header(Name, Req, Default) -> + {Value, Req2} = header(Name, Req, Default), + {undefined, Value, Req2}. + +parse_header(Name, Req=#http_req{p_headers=PHeaders}, Default, Fun) -> + case header(Name, Req) of + {undefined, Req2} -> + {Default, Req2#http_req{p_headers=[{Name, Default}|PHeaders]}}; + {Value, Req2} -> + case Fun(Value) of + {error, badarg} -> + {error, badarg}; + P -> + {P, Req2#http_req{p_headers=[{Name, P}|PHeaders]}} + end + end. + +%% @equiv cookie(Name, Req, undefined) +-spec cookie(binary(), #http_req{}) + -> {binary() | true | undefined, #http_req{}}. +cookie(Name, Req) when is_binary(Name) -> + cookie(Name, Req, undefined). + +%% @doc Return the cookie value for the given key, or a default if +%% missing. +cookie(Name, Req=#http_req{cookies=undefined}, Default) when is_binary(Name) -> + case header('Cookie', Req) of + {undefined, Req2} -> + {Default, Req2#http_req{cookies=[]}}; + {RawCookie, Req2} -> + Cookies = cowboy_cookies:parse_cookie(RawCookie), + cookie(Name, Req2#http_req{cookies=Cookies}, Default) + end; +cookie(Name, Req, Default) -> + case lists:keyfind(Name, 1, Req#http_req.cookies) of + {Name, Value} -> {Value, Req}; + false -> {Default, Req} + end. + +%% @doc Return the full list of cookie values. +-spec cookies(#http_req{}) -> {list({binary(), binary() | true}), #http_req{}}. +cookies(Req=#http_req{cookies=undefined}) -> + case header('Cookie', Req) of + {undefined, Req2} -> + {[], Req2#http_req{cookies=[]}}; + {RawCookie, Req2} -> + Cookies = cowboy_cookies:parse_cookie(RawCookie), + cookies(Req2#http_req{cookies=Cookies}) + end; +cookies(Req=#http_req{cookies=Cookies}) -> + {Cookies, Req}. + +%% @equiv meta(Name, Req, undefined) +-spec meta(atom(), #http_req{}) -> {any() | undefined, #http_req{}}. +meta(Name, Req) -> + meta(Name, Req, undefined). + +%% @doc Return metadata information about the request. +%% +%% Metadata information varies from one protocol to another. Websockets +%% would define the protocol version here, while REST would use it to +%% indicate which media type, language and charset were retained. +-spec meta(atom(), #http_req{}, any()) -> {any(), #http_req{}}. +meta(Name, Req, Default) -> + case lists:keyfind(Name, 1, Req#http_req.meta) of + {Name, Value} -> {Value, Req}; + false -> {Default, Req} + end. + +%% Request Body API. + +%% @doc Return the full body sent with the request, or {error, badarg} +%% if no Content-Length is available. +%% @todo We probably want to allow a max length. +%% @todo Add multipart support to this function. +-spec body(#http_req{}) -> {ok, binary(), #http_req{}} | {error, atom()}. +body(Req) -> + {Length, Req2} = cowboy_http_req:parse_header('Content-Length', Req), + case Length of + undefined -> {error, badarg}; + {error, badarg} -> {error, badarg}; + _Any -> + body(Length, Req2) + end. + +%% @doc Return Length bytes of the request body. +%% +%% You probably shouldn't be calling this function directly, as it expects the +%% Length argument to be the full size of the body, and will consider +%% the body to be fully read from the socket. +%% @todo We probably want to configure the timeout. +-spec body(non_neg_integer(), #http_req{}) + -> {ok, binary(), #http_req{}} | {error, atom()}. +body(Length, Req=#http_req{body_state=waiting, buffer=Buffer}) + when is_integer(Length) andalso Length =< byte_size(Buffer) -> + << Body:Length/binary, Rest/bits >> = Buffer, + {ok, Body, Req#http_req{body_state=done, buffer=Rest}}; +body(Length, Req=#http_req{socket=Socket, transport=Transport, + body_state=waiting, buffer=Buffer}) -> + case Transport:recv(Socket, Length - byte_size(Buffer), 5000) of + {ok, Body} -> {ok, << Buffer/binary, Body/binary >>, + Req#http_req{body_state=done, buffer= <<>>}}; + {error, Reason} -> {error, Reason} + end. + +%% @doc Return the full body sent with the reqest, parsed as an +%% application/x-www-form-urlencoded string. Essentially a POST query string. +-spec body_qs(#http_req{}) -> {list({binary(), binary() | true}), #http_req{}}. +body_qs(Req=#http_req{urldecode={URLDecFun, URLDecArg}}) -> + {ok, Body, Req2} = body(Req), + {parse_qs(Body, fun(Bin) -> URLDecFun(Bin, URLDecArg) end), Req2}. + +%% Multipart Request API. + +%% @doc Return data from the multipart parser. +%% +%% Use this function for multipart streaming. For each part in the request, +%% this function returns {headers, Headers} followed by a sequence of +%% {data, Data} tuples and finally end_of_part. When there +%% is no part to parse anymore, eof is returned. +%% +%% If the request Content-Type is not a multipart one, {error, badarg} +%% is returned. +-spec multipart_data(#http_req{}) + -> {{headers, cowboy_http:headers()} + | {data, binary()} | end_of_part | eof, + #http_req{}}. +multipart_data(Req=#http_req{body_state=waiting}) -> + {{<<"multipart">>, _SubType, Params}, Req2} = + parse_header('Content-Type', Req), + {_, Boundary} = lists:keyfind(<<"boundary">>, 1, Params), + {Length, Req3=#http_req{buffer=Buffer}} = + parse_header('Content-Length', Req2), + multipart_data(Req3, Length, cowboy_multipart:parser(Boundary), Buffer); +multipart_data(Req=#http_req{body_state={multipart, Length, Cont}}) -> + multipart_data(Req, Length, Cont()); +multipart_data(Req=#http_req{body_state=done}) -> + {eof, Req}. + +multipart_data(Req, Length, Parser, Buffer) when byte_size(Buffer) >= Length -> + << Data:Length/binary, Rest/binary >> = Buffer, + multipart_data(Req#http_req{buffer=Rest}, 0, Parser(Data)); +multipart_data(Req, Length, Parser, Buffer) -> + NewLength = Length - byte_size(Buffer), + multipart_data(Req#http_req{buffer= <<>>}, NewLength, Parser(Buffer)). + +multipart_data(Req, Length, {headers, Headers, Cont}) -> + {{headers, Headers}, Req#http_req{body_state={multipart, Length, Cont}}}; +multipart_data(Req, Length, {body, Data, Cont}) -> + {{body, Data}, Req#http_req{body_state={multipart, Length, Cont}}}; +multipart_data(Req, Length, {end_of_part, Cont}) -> + {end_of_part, Req#http_req{body_state={multipart, Length, Cont}}}; +multipart_data(Req, 0, eof) -> + {eof, Req#http_req{body_state=done}}; +multipart_data(Req=#http_req{socket=Socket, transport=Transport}, + Length, eof) -> + {ok, _Data} = Transport:recv(Socket, Length, 5000), + {eof, Req#http_req{body_state=done}}; +multipart_data(Req=#http_req{socket=Socket, transport=Transport}, + Length, {more, Parser}) when Length > 0 -> + case Transport:recv(Socket, 0, 5000) of + {ok, << Data:Length/binary, Buffer/binary >>} -> + multipart_data(Req#http_req{buffer=Buffer}, 0, Parser(Data)); + {ok, Data} -> + multipart_data(Req, Length - byte_size(Data), Parser(Data)) + end. + +%% @doc Skip a part returned by the multipart parser. +%% +%% This function repeatedly calls multipart_data/1 until +%% end_of_part or eof is parsed. +multipart_skip(Req) -> + case multipart_data(Req) of + {end_of_part, Req2} -> {ok, Req2}; + {eof, Req2} -> {ok, Req2}; + {_Other, Req2} -> multipart_skip(Req2) + end. + +%% Response API. + +%% @doc Add a cookie header to the response. +-spec set_resp_cookie(binary(), binary(), [cowboy_cookies:cookie_option()], + #http_req{}) -> {ok, #http_req{}}. +set_resp_cookie(Name, Value, Options, Req) -> + {HeaderName, HeaderValue} = cowboy_cookies:cookie(Name, Value, Options), + set_resp_header(HeaderName, HeaderValue, Req). + +%% @doc Add a header to the response. +set_resp_header(Name, Value, Req=#http_req{resp_headers=RespHeaders}) -> + NameBin = header_to_binary(Name), + {ok, Req#http_req{resp_headers=[{NameBin, Value}|RespHeaders]}}. + +%% @doc Add a body to the response. +%% +%% The body set here is ignored if the response is later sent using +%% anything other than reply/2 or reply/3. The response body is expected +%% to be a binary or an iolist. +set_resp_body(Body, Req) -> + {ok, Req#http_req{resp_body=Body}}. + + +%% @doc Add a body function to the response. +%% +%% The response body may also be set to a content-length - stream-function pair. +%% If the response body is of this type normal response headers will be sent. +%% After the response headers has been sent the body function is applied. +%% The body function is expected to write the response body directly to the +%% socket using the transport module. +%% +%% If the body function crashes while writing the response body or writes fewer +%% bytes than declared the behaviour is undefined. The body set here is ignored +%% if the response is later sent using anything other than `reply/2' or +%% `reply/3'. +%% +%% @see cowboy_http_req:transport/1. +-spec set_resp_body_fun(non_neg_integer(), fun(() -> {sent, non_neg_integer()}), + #http_req{}) -> {ok, #http_req{}}. +set_resp_body_fun(StreamLen, StreamFun, Req) -> + {ok, Req#http_req{resp_body={StreamLen, StreamFun}}}. + + +%% @doc Return whether the given header has been set for the response. +has_resp_header(Name, #http_req{resp_headers=RespHeaders}) -> + NameBin = header_to_binary(Name), + lists:keymember(NameBin, 1, RespHeaders). + +%% @doc Return whether a body has been set for the response. +has_resp_body(#http_req{resp_body={Length, _}}) -> + Length > 0; +has_resp_body(#http_req{resp_body=RespBody}) -> + iolist_size(RespBody) > 0. + +%% @equiv reply(Status, [], [], Req) +-spec reply(cowboy_http:status(), #http_req{}) -> {ok, #http_req{}}. +reply(Status, Req=#http_req{resp_body=Body}) -> + reply(Status, [], Body, Req). + +%% @equiv reply(Status, Headers, [], Req) +-spec reply(cowboy_http:status(), cowboy_http:headers(), #http_req{}) + -> {ok, #http_req{}}. +reply(Status, Headers, Req=#http_req{resp_body=Body}) -> + reply(Status, Headers, Body, Req). + +%% @doc Send a reply to the client. +reply(Status, Headers, Body, Req=#http_req{socket=Socket, + transport=Transport, connection=Connection, pid=ReqPid, + method=Method, resp_state=waiting, resp_headers=RespHeaders}) -> + RespConn = response_connection(Headers, Connection), + ContentLen = case Body of {CL, _} -> CL; _ -> iolist_size(Body) end, + Head = response_head(Status, Headers, RespHeaders, [ + {<<"Connection">>, atom_to_connection(Connection)}, + {<<"Content-Length">>, integer_to_list(ContentLen)}, + {<<"Date">>, cowboy_clock:rfc1123()}, + {<<"Server">>, <<"Cowboy">>} + ]), + case {Method, Body} of + {'HEAD', _} -> Transport:send(Socket, Head); + {_, {_, StreamFun}} -> Transport:send(Socket, Head), StreamFun(); + {_, _} -> Transport:send(Socket, [Head, Body]) + end, + ReqPid ! {?MODULE, resp_sent}, + {ok, Req#http_req{connection=RespConn, resp_state=done, + resp_headers=[], resp_body= <<>>}}. + +%% @equiv chunked_reply(Status, [], Req) +-spec chunked_reply(cowboy_http:status(), #http_req{}) -> {ok, #http_req{}}. +chunked_reply(Status, Req) -> + chunked_reply(Status, [], Req). + +%% @doc Initiate the sending of a chunked reply to the client. +%% @see cowboy_http_req:chunk/2 +-spec chunked_reply(cowboy_http:status(), cowboy_http:headers(), #http_req{}) + -> {ok, #http_req{}}. +chunked_reply(Status, Headers, Req=#http_req{socket=Socket, + transport=Transport, connection=Connection, pid=ReqPid, + resp_state=waiting, resp_headers=RespHeaders}) -> + RespConn = response_connection(Headers, Connection), + Head = response_head(Status, Headers, RespHeaders, [ + {<<"Connection">>, atom_to_connection(Connection)}, + {<<"Transfer-Encoding">>, <<"chunked">>}, + {<<"Date">>, cowboy_clock:rfc1123()}, + {<<"Server">>, <<"Cowboy">>} + ]), + Transport:send(Socket, Head), + ReqPid ! {?MODULE, resp_sent}, + {ok, Req#http_req{connection=RespConn, resp_state=chunks, + resp_headers=[], resp_body= <<>>}}. + +%% @doc Send a chunk of data. +%% +%% A chunked reply must have been initiated before calling this function. +chunk(_Data, #http_req{socket=_Socket, transport=_Transport, method='HEAD'}) -> + ok; +chunk(Data, #http_req{socket=Socket, transport=Transport, resp_state=chunks}) -> + Transport:send(Socket, [erlang:integer_to_list(iolist_size(Data), 16), + <<"\r\n">>, Data, <<"\r\n">>]). + +%% @doc Send an upgrade reply. +%% @private +-spec upgrade_reply(cowboy_http:status(), cowboy_http:headers(), #http_req{}) + -> {ok, #http_req{}}. +upgrade_reply(Status, Headers, Req=#http_req{socket=Socket, transport=Transport, + pid=ReqPid, resp_state=waiting, resp_headers=RespHeaders}) -> + Head = response_head(Status, Headers, RespHeaders, [ + {<<"Connection">>, <<"Upgrade">>} + ]), + Transport:send(Socket, Head), + ReqPid ! {?MODULE, resp_sent}, + {ok, Req#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}}. + +%% Misc API. + +%% @doc Compact the request data by removing all non-system information. +%% +%% This essentially removes the host, path, query string, bindings and headers. +%% Use it when you really need to save up memory, for example when having +%% many concurrent long-running connections. +-spec compact(#http_req{}) -> #http_req{}. +compact(Req) -> + Req#http_req{host=undefined, host_info=undefined, path=undefined, + path_info=undefined, qs_vals=undefined, + bindings=undefined, headers=[], + p_headers=[], cookies=[]}. + +%% @doc Return the transport module and socket associated with a request. +%% +%% This exposes the same socket interface used internally by the HTTP protocol +%% implementation to developers that needs low level access to the socket. +%% +%% It is preferred to use this in conjuction with the stream function support +%% in `set_resp_body_fun/3' if this is used to write a response body directly +%% to the socket. This ensures that the response headers are set correctly. +-spec transport(#http_req{}) -> {ok, module(), inet:socket()}. +transport(#http_req{transport=Transport, socket=Socket}) -> + {ok, Transport, Socket}. + +%% Internal. + +-spec parse_qs(binary(), fun((binary()) -> binary())) -> + list({binary(), binary() | true}). +parse_qs(<<>>, _URLDecode) -> + []; +parse_qs(Qs, URLDecode) -> + Tokens = binary:split(Qs, <<"&">>, [global, trim]), + [case binary:split(Token, <<"=">>) of + [Token] -> {URLDecode(Token), true}; + [Name, Value] -> {URLDecode(Name), URLDecode(Value)} + end || Token <- Tokens]. + +-spec response_connection(cowboy_http:headers(), keepalive | close) + -> keepalive | close. +response_connection([], Connection) -> + Connection; +response_connection([{Name, Value}|Tail], Connection) -> + case Name of + 'Connection' -> response_connection_parse(Value); + Name when is_atom(Name) -> response_connection(Tail, Connection); + Name -> + Name2 = cowboy_bstr:to_lower(Name), + case Name2 of + <<"connection">> -> response_connection_parse(Value); + _Any -> response_connection(Tail, Connection) + end + end. + +-spec response_connection_parse(binary()) -> keepalive | close. +response_connection_parse(ReplyConn) -> + Tokens = cowboy_http:nonempty_list(ReplyConn, fun cowboy_http:token/2), + cowboy_http:connection_to_atom(Tokens). + +-spec response_head(cowboy_http:status(), cowboy_http:headers(), + cowboy_http:headers(), cowboy_http:headers()) -> iolist(). +response_head(Status, Headers, RespHeaders, DefaultHeaders) -> + StatusLine = <<"HTTP/1.1 ", (status(Status))/binary, "\r\n">>, + Headers2 = [{header_to_binary(Key), Value} || {Key, Value} <- Headers], + Headers3 = merge_headers( + merge_headers(Headers2, RespHeaders), + DefaultHeaders), + Headers4 = [[Key, <<": ">>, Value, <<"\r\n">>] + || {Key, Value} <- Headers3], + [StatusLine, Headers4, <<"\r\n">>]. + +-spec merge_headers(cowboy_http:headers(), cowboy_http:headers()) + -> cowboy_http:headers(). +merge_headers(Headers, []) -> + Headers; +merge_headers(Headers, [{Name, Value}|Tail]) -> + Headers2 = case lists:keymember(Name, 1, Headers) of + true -> Headers; + false -> Headers ++ [{Name, Value}] + end, + merge_headers(Headers2, Tail). + +-spec atom_to_connection(keepalive) -> <<_:80>>; + (close) -> <<_:40>>. +atom_to_connection(keepalive) -> + <<"keep-alive">>; +atom_to_connection(close) -> + <<"close">>. + +-spec status(cowboy_http:status()) -> binary(). +status(100) -> <<"100 Continue">>; +status(101) -> <<"101 Switching Protocols">>; +status(102) -> <<"102 Processing">>; +status(200) -> <<"200 OK">>; +status(201) -> <<"201 Created">>; +status(202) -> <<"202 Accepted">>; +status(203) -> <<"203 Non-Authoritative Information">>; +status(204) -> <<"204 No Content">>; +status(205) -> <<"205 Reset Content">>; +status(206) -> <<"206 Partial Content">>; +status(207) -> <<"207 Multi-Status">>; +status(226) -> <<"226 IM Used">>; +status(300) -> <<"300 Multiple Choices">>; +status(301) -> <<"301 Moved Permanently">>; +status(302) -> <<"302 Found">>; +status(303) -> <<"303 See Other">>; +status(304) -> <<"304 Not Modified">>; +status(305) -> <<"305 Use Proxy">>; +status(306) -> <<"306 Switch Proxy">>; +status(307) -> <<"307 Temporary Redirect">>; +status(400) -> <<"400 Bad Request">>; +status(401) -> <<"401 Unauthorized">>; +status(402) -> <<"402 Payment Required">>; +status(403) -> <<"403 Forbidden">>; +status(404) -> <<"404 Not Found">>; +status(405) -> <<"405 Method Not Allowed">>; +status(406) -> <<"406 Not Acceptable">>; +status(407) -> <<"407 Proxy Authentication Required">>; +status(408) -> <<"408 Request Timeout">>; +status(409) -> <<"409 Conflict">>; +status(410) -> <<"410 Gone">>; +status(411) -> <<"411 Length Required">>; +status(412) -> <<"412 Precondition Failed">>; +status(413) -> <<"413 Request Entity Too Large">>; +status(414) -> <<"414 Request-URI Too Long">>; +status(415) -> <<"415 Unsupported Media Type">>; +status(416) -> <<"416 Requested Range Not Satisfiable">>; +status(417) -> <<"417 Expectation Failed">>; +status(418) -> <<"418 I'm a teapot">>; +status(422) -> <<"422 Unprocessable Entity">>; +status(423) -> <<"423 Locked">>; +status(424) -> <<"424 Failed Dependency">>; +status(425) -> <<"425 Unordered Collection">>; +status(426) -> <<"426 Upgrade Required">>; +status(500) -> <<"500 Internal Server Error">>; +status(501) -> <<"501 Not Implemented">>; +status(502) -> <<"502 Bad Gateway">>; +status(503) -> <<"503 Service Unavailable">>; +status(504) -> <<"504 Gateway Timeout">>; +status(505) -> <<"505 HTTP Version Not Supported">>; +status(506) -> <<"506 Variant Also Negotiates">>; +status(507) -> <<"507 Insufficient Storage">>; +status(510) -> <<"510 Not Extended">>; +status(B) when is_binary(B) -> B. + +-spec header_to_binary(cowboy_http:header()) -> binary(). +header_to_binary('Cache-Control') -> <<"Cache-Control">>; +header_to_binary('Connection') -> <<"Connection">>; +header_to_binary('Date') -> <<"Date">>; +header_to_binary('Pragma') -> <<"Pragma">>; +header_to_binary('Transfer-Encoding') -> <<"Transfer-Encoding">>; +header_to_binary('Upgrade') -> <<"Upgrade">>; +header_to_binary('Via') -> <<"Via">>; +header_to_binary('Accept') -> <<"Accept">>; +header_to_binary('Accept-Charset') -> <<"Accept-Charset">>; +header_to_binary('Accept-Encoding') -> <<"Accept-Encoding">>; +header_to_binary('Accept-Language') -> <<"Accept-Language">>; +header_to_binary('Authorization') -> <<"Authorization">>; +header_to_binary('From') -> <<"From">>; +header_to_binary('Host') -> <<"Host">>; +header_to_binary('If-Modified-Since') -> <<"If-Modified-Since">>; +header_to_binary('If-Match') -> <<"If-Match">>; +header_to_binary('If-None-Match') -> <<"If-None-Match">>; +header_to_binary('If-Range') -> <<"If-Range">>; +header_to_binary('If-Unmodified-Since') -> <<"If-Unmodified-Since">>; +header_to_binary('Max-Forwards') -> <<"Max-Forwards">>; +header_to_binary('Proxy-Authorization') -> <<"Proxy-Authorization">>; +header_to_binary('Range') -> <<"Range">>; +header_to_binary('Referer') -> <<"Referer">>; +header_to_binary('User-Agent') -> <<"User-Agent">>; +header_to_binary('Age') -> <<"Age">>; +header_to_binary('Location') -> <<"Location">>; +header_to_binary('Proxy-Authenticate') -> <<"Proxy-Authenticate">>; +header_to_binary('Public') -> <<"Public">>; +header_to_binary('Retry-After') -> <<"Retry-After">>; +header_to_binary('Server') -> <<"Server">>; +header_to_binary('Vary') -> <<"Vary">>; +header_to_binary('Warning') -> <<"Warning">>; +header_to_binary('Www-Authenticate') -> <<"Www-Authenticate">>; +header_to_binary('Allow') -> <<"Allow">>; +header_to_binary('Content-Base') -> <<"Content-Base">>; +header_to_binary('Content-Encoding') -> <<"Content-Encoding">>; +header_to_binary('Content-Language') -> <<"Content-Language">>; +header_to_binary('Content-Length') -> <<"Content-Length">>; +header_to_binary('Content-Location') -> <<"Content-Location">>; +header_to_binary('Content-Md5') -> <<"Content-Md5">>; +header_to_binary('Content-Range') -> <<"Content-Range">>; +header_to_binary('Content-Type') -> <<"Content-Type">>; +header_to_binary('Etag') -> <<"Etag">>; +header_to_binary('Expires') -> <<"Expires">>; +header_to_binary('Last-Modified') -> <<"Last-Modified">>; +header_to_binary('Accept-Ranges') -> <<"Accept-Ranges">>; +header_to_binary('Set-Cookie') -> <<"Set-Cookie">>; +header_to_binary('Set-Cookie2') -> <<"Set-Cookie2">>; +header_to_binary('X-Forwarded-For') -> <<"X-Forwarded-For">>; +header_to_binary('Cookie') -> <<"Cookie">>; +header_to_binary('Keep-Alive') -> <<"Keep-Alive">>; +header_to_binary('Proxy-Connection') -> <<"Proxy-Connection">>; +header_to_binary(B) when is_binary(B) -> B. + +%% Tests. + +-ifdef(TEST). + +parse_qs_test_() -> + %% {Qs, Result} + Tests = [ + {<<"">>, []}, + {<<"a=b">>, [{<<"a">>, <<"b">>}]}, + {<<"aaa=bbb">>, [{<<"aaa">>, <<"bbb">>}]}, + {<<"a&b">>, [{<<"a">>, true}, {<<"b">>, true}]}, + {<<"a=b&c&d=e">>, [{<<"a">>, <<"b">>}, + {<<"c">>, true}, {<<"d">>, <<"e">>}]}, + {<<"a=b=c=d=e&f=g">>, [{<<"a">>, <<"b=c=d=e">>}, {<<"f">>, <<"g">>}]}, + {<<"a+b=c+d">>, [{<<"a b">>, <<"c d">>}]} + ], + URLDecode = fun cowboy_http:urldecode/1, + [{Qs, fun() -> R = parse_qs(Qs, URLDecode) end} || {Qs, R} <- Tests]. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_rest.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_rest.erl new file mode 100644 index 0000000..e6cc6ff --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_rest.erl @@ -0,0 +1,905 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% +%% 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. + +%% @doc Experimental REST protocol implementation. +%% +%% Based on the Webmachine Diagram from Alan Dean and Justin Sheehy, which +%% can be found in the Webmachine source tree, and on the Webmachine +%% documentation available at http://wiki.basho.com/Webmachine.html +%% at the time of writing. +-module(cowboy_http_rest). +-export([upgrade/4]). + +-record(state, { + %% Handler. + handler :: atom(), + handler_state :: any(), + + %% Media type. + content_types_p = [] :: + [{{binary(), binary(), [{binary(), binary()}]}, atom()}], + content_type_a :: undefined + | {{binary(), binary(), [{binary(), binary()}]}, atom()}, + + %% Language. + languages_p = [] :: [binary()], + language_a :: undefined | binary(), + + %% Charset. + charsets_p = [] :: [binary()], + charset_a :: undefined | binary(), + + %% Cached resource calls. + etag :: undefined | no_call | binary(), + last_modified :: undefined | no_call | calendar:datetime(), + expires :: undefined | no_call | calendar:datetime() +}). + +-include("include/http.hrl"). + +%% @doc Upgrade a HTTP request to the REST protocol. +%% +%% You do not need to call this function manually. To upgrade to the REST +%% protocol, you simply need to return {upgrade, protocol, {@module}} +%% in your cowboy_http_handler:init/3 handler function. +-spec upgrade(pid(), module(), any(), #http_req{}) + -> {ok, #http_req{}} | close. +upgrade(_ListenerPid, Handler, Opts, Req) -> + try + case erlang:function_exported(Handler, rest_init, 2) of + true -> + case Handler:rest_init(Req, Opts) of + {ok, Req2, HandlerState} -> + service_available(Req2, #state{handler=Handler, + handler_state=HandlerState}) + end; + false -> + service_available(Req, #state{handler=Handler}) + end + catch Class:Reason -> + error_logger:error_msg( + "** Handler ~p terminating in rest_init/3~n" + " for the reason ~p:~p~n** Options were ~p~n" + "** Request was ~p~n** Stacktrace: ~p~n~n", + [Handler, Class, Reason, Opts, Req, erlang:get_stacktrace()]), + {ok, _Req2} = cowboy_http_req:reply(500, Req), + close + end. + +service_available(Req, State) -> + expect(Req, State, service_available, true, fun known_methods/2, 503). + +%% known_methods/2 should return a list of atoms or binary methods. +known_methods(Req=#http_req{method=Method}, State) -> + case call(Req, State, known_methods) of + no_call when Method =:= 'HEAD'; Method =:= 'GET'; Method =:= 'POST'; + Method =:= 'PUT'; Method =:= 'DELETE'; Method =:= 'TRACE'; + Method =:= 'CONNECT'; Method =:= 'OPTIONS' -> + next(Req, State, fun uri_too_long/2); + no_call -> + next(Req, State, 501); + {halt, Req2, HandlerState} -> + terminate(Req2, State#state{handler_state=HandlerState}); + {List, Req2, HandlerState} -> + State2 = State#state{handler_state=HandlerState}, + case lists:member(Method, List) of + true -> next(Req2, State2, fun uri_too_long/2); + false -> next(Req2, State2, 501) + end + end. + +uri_too_long(Req, State) -> + expect(Req, State, uri_too_long, false, fun allowed_methods/2, 414). + +%% allowed_methods/2 should return a list of atoms or binary methods. +allowed_methods(Req=#http_req{method=Method}, State) -> + case call(Req, State, allowed_methods) of + no_call when Method =:= 'HEAD'; Method =:= 'GET' -> + next(Req, State, fun malformed_request/2); + no_call -> + method_not_allowed(Req, State, ['GET', 'HEAD']); + {halt, Req2, HandlerState} -> + terminate(Req2, State#state{handler_state=HandlerState}); + {List, Req2, HandlerState} -> + State2 = State#state{handler_state=HandlerState}, + case lists:member(Method, List) of + true -> next(Req2, State2, fun malformed_request/2); + false -> method_not_allowed(Req2, State2, List) + end + end. + +method_not_allowed(Req, State, Methods) -> + {ok, Req2} = cowboy_http_req:set_resp_header( + <<"Allow">>, method_not_allowed_build(Methods, []), Req), + respond(Req2, State, 405). + +method_not_allowed_build([], []) -> + <<>>; +method_not_allowed_build([], [_Ignore|Acc]) -> + lists:reverse(Acc); +method_not_allowed_build([Method|Tail], Acc) when is_atom(Method) -> + Method2 = list_to_binary(atom_to_list(Method)), + method_not_allowed_build(Tail, [<<", ">>, Method2|Acc]); +method_not_allowed_build([Method|Tail], Acc) -> + method_not_allowed_build(Tail, [<<", ">>, Method|Acc]). + +malformed_request(Req, State) -> + expect(Req, State, malformed_request, false, fun is_authorized/2, 400). + +%% is_authorized/2 should return true or {false, WwwAuthenticateHeader}. +is_authorized(Req, State) -> + case call(Req, State, is_authorized) of + no_call -> + forbidden(Req, State); + {halt, Req2, HandlerState} -> + terminate(Req2, State#state{handler_state=HandlerState}); + {true, Req2, HandlerState} -> + forbidden(Req2, State#state{handler_state=HandlerState}); + {{false, AuthHead}, Req2, HandlerState} -> + {ok, Req3} = cowboy_http_req:set_resp_header( + <<"Www-Authenticate">>, AuthHead, Req2), + respond(Req3, State#state{handler_state=HandlerState}, 401) + end. + +forbidden(Req, State) -> + expect(Req, State, forbidden, false, fun valid_content_headers/2, 403). + +valid_content_headers(Req, State) -> + expect(Req, State, valid_content_headers, true, + fun known_content_type/2, 501). + +known_content_type(Req, State) -> + expect(Req, State, known_content_type, true, + fun valid_entity_length/2, 413). + +valid_entity_length(Req, State) -> + expect(Req, State, valid_entity_length, true, fun options/2, 413). + +%% If you need to add additional headers to the response at this point, +%% you should do it directly in the options/2 call using set_resp_headers. +options(Req=#http_req{method='OPTIONS'}, State) -> + case call(Req, State, options) of + {halt, Req2, HandlerState} -> + terminate(Req2, State#state{handler_state=HandlerState}); + {ok, Req2, HandlerState} -> + respond(Req2, State#state{handler_state=HandlerState}, 200) + end; +options(Req, State) -> + content_types_provided(Req, State). + +%% content_types_provided/2 should return a list of content types and their +%% associated callback function as a tuple: {{Type, SubType, Params}, Fun}. +%% Type and SubType are the media type as binary. Params is a list of +%% Key/Value tuple, with Key and Value a binary. Fun is the name of the +%% callback that will be used to return the content of the response. It is +%% given as an atom. +%% +%% An example of such return value would be: +%% {{<<"text">>, <<"html">>, []}, to_html} +%% +%% Note that it is also possible to return a binary content type that will +%% then be parsed by Cowboy. However note that while this may make your +%% resources a little more readable, this is a lot less efficient. An example +%% of such a return value would be: +%% {<<"text/html">>, to_html} +content_types_provided(Req=#http_req{meta=Meta}, State) -> + case call(Req, State, content_types_provided) of + no_call -> + not_acceptable(Req, State); + {halt, Req2, HandlerState} -> + terminate(Req2, State#state{handler_state=HandlerState}); + {[], Req2, HandlerState} -> + not_acceptable(Req2, State#state{handler_state=HandlerState}); + {CTP, Req2, HandlerState} -> + CTP2 = [normalize_content_types_provided(P) || P <- CTP], + State2 = State#state{ + handler_state=HandlerState, content_types_p=CTP2}, + {Accept, Req3} = cowboy_http_req:parse_header('Accept', Req2), + case Accept of + undefined -> + {PMT, _Fun} = HeadCTP = hd(CTP2), + languages_provided( + Req3#http_req{meta=[{media_type, PMT}|Meta]}, + State2#state{content_type_a=HeadCTP}); + Accept -> + Accept2 = prioritize_accept(Accept), + choose_media_type(Req3, State2, Accept2) + end + end. + +normalize_content_types_provided({ContentType, Handler}) + when is_binary(ContentType) -> + {cowboy_http:content_type(ContentType), Handler}; +normalize_content_types_provided(Provided) -> + Provided. + +prioritize_accept(Accept) -> + lists:sort( + fun ({MediaTypeA, Quality, _AcceptParamsA}, + {MediaTypeB, Quality, _AcceptParamsB}) -> + %% Same quality, check precedence in more details. + prioritize_mediatype(MediaTypeA, MediaTypeB); + ({_MediaTypeA, QualityA, _AcceptParamsA}, + {_MediaTypeB, QualityB, _AcceptParamsB}) -> + %% Just compare the quality. + QualityA > QualityB + end, Accept). + +%% Media ranges can be overridden by more specific media ranges or +%% specific media types. If more than one media range applies to a given +%% type, the most specific reference has precedence. +%% +%% We always choose B over A when we can't decide between the two. +prioritize_mediatype({TypeA, SubTypeA, ParamsA}, {TypeB, SubTypeB, ParamsB}) -> + case TypeB of + TypeA -> + case SubTypeB of + SubTypeA -> length(ParamsA) > length(ParamsB); + <<"*">> -> true; + _Any -> false + end; + <<"*">> -> true; + _Any -> false + end. + +%% Ignoring the rare AcceptParams. Not sure what should be done about them. +choose_media_type(Req, State, []) -> + not_acceptable(Req, State); +choose_media_type(Req, State=#state{content_types_p=CTP}, + [MediaType|Tail]) -> + match_media_type(Req, State, Tail, CTP, MediaType). + +match_media_type(Req, State, Accept, [], _MediaType) -> + choose_media_type(Req, State, Accept); +match_media_type(Req, State, Accept, CTP, + MediaType = {{<<"*">>, <<"*">>, _Params_A}, _QA, _APA}) -> + match_media_type_params(Req, State, Accept, CTP, MediaType); +match_media_type(Req, State, Accept, + CTP = [{{Type, SubType_P, _PP}, _Fun}|_Tail], + MediaType = {{Type, SubType_A, _PA}, _QA, _APA}) + when SubType_P =:= SubType_A; SubType_A =:= <<"*">> -> + match_media_type_params(Req, State, Accept, CTP, MediaType); +match_media_type(Req, State, Accept, [_Any|Tail], MediaType) -> + match_media_type(Req, State, Accept, Tail, MediaType). + +match_media_type_params(Req=#http_req{meta=Meta}, State, Accept, + [Provided = {PMT = {_TP, _STP, Params_P}, _Fun}|Tail], + MediaType = {{_TA, _STA, Params_A}, _QA, _APA}) -> + case lists:sort(Params_P) =:= lists:sort(Params_A) of + true -> + languages_provided(Req#http_req{meta=[{media_type, PMT}|Meta]}, + State#state{content_type_a=Provided}); + false -> + match_media_type(Req, State, Accept, Tail, MediaType) + end. + +%% languages_provided should return a list of binary values indicating +%% which languages are accepted by the resource. +%% +%% @todo I suppose we should also ask the resource if it wants to +%% set a language itself or if it wants it to be automatically chosen. +languages_provided(Req, State) -> + case call(Req, State, languages_provided) of + no_call -> + charsets_provided(Req, State); + {halt, Req2, HandlerState} -> + terminate(Req2, State#state{handler_state=HandlerState}); + {[], Req2, HandlerState} -> + not_acceptable(Req2, State#state{handler_state=HandlerState}); + {LP, Req2, HandlerState} -> + State2 = State#state{handler_state=HandlerState, languages_p=LP}, + {AcceptLanguage, Req3} = + cowboy_http_req:parse_header('Accept-Language', Req2), + case AcceptLanguage of + undefined -> + set_language(Req3, State2#state{language_a=hd(LP)}); + AcceptLanguage -> + AcceptLanguage2 = prioritize_languages(AcceptLanguage), + choose_language(Req3, State2, AcceptLanguage2) + end + end. + +%% A language-range matches a language-tag if it exactly equals the tag, +%% or if it exactly equals a prefix of the tag such that the first tag +%% character following the prefix is "-". The special range "*", if +%% present in the Accept-Language field, matches every tag not matched +%% by any other range present in the Accept-Language field. +%% +%% @todo The last sentence probably means we should always put '*' +%% at the end of the list. +prioritize_languages(AcceptLanguages) -> + lists:sort( + fun ({_TagA, QualityA}, {_TagB, QualityB}) -> + QualityA > QualityB + end, AcceptLanguages). + +choose_language(Req, State, []) -> + not_acceptable(Req, State); +choose_language(Req, State=#state{languages_p=LP}, [Language|Tail]) -> + match_language(Req, State, Tail, LP, Language). + +match_language(Req, State, Accept, [], _Language) -> + choose_language(Req, State, Accept); +match_language(Req, State, _Accept, [Provided|_Tail], {'*', _Quality}) -> + set_language(Req, State#state{language_a=Provided}); +match_language(Req, State, _Accept, [Provided|_Tail], {Provided, _Quality}) -> + set_language(Req, State#state{language_a=Provided}); +match_language(Req, State, Accept, [Provided|Tail], + Language = {Tag, _Quality}) -> + Length = byte_size(Tag), + case Provided of + << Tag:Length/binary, $-, _Any/bits >> -> + set_language(Req, State#state{language_a=Provided}); + _Any -> + match_language(Req, State, Accept, Tail, Language) + end. + +set_language(Req=#http_req{meta=Meta}, State=#state{language_a=Language}) -> + {ok, Req2} = cowboy_http_req:set_resp_header( + <<"Content-Language">>, Language, Req), + charsets_provided(Req2#http_req{meta=[{language, Language}|Meta]}, State). + +%% charsets_provided should return a list of binary values indicating +%% which charsets are accepted by the resource. +charsets_provided(Req, State) -> + case call(Req, State, charsets_provided) of + no_call -> + set_content_type(Req, State); + {halt, Req2, HandlerState} -> + terminate(Req2, State#state{handler_state=HandlerState}); + {[], Req2, HandlerState} -> + not_acceptable(Req2, State#state{handler_state=HandlerState}); + {CP, Req2, HandlerState} -> + State2 = State#state{handler_state=HandlerState, charsets_p=CP}, + {AcceptCharset, Req3} = + cowboy_http_req:parse_header('Accept-Charset', Req2), + case AcceptCharset of + undefined -> + set_content_type(Req3, State2#state{charset_a=hd(CP)}); + AcceptCharset -> + AcceptCharset2 = prioritize_charsets(AcceptCharset), + choose_charset(Req3, State2, AcceptCharset2) + end + end. + +%% The special value "*", if present in the Accept-Charset field, +%% matches every character set (including ISO-8859-1) which is not +%% mentioned elsewhere in the Accept-Charset field. If no "*" is present +%% in an Accept-Charset field, then all character sets not explicitly +%% mentioned get a quality value of 0, except for ISO-8859-1, which gets +%% a quality value of 1 if not explicitly mentioned. +prioritize_charsets(AcceptCharsets) -> + AcceptCharsets2 = lists:sort( + fun ({_CharsetA, QualityA}, {_CharsetB, QualityB}) -> + QualityA > QualityB + end, AcceptCharsets), + case lists:keymember(<<"*">>, 1, AcceptCharsets2) of + true -> AcceptCharsets2; + false -> [{<<"iso-8859-1">>, 1000}|AcceptCharsets2] + end. + +choose_charset(Req, State, []) -> + not_acceptable(Req, State); +choose_charset(Req, State=#state{charsets_p=CP}, [Charset|Tail]) -> + match_charset(Req, State, Tail, CP, Charset). + +match_charset(Req, State, Accept, [], _Charset) -> + choose_charset(Req, State, Accept); +match_charset(Req, State, _Accept, [Provided|_Tail], + {Provided, _Quality}) -> + set_content_type(Req, State#state{charset_a=Provided}); +match_charset(Req, State, Accept, [_Provided|Tail], Charset) -> + match_charset(Req, State, Accept, Tail, Charset). + +set_content_type(Req=#http_req{meta=Meta}, State=#state{ + content_type_a={{Type, SubType, Params}, _Fun}, + charset_a=Charset}) -> + ParamsBin = set_content_type_build_params(Params, []), + ContentType = [Type, <<"/">>, SubType, ParamsBin], + ContentType2 = case Charset of + undefined -> ContentType; + Charset -> [ContentType, <<"; charset=">>, Charset] + end, + {ok, Req2} = cowboy_http_req:set_resp_header( + <<"Content-Type">>, ContentType2, Req), + encodings_provided(Req2#http_req{meta=[{charset, Charset}|Meta]}, State). + +set_content_type_build_params([], []) -> + <<>>; +set_content_type_build_params([], Acc) -> + lists:reverse(Acc); +set_content_type_build_params([{Attr, Value}|Tail], Acc) -> + set_content_type_build_params(Tail, [[Attr, <<"=">>, Value], <<";">>|Acc]). + +%% @todo Match for identity as we provide nothing else for now. +%% @todo Don't forget to set the Content-Encoding header when we reply a body +%% and the found encoding is something other than identity. +encodings_provided(Req, State) -> + variances(Req, State). + +not_acceptable(Req, State) -> + respond(Req, State, 406). + +%% variances/2 should return a list of headers that will be added +%% to the Vary response header. The Accept, Accept-Language, +%% Accept-Charset and Accept-Encoding headers do not need to be +%% specified. +%% +%% @todo Do Accept-Encoding too when we handle it. +%% @todo Does the order matter? +variances(Req, State=#state{content_types_p=CTP, + languages_p=LP, charsets_p=CP}) -> + Variances = case CTP of + [] -> []; + [_] -> []; + [_|_] -> [<<"Accept">>] + end, + Variances2 = case LP of + [] -> Variances; + [_] -> Variances; + [_|_] -> [<<"Accept-Language">>|Variances] + end, + Variances3 = case CP of + [] -> Variances2; + [_] -> Variances2; + [_|_] -> [<<"Accept-Charset">>|Variances2] + end, + {Variances4, Req3, State2} = case call(Req, State, variances) of + no_call -> + {Variances3, Req, State}; + {HandlerVariances, Req2, HandlerState} -> + {Variances3 ++ HandlerVariances, Req2, + State#state{handler_state=HandlerState}} + end, + case [[<<", ">>, V] || V <- Variances4] of + [] -> + resource_exists(Req3, State2); + [[<<", ">>, H]|Variances5] -> + {ok, Req4} = cowboy_http_req:set_resp_header( + <<"Variances">>, [H|Variances5], Req3), + resource_exists(Req4, State2) + end. + +resource_exists(Req, State) -> + expect(Req, State, resource_exists, true, + fun if_match_exists/2, fun if_match_musnt_exist/2). + +if_match_exists(Req, State) -> + case cowboy_http_req:parse_header('If-Match', Req) of + {undefined, Req2} -> + if_unmodified_since_exists(Req2, State); + {'*', Req2} -> + if_unmodified_since_exists(Req2, State); + {ETagsList, Req2} -> + if_match(Req2, State, ETagsList) + end. + +if_match(Req, State, EtagsList) -> + {Etag, Req2, State2} = generate_etag(Req, State), + case Etag of + no_call -> + precondition_failed(Req2, State2); + Etag -> + case lists:member(Etag, EtagsList) of + true -> if_unmodified_since_exists(Req2, State2); + false -> precondition_failed(Req2, State2) + end + end. + +if_match_musnt_exist(Req, State) -> + case cowboy_http_req:header('If-Match', Req) of + {undefined, Req2} -> is_put_to_missing_resource(Req2, State); + {_Any, Req2} -> precondition_failed(Req2, State) + end. + +if_unmodified_since_exists(Req, State) -> + case cowboy_http_req:parse_header('If-Unmodified-Since', Req) of + {undefined, Req2} -> + if_none_match_exists(Req2, State); + {{error, badarg}, Req2} -> + if_none_match_exists(Req2, State); + {IfUnmodifiedSince, Req2} -> + if_unmodified_since(Req2, State, IfUnmodifiedSince) + end. + +%% If LastModified is the atom 'no_call', we continue. +if_unmodified_since(Req, State, IfUnmodifiedSince) -> + {LastModified, Req2, State2} = last_modified(Req, State), + case LastModified > IfUnmodifiedSince of + true -> precondition_failed(Req2, State2); + false -> if_none_match_exists(Req2, State2) + end. + +if_none_match_exists(Req, State) -> + case cowboy_http_req:parse_header('If-None-Match', Req) of + {undefined, Req2} -> + if_modified_since_exists(Req2, State); + {'*', Req2} -> + precondition_is_head_get(Req2, State); + {EtagsList, Req2} -> + if_none_match(Req2, State, EtagsList) + end. + +if_none_match(Req, State, EtagsList) -> + {Etag, Req2, State2} = generate_etag(Req, State), + case Etag of + no_call -> + precondition_failed(Req2, State2); + Etag -> + case lists:member(Etag, EtagsList) of + true -> precondition_is_head_get(Req2, State2); + false -> if_modified_since_exists(Req2, State2) + end + end. + +precondition_is_head_get(Req=#http_req{method=Method}, State) + when Method =:= 'HEAD'; Method =:= 'GET' -> + not_modified(Req, State); +precondition_is_head_get(Req, State) -> + precondition_failed(Req, State). + +if_modified_since_exists(Req, State) -> + case cowboy_http_req:parse_header('If-Modified-Since', Req) of + {undefined, Req2} -> + method(Req2, State); + {{error, badarg}, Req2} -> + method(Req2, State); + {IfModifiedSince, Req2} -> + if_modified_since_now(Req2, State, IfModifiedSince) + end. + +if_modified_since_now(Req, State, IfModifiedSince) -> + case IfModifiedSince > erlang:universaltime() of + true -> method(Req, State); + false -> if_modified_since(Req, State, IfModifiedSince) + end. + +if_modified_since(Req, State, IfModifiedSince) -> + {LastModified, Req2, State2} = last_modified(Req, State), + case LastModified of + no_call -> + method(Req2, State2); + LastModified -> + case LastModified > IfModifiedSince of + true -> method(Req2, State2); + false -> not_modified(Req2, State2) + end + end. + +not_modified(Req=#http_req{resp_headers=RespHeaders}, State) -> + RespHeaders2 = lists:keydelete(<<"Content-Type">>, 1, RespHeaders), + Req2 = Req#http_req{resp_headers=RespHeaders2}, + {Req3, State2} = set_resp_etag(Req2, State), + {Req4, State3} = set_resp_expires(Req3, State2), + respond(Req4, State3, 304). + +precondition_failed(Req, State) -> + respond(Req, State, 412). + +is_put_to_missing_resource(Req=#http_req{method='PUT'}, State) -> + moved_permanently(Req, State, fun is_conflict/2); +is_put_to_missing_resource(Req, State) -> + previously_existed(Req, State). + +%% moved_permanently/2 should return either false or {true, Location} +%% with Location the full new URI of the resource. +moved_permanently(Req, State, OnFalse) -> + case call(Req, State, moved_permanently) of + {{true, Location}, Req2, HandlerState} -> + {ok, Req3} = cowboy_http_req:set_resp_header( + <<"Location">>, Location, Req2), + respond(Req3, State#state{handler_state=HandlerState}, 301); + {false, Req2, HandlerState} -> + OnFalse(Req2, State#state{handler_state=HandlerState}); + {halt, Req2, HandlerState} -> + terminate(Req2, State#state{handler_state=HandlerState}); + no_call -> + OnFalse(Req, State) + end. + +previously_existed(Req, State) -> + expect(Req, State, previously_existed, false, + fun (R, S) -> is_post_to_missing_resource(R, S, 404) end, + fun (R, S) -> moved_permanently(R, S, fun moved_temporarily/2) end). + +%% moved_temporarily/2 should return either false or {true, Location} +%% with Location the full new URI of the resource. +moved_temporarily(Req, State) -> + case call(Req, State, moved_temporarily) of + {{true, Location}, Req2, HandlerState} -> + {ok, Req3} = cowboy_http_req:set_resp_header( + <<"Location">>, Location, Req2), + respond(Req3, State#state{handler_state=HandlerState}, 307); + {false, Req2, HandlerState} -> + is_post_to_missing_resource(Req2, State#state{handler_state=HandlerState}, 410); + {halt, Req2, HandlerState} -> + terminate(Req2, State#state{handler_state=HandlerState}); + no_call -> + is_post_to_missing_resource(Req, State, 410) + end. + +is_post_to_missing_resource(Req=#http_req{method='POST'}, State, OnFalse) -> + allow_missing_post(Req, State, OnFalse); +is_post_to_missing_resource(Req, State, OnFalse) -> + respond(Req, State, OnFalse). + +allow_missing_post(Req, State, OnFalse) -> + expect(Req, State, allow_missing_post, true, fun post_is_create/2, OnFalse). + +method(Req=#http_req{method='DELETE'}, State) -> + delete_resource(Req, State); +method(Req=#http_req{method='POST'}, State) -> + post_is_create(Req, State); +method(Req=#http_req{method='PUT'}, State) -> + is_conflict(Req, State); +method(Req, State) -> + set_resp_body(Req, State). + +%% delete_resource/2 should start deleting the resource and return. +delete_resource(Req, State) -> + expect(Req, State, delete_resource, true, fun delete_completed/2, 500). + +%% delete_completed/2 indicates whether the resource has been deleted yet. +delete_completed(Req, State) -> + expect(Req, State, delete_completed, true, fun has_resp_body/2, 202). + +%% post_is_create/2 indicates whether the POST method can create new resources. +post_is_create(Req, State) -> + expect(Req, State, post_is_create, false, fun process_post/2, fun create_path/2). + +%% When the POST method can create new resources, create_path/2 will be called +%% and is expected to return the full path to the new resource +%% (including the leading /). +create_path(Req=#http_req{meta=Meta}, State) -> + case call(Req, State, create_path) of + {halt, Req2, HandlerState} -> + terminate(Req2, State#state{handler_state=HandlerState}); + {Path, Req2, HandlerState} -> + Location = create_path_location(Req2, Path), + State2 = State#state{handler_state=HandlerState}, + {ok, Req3} = cowboy_http_req:set_resp_header( + <<"Location">>, Location, Req2), + put_resource(Req3#http_req{meta=[{put_path, Path}|Meta]}, + State2, 303) + end. + +create_path_location(#http_req{transport=Transport, raw_host=Host, + port=Port}, Path) -> + TransportName = Transport:name(), + << (create_path_location_protocol(TransportName))/binary, "://", + Host/binary, (create_path_location_port(TransportName, Port))/binary, + Path/binary >>. + +create_path_location_protocol(ssl) -> <<"https">>; +create_path_location_protocol(_) -> <<"http">>. + +create_path_location_port(ssl, 443) -> + <<>>; +create_path_location_port(tcp, 80) -> + <<>>; +create_path_location_port(_, Port) -> + <<":", (list_to_binary(integer_to_list(Port)))/binary>>. + +%% process_post should return true when the POST body could be processed +%% and false when it hasn't, in which case a 500 error is sent. +process_post(Req, State) -> + case call(Req, State, process_post) of + {halt, Req2, HandlerState} -> + terminate(Req2, State#state{handler_state=HandlerState}); + {true, Req2, HandlerState} -> + State2 = State#state{handler_state=HandlerState}, + next(Req2, State2, 201); + {false, Req2, HandlerState} -> + State2 = State#state{handler_state=HandlerState}, + respond(Req2, State2, 500) + end. + +is_conflict(Req, State) -> + expect(Req, State, is_conflict, false, fun put_resource/2, 409). + +put_resource(Req=#http_req{raw_path=RawPath, meta=Meta}, State) -> + Req2 = Req#http_req{meta=[{put_path, RawPath}|Meta]}, + put_resource(Req2, State, fun is_new_resource/2). + +%% content_types_accepted should return a list of media types and their +%% associated callback functions in the same format as content_types_provided. +%% +%% The callback will then be called and is expected to process the content +%% pushed to the resource in the request body. The path to the new resource +%% may be different from the request path, and is stored as request metadata. +%% It is always defined past this point. It can be retrieved as demonstrated: +%% {PutPath, Req2} = cowboy_http_req:meta(put_path, Req) +put_resource(Req, State, OnTrue) -> + case call(Req, State, content_types_accepted) of + no_call -> + respond(Req, State, 415); + {halt, Req2, HandlerState} -> + terminate(Req2, State#state{handler_state=HandlerState}); + {CTA, Req2, HandlerState} -> + State2 = State#state{handler_state=HandlerState}, + {ContentType, Req3} + = cowboy_http_req:parse_header('Content-Type', Req2), + choose_content_type(Req3, State2, OnTrue, ContentType, CTA) + end. + +choose_content_type(Req, State, _OnTrue, _ContentType, []) -> + respond(Req, State, 415); +choose_content_type(Req, State, OnTrue, ContentType, + [{Accepted, Fun}|_Tail]) when ContentType =:= Accepted -> + case call(Req, State, Fun) of + {halt, Req2, HandlerState} -> + terminate(Req2, State#state{handler_state=HandlerState}); + {true, Req2, HandlerState} -> + State2 = State#state{handler_state=HandlerState}, + next(Req2, State2, OnTrue); + {false, Req2, HandlerState} -> + State2 = State#state{handler_state=HandlerState}, + respond(Req2, State2, 500) + end; +choose_content_type(Req, State, OnTrue, ContentType, [_Any|Tail]) -> + choose_content_type(Req, State, OnTrue, ContentType, Tail). + +%% Whether we created a new resource, either through PUT or POST. +%% This is easily testable because we would have set the Location +%% header by this point if we did so. +is_new_resource(Req, State) -> + case cowboy_http_req:has_resp_header(<<"Location">>, Req) of + true -> respond(Req, State, 201); + false -> has_resp_body(Req, State) + end. + +has_resp_body(Req, State) -> + case cowboy_http_req:has_resp_body(Req) of + true -> multiple_choices(Req, State); + false -> respond(Req, State, 204) + end. + +%% Set the response headers and call the callback found using +%% content_types_provided/2 to obtain the request body and add +%% it to the response. +set_resp_body(Req=#http_req{method=Method}, + State=#state{content_type_a={_Type, Fun}}) + when Method =:= 'GET'; Method =:= 'HEAD' -> + {Req2, State2} = set_resp_etag(Req, State), + {LastModified, Req3, State3} = last_modified(Req2, State2), + case LastModified of + LastModified when is_atom(LastModified) -> + Req4 = Req3; + LastModified -> + LastModifiedStr = httpd_util:rfc1123_date(LastModified), + {ok, Req4} = cowboy_http_req:set_resp_header( + <<"Last-Modified">>, LastModifiedStr, Req3) + end, + {Req5, State4} = set_resp_expires(Req4, State3), + case call(Req5, State4, Fun) of + {halt, Req6, HandlerState} -> + terminate(Req6, State4#state{handler_state=HandlerState}); + {Body, Req6, HandlerState} -> + State5 = State4#state{handler_state=HandlerState}, + {ok, Req7} = case Body of + {stream, Len, Fun1} -> + cowboy_http_req:set_resp_body_fun(Len, Fun1, Req6); + _Contents -> + cowboy_http_req:set_resp_body(Body, Req6) + end, + multiple_choices(Req7, State5) + end; +set_resp_body(Req, State) -> + multiple_choices(Req, State). + +multiple_choices(Req, State) -> + expect(Req, State, multiple_choices, false, 200, 300). + +%% Response utility functions. + +set_resp_etag(Req, State) -> + {Etag, Req2, State2} = generate_etag(Req, State), + case Etag of + undefined -> + {Req2, State2}; + Etag -> + {ok, Req3} = cowboy_http_req:set_resp_header( + <<"Etag">>, Etag, Req2), + {Req3, State2} + end. + +set_resp_expires(Req, State) -> + {Expires, Req2, State2} = expires(Req, State), + case Expires of + Expires when is_atom(Expires) -> + {Req2, State2}; + Expires -> + ExpiresStr = httpd_util:rfc1123_date(Expires), + {ok, Req3} = cowboy_http_req:set_resp_header( + <<"Expires">>, ExpiresStr, Req2), + {Req3, State2} + end. + +%% Info retrieval. No logic. + +generate_etag(Req, State=#state{etag=no_call}) -> + {undefined, Req, State}; +generate_etag(Req, State=#state{etag=undefined}) -> + case call(Req, State, generate_etag) of + no_call -> + {undefined, Req, State#state{etag=no_call}}; + {Etag, Req2, HandlerState} -> + {Etag, Req2, State#state{handler_state=HandlerState, etag=Etag}} + end; +generate_etag(Req, State=#state{etag=Etag}) -> + {Etag, Req, State}. + +last_modified(Req, State=#state{last_modified=no_call}) -> + {undefined, Req, State}; +last_modified(Req, State=#state{last_modified=undefined}) -> + case call(Req, State, last_modified) of + no_call -> + {undefined, Req, State#state{last_modified=no_call}}; + {LastModified, Req2, HandlerState} -> + {LastModified, Req2, State#state{handler_state=HandlerState, + last_modified=LastModified}} + end; +last_modified(Req, State=#state{last_modified=LastModified}) -> + {LastModified, Req, State}. + +expires(Req, State=#state{expires=no_call}) -> + {undefined, Req, State}; +expires(Req, State=#state{expires=undefined}) -> + case call(Req, State, expires) of + no_call -> + {undefined, Req, State#state{expires=no_call}}; + {Expires, Req2, HandlerState} -> + {Expires, Req2, State#state{handler_state=HandlerState, + expires=Expires}} + end; +expires(Req, State=#state{expires=Expires}) -> + {Expires, Req, State}. + +%% REST primitives. + +expect(Req, State, Callback, Expected, OnTrue, OnFalse) -> + case call(Req, State, Callback) of + no_call -> + next(Req, State, OnTrue); + {halt, Req2, HandlerState} -> + terminate(Req2, State#state{handler_state=HandlerState}); + {Expected, Req2, HandlerState} -> + next(Req2, State#state{handler_state=HandlerState}, OnTrue); + {_Unexpected, Req2, HandlerState} -> + next(Req2, State#state{handler_state=HandlerState}, OnFalse) + end. + +call(Req, #state{handler=Handler, handler_state=HandlerState}, Fun) -> + case erlang:function_exported(Handler, Fun, 2) of + true -> Handler:Fun(Req, HandlerState); + false -> no_call + end. + +next(Req, State, Next) when is_function(Next) -> + Next(Req, State); +next(Req, State, StatusCode) when is_integer(StatusCode) -> + respond(Req, State, StatusCode). + +%% @todo Allow some sort of callback for custom error pages. +respond(Req, State, StatusCode) -> + {ok, Req2} = cowboy_http_req:reply(StatusCode, Req), + terminate(Req2, State). + +terminate(Req, #state{handler=Handler, handler_state=HandlerState}) -> + case erlang:function_exported(Handler, rest_terminate, 2) of + true -> ok = Handler:rest_terminate( + Req#http_req{resp_state=locked}, HandlerState); + false -> ok + end, + {ok, Req}. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_static.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_static.erl new file mode 100644 index 0000000..da3bd33 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_static.erl @@ -0,0 +1,456 @@ +%% Copyright (c) 2011, Magnus Klaar +%% +%% 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. + +%% @doc Static resource handler. +%% +%% This built in HTTP handler provides a simple file serving capability for +%% cowboy applications. It should be considered an experimental feature because +%% of it's dependency on the experimental REST handler. It's recommended to be +%% used for small or temporary environments where it is not preferrable to set +%% up a second server just to serve files. +%% +%% If this handler is used the Erlang node running the cowboy application must +%% be configured to use an async thread pool. This is configured by adding the +%% `+A $POOL_SIZE' argument to the `erl' command used to start the node. See +%% +%% this reply from the OTP team to erlang-bugs +%% +%% == Base configuration == +%% +%% The handler must be configured with a request path prefix to serve files +%% under and the path to a directory to read files from. The request path prefix +%% is defined in the path pattern of the cowboy dispatch rule for the handler. +%% The request path pattern must end with a ``'...''' token. +%% The directory path can be set to either an absolute or relative path in the +%% form of a list or binary string representation of a file system path. A list +%% of binary path segments, as is used throughout cowboy, is also a valid +%% directory path. +%% +%% The directory path can also be set to a relative path within the `priv/' +%% directory of an application. This is configured by setting the value of the +%% directory option to a tuple of the form `{priv_dir, Application, Relpath}'. +%% +%% ==== Examples ==== +%% ``` +%% %% Serve files from /var/www/ under http://example.com/static/ +%% {[<<"static">>, '...'], cowboy_http_static, +%% [{directory, "/var/www"}]} +%% +%% %% Serve files from the current working directory under http://example.com/static/ +%% {[<<"static">>, '...'], cowboy_http_static, +%% [{directory, <<"./">>}]} +%% +%% %% Serve files from cowboy/priv/www under http://example.com/ +%% {['...'], cowboy_http_static, +%% [{directory, {priv_dir, cowboy, [<<"www">>]}}]} +%% ''' +%% +%% == Content type configuration == +%% +%% By default the content type of all static resources will be set to +%% `application/octet-stream'. This can be overriden by supplying a list +%% of filename extension to mimetypes pairs in the `mimetypes' option. +%% The filename extension should be a binary string including the leading dot. +%% The mimetypes must be of a type that the `cowboy_http_rest' protocol can +%% handle. +%% +%% The spawngrid/mimetypes +%% application, or an arbitrary function accepting the path to the file being +%% served, can also be used to generate the list of content types for a static +%% file resource. The function used must accept an additional argument after +%% the file path argument. +%% +%% ==== Example ==== +%% ``` +%% %% Use a static list of content types. +%% {[<<"static">>, '...'], cowboy_http_static, +%% [{directory, {priv_dir, cowboy, []}}, +%% {mimetypes, [ +%% {<<".css">>, [<<"text/css">>]}, +%% {<<".js">>, [<<"application/javascript">>]}]}]} +%% +%% %% Use the default database in the mimetypes application. +%% {[<<"static">>, '...', cowboy_http_static, +%% [{directory, {priv_dir, cowboy, []}}, +%% {mimetypes, {fun mimetypes:path_to_mimes/2, default}}]]} +%% ''' +%% +%% == ETag Header Function == +%% +%% The default behaviour of the static file handler is to not generate ETag +%% headers. This is because generating ETag headers based on file metadata +%% causes different servers in a cluster to generate different ETag values for +%% the same file unless the metadata is also synced. Generating strong ETags +%% based on the contents of a file is currently out of scope for this module. +%% +%% The default behaviour can be overridden to generate an ETag header based on +%% a combination of the file path, file size, inode and mtime values. If the +%% option value is a list of attribute names tagged with `attributes' a hex +%% encoded CRC32 checksum of the attribute values are used as the ETag header +%% value. +%% +%% If a strong ETag is required a user defined function for generating the +%% header value can be supplied. The function must accept a proplist of the +%% file attributes as the first argument and a second argument containing any +%% additional data that the function requires. The function must return a +%% `binary()' or `undefined'. +%% +%% ==== Examples ==== +%% ``` +%% %% A value of default is equal to not specifying the option. +%% {[<<"static">>, '...', cowboy_http_static, +%% [{directory, {priv_dir, cowboy, []}}, +%% {etag, default}]]} +%% +%% %% Use all avaliable ETag function arguments to generate a header value. +%% {[<<"static">>, '...', cowboy_http_static, +%% [{directory, {priv_dir, cowboy, []}}, +%% {etag, {attributes, [filepath, filesize, inode, mtime]}}]]} +%% +%% %% Use a user defined function to generate a strong ETag header value. +%% {[<<"static">>, '...', cowboy_http_static, +%% [{directory, {priv_dir, cowboy, []}}, +%% {etag, {fun generate_strong_etag/2, strong_etag_extra}}]]} +%% +%% generate_strong_etag(Arguments, strong_etag_extra) -> +%% {_, Filepath} = lists:keyfind(filepath, 1, Arguments), +%% {_, _Filesize} = lists:keyfind(filesize, 1, Arguments), +%% {_, _INode} = lists:keyfind(inode, 1, Arguments), +%% {_, _Modified} = lists:keyfind(mtime, 1, Arguments), +%% ChecksumCommand = lists:flatten(io_lib:format("sha1sum ~s", [Filepath])), +%% [Checksum|_] = string:tokens(os:cmd(ChecksumCommand), " "), +%% iolist_to_binary(Checksum). +%% ''' +-module(cowboy_http_static). + +%% include files +-include("http.hrl"). +-include_lib("kernel/include/file.hrl"). + +%% cowboy_http_protocol callbacks +-export([init/3]). + +%% cowboy_http_rest callbacks +-export([rest_init/2, allowed_methods/2, malformed_request/2, + resource_exists/2, forbidden/2, last_modified/2, generate_etag/2, + content_types_provided/2, file_contents/2]). + +%% internal +-export([path_to_mimetypes/2]). + +%% types +-type dirpath() :: string() | binary() | [binary()]. +-type dirspec() :: dirpath() | {priv, atom(), dirpath()}. +-type mimedef() :: {binary(), binary(), [{binary(), binary()}]}. +-type etagarg() :: {filepath, binary()} | {mtime, calendar:datetime()} + | {inode, non_neg_integer()} | {filesize, non_neg_integer()}. + +%% handler state +-record(state, { + filepath :: binary() | error, + fileinfo :: {ok, #file_info{}} | {error, _} | error, + mimetypes :: {fun((binary(), T) -> [mimedef()]), T} | undefined, + etag_fun :: {fun(([etagarg()], T) -> undefined | binary()), T}}). + + +%% @private Upgrade from HTTP handler to REST handler. +init({_Transport, http}, _Req, _Opts) -> + {upgrade, protocol, cowboy_http_rest}. + + +%% @private Set up initial state of REST handler. +-spec rest_init(#http_req{}, list()) -> {ok, #http_req{}, #state{}}. +rest_init(Req, Opts) -> + Directory = proplists:get_value(directory, Opts), + Directory1 = directory_path(Directory), + Mimetypes = proplists:get_value(mimetypes, Opts, []), + Mimetypes1 = case Mimetypes of + {_, _} -> Mimetypes; + [] -> {fun path_to_mimetypes/2, []}; + [_|_] -> {fun path_to_mimetypes/2, Mimetypes} + end, + ETagFunction = case proplists:get_value(etag, Opts) of + default -> {fun no_etag_function/2, undefined}; + undefined -> {fun no_etag_function/2, undefined}; + {attributes, Attrs} -> {fun attr_etag_function/2, Attrs}; + {_, _}=EtagFunction1 -> EtagFunction1 + end, + {Filepath, Req1} = cowboy_http_req:path_info(Req), + State = case check_path(Filepath) of + error -> + #state{filepath=error, fileinfo=error, mimetypes=undefined, + etag_fun=ETagFunction}; + ok -> + Filepath1 = join_paths(Directory1, Filepath), + Fileinfo = file:read_file_info(Filepath1), + #state{filepath=Filepath1, fileinfo=Fileinfo, mimetypes=Mimetypes1, + etag_fun=ETagFunction} + end, + {ok, Req1, State}. + + +%% @private Only allow GET and HEAD requests on files. +-spec allowed_methods(#http_req{}, #state{}) -> + {[atom()], #http_req{}, #state{}}. +allowed_methods(Req, State) -> + {['GET', 'HEAD'], Req, State}. + +%% @private +malformed_request(Req, #state{filepath=error}=State) -> + {true, Req, State}; +malformed_request(Req, State) -> + {false, Req, State}. + + +%% @private Check if the resource exists under the document root. +resource_exists(Req, #state{fileinfo={error, _}}=State) -> + {false, Req, State}; +resource_exists(Req, #state{fileinfo={ok, Fileinfo}}=State) -> + {Fileinfo#file_info.type =:= regular, Req, State}. + + +%% @private +%% Access to a file resource is forbidden if it exists and the local node does +%% not have permission to read it. Directory listings are always forbidden. +forbidden(Req, #state{fileinfo={_, #file_info{type=directory}}}=State) -> + {true, Req, State}; +forbidden(Req, #state{fileinfo={error, eacces}}=State) -> + {true, Req, State}; +forbidden(Req, #state{fileinfo={error, _}}=State) -> + {false, Req, State}; +forbidden(Req, #state{fileinfo={ok, #file_info{access=Access}}}=State) -> + {not (Access =:= read orelse Access =:= read_write), Req, State}. + + +%% @private Read the time a file system system object was last modified. +-spec last_modified(#http_req{}, #state{}) -> + {calendar:datetime(), #http_req{}, #state{}}. +last_modified(Req, #state{fileinfo={ok, #file_info{mtime=Modified}}}=State) -> + {Modified, Req, State}. + + +%% @private Generate the ETag header value for this file. +%% The ETag header value is only generated if the resource is a file that +%% exists in document root. +-spec generate_etag(#http_req{}, #state{}) -> + {undefined | binary(), #http_req{}, #state{}}. +generate_etag(Req, #state{fileinfo={_, #file_info{type=regular, inode=INode, + mtime=Modified, size=Filesize}}, filepath=Filepath, + etag_fun={ETagFun, ETagData}}=State) -> + ETagArgs = [ + {filepath, Filepath}, {filesize, Filesize}, + {inode, INode}, {mtime, Modified}], + {ETagFun(ETagArgs, ETagData), Req, State}; +generate_etag(Req, State) -> + {undefined, Req, State}. + + +%% @private Return the content type of a file. +-spec content_types_provided(#http_req{}, #state{}) -> tuple(). +content_types_provided(Req, #state{filepath=Filepath, + mimetypes={MimetypesFun, MimetypesData}}=State) -> + Mimetypes = [{T, file_contents} + || T <- MimetypesFun(Filepath, MimetypesData)], + {Mimetypes, Req, State}. + + +%% @private Return a function that writes a file directly to the socket. +-spec file_contents(#http_req{}, #state{}) -> tuple(). +file_contents(Req, #state{filepath=Filepath, + fileinfo={ok, #file_info{size=Filesize}}}=State) -> + {ok, Transport, Socket} = cowboy_http_req:transport(Req), + Writefile = content_function(Transport, Socket, Filepath), + {{stream, Filesize, Writefile}, Req, State}. + + +%% @private Return a function writing the contents of a file to a socket. +%% The function returns the number of bytes written to the socket to enable +%% the calling function to determine if the expected number of bytes were +%% written to the socket. +-spec content_function(module(), inet:socket(), binary()) -> + fun(() -> {sent, non_neg_integer()}). +content_function(Transport, Socket, Filepath) -> + %% `file:sendfile/2' will only work with the `cowboy_tcp_transport' + %% transport module. SSL or future SPDY transports that require the + %% content to be encrypted or framed as the content is sent. + case erlang:function_exported(file, sendfile, 2) of + false -> + fun() -> sfallback(Transport, Socket, Filepath) end; + _ when Transport =/= cowboy_tcp_transport -> + fun() -> sfallback(Transport, Socket, Filepath) end; + true -> + fun() -> sendfile(Socket, Filepath) end + end. + + +%% @private Sendfile fallback function. +-spec sfallback(module(), inet:socket(), binary()) -> {sent, non_neg_integer()}. +sfallback(Transport, Socket, Filepath) -> + {ok, File} = file:open(Filepath, [read,binary,raw]), + sfallback(Transport, Socket, File, 0). + +-spec sfallback(module(), inet:socket(), file:io_device(), + non_neg_integer()) -> {sent, non_neg_integer()}. +sfallback(Transport, Socket, File, Sent) -> + case file:read(File, 16#1FFF) of + eof -> + ok = file:close(File), + {sent, Sent}; + {ok, Bin} -> + ok = Transport:send(Socket, Bin), + sfallback(Transport, Socket, File, Sent + byte_size(Bin)) + end. + + +%% @private Wrapper for sendfile function. +-spec sendfile(inet:socket(), binary()) -> {sent, non_neg_integer()}. +sendfile(Socket, Filepath) -> + {ok, Sent} = file:sendfile(Filepath, Socket), + {sent, Sent}. + +-spec directory_path(dirspec()) -> dirpath(). +directory_path({priv_dir, App, []}) -> + priv_dir_path(App); +directory_path({priv_dir, App, [H|_]=Path}) when is_integer(H) -> + filename:join(priv_dir_path(App), Path); +directory_path({priv_dir, App, [H|_]=Path}) when is_binary(H) -> + filename:join(filename:split(priv_dir_path(App)) ++ Path); +directory_path({priv_dir, App, Path}) when is_binary(Path) -> + filename:join(priv_dir_path(App), Path); +directory_path(Path) -> + Path. + + +%% @private Validate a request path for unsafe characters. +%% There is no way to escape special characters in a filesystem path. +-spec check_path(Path::[binary()]) -> ok | error. +check_path([]) -> ok; +check_path([<<"">>|_T]) -> error; +check_path([<<".">>|_T]) -> error; +check_path([<<"..">>|_T]) -> error; +check_path([H|T]) -> + case binary:match(H, <<"/">>) of + {_, _} -> error; + nomatch -> check_path(T) + end. + + +%% @private Join the the directory and request paths. +-spec join_paths(dirpath(), [binary()]) -> binary(). +join_paths([H|_]=Dirpath, Filepath) when is_integer(H) -> + filename:join(filename:split(Dirpath) ++ Filepath); +join_paths([H|_]=Dirpath, Filepath) when is_binary(H) -> + filename:join(Dirpath ++ Filepath); +join_paths(Dirpath, Filepath) when is_binary(Dirpath) -> + filename:join([Dirpath] ++ Filepath); +join_paths([], Filepath) -> + filename:join(Filepath). + + +%% @private Return the path to the priv/ directory of an application. +-spec priv_dir_path(atom()) -> string(). +priv_dir_path(App) -> + case code:priv_dir(App) of + {error, bad_name} -> priv_dir_mod(App); + Dir -> Dir + end. + +-spec priv_dir_mod(atom()) -> string(). +priv_dir_mod(Mod) -> + case code:which(Mod) of + File when not is_list(File) -> "../priv"; + File -> filename:join([filename:dirname(File),"../priv"]) + end. + + +%% @private Use application/octet-stream as the default mimetype. +%% If a list of extension - mimetype pairs are provided as the mimetypes +%% an attempt to find the mimetype using the file extension. If no match +%% is found the default mimetype is returned. +-spec path_to_mimetypes(binary(), [{binary(), [mimedef()]}]) -> + [mimedef()]. +path_to_mimetypes(Filepath, Extensions) when is_binary(Filepath) -> + Ext = filename:extension(Filepath), + case Ext of + <<>> -> default_mimetype(); + _Ext -> path_to_mimetypes_(Ext, Extensions) + end. + +-spec path_to_mimetypes_(binary(), [{binary(), [mimedef()]}]) -> [mimedef()]. +path_to_mimetypes_(Ext, Extensions) -> + case lists:keyfind(Ext, 1, Extensions) of + {_, MTs} -> MTs; + _Unknown -> default_mimetype() + end. + +-spec default_mimetype() -> [mimedef()]. +default_mimetype() -> + [{<<"application">>, <<"octet-stream">>, []}]. + + +%% @private Do not send ETag headers in the default configuration. +-spec no_etag_function([etagarg()], undefined) -> undefined. +no_etag_function(_Args, undefined) -> + undefined. + +%% @private A simple alternative is to send an ETag based on file attributes. +-type fileattr() :: filepath | filesize | mtime | inode. +-spec attr_etag_function([etagarg()], [fileattr()]) -> binary(). +attr_etag_function(Args, Attrs) -> + attr_etag_function(Args, Attrs, []). + +-spec attr_etag_function([etagarg()], [fileattr()], [binary()]) -> binary(). +attr_etag_function(_Args, [], Acc) -> + list_to_binary(erlang:integer_to_list(erlang:crc32(Acc), 16)); +attr_etag_function(Args, [H|T], Acc) -> + {_, Value} = lists:keyfind(H, 1, Args), + attr_etag_function(Args, T, [term_to_binary(Value)|Acc]). + + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-define(_eq(E, I), ?_assertEqual(E, I)). + +check_path_test_() -> + C = fun check_path/1, + [?_eq(error, C([<<>>])), + ?_eq(ok, C([<<"abc">>])), + ?_eq(error, C([<<".">>])), + ?_eq(error, C([<<"..">>])), + ?_eq(error, C([<<"/">>])) + ]. + +join_paths_test_() -> + P = fun join_paths/2, + [?_eq(<<"a">>, P([], [<<"a">>])), + ?_eq(<<"a/b/c">>, P(<<"a/b">>, [<<"c">>])), + ?_eq(<<"a/b/c">>, P("a/b", [<<"c">>])), + ?_eq(<<"a/b/c">>, P([<<"a">>, <<"b">>], [<<"c">>])) + ]. + +directory_path_test_() -> + P = fun directory_path/1, + PL = fun(I) -> length(filename:split(P(I))) end, + Base = PL({priv_dir, cowboy, []}), + [?_eq(Base + 1, PL({priv_dir, cowboy, "a"})), + ?_eq(Base + 1, PL({priv_dir, cowboy, <<"a">>})), + ?_eq(Base + 1, PL({priv_dir, cowboy, [<<"a">>]})), + ?_eq(Base + 2, PL({priv_dir, cowboy, "a/b"})), + ?_eq(Base + 2, PL({priv_dir, cowboy, <<"a/b">>})), + ?_eq(Base + 2, PL({priv_dir, cowboy, [<<"a">>, <<"b">>]})), + ?_eq("a/b", P("a/b")) + ]. + + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_websocket.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_websocket.erl new file mode 100644 index 0000000..5100213 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_websocket.erl @@ -0,0 +1,530 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% +%% 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. + +%% @doc WebSocket protocol implementation. +%% +%% Supports the protocol version 0 (hixie-76), version 7 (hybi-7) +%% and version 8 (hybi-8, hybi-9 and hybi-10). +%% +%% Version 0 is supported by the following browsers: +%%
    +%%
  • Firefox 4-5 (disabled by default)
  • +%%
  • Chrome 6-13
  • +%%
  • Safari 5.0.1+
  • +%%
  • Opera 11.00+ (disabled by default)
  • +%%
+%% +%% Version 7 is supported by the following browser: +%%
    +%%
  • Firefox 6
  • +%%
+%% +%% Version 8+ is supported by the following browsers: +%%
    +%%
  • Firefox 7+
  • +%%
  • Chrome 14+
  • +%%
+-module(cowboy_http_websocket). + +-export([upgrade/4]). %% API. +-export([handler_loop/4]). %% Internal. + +-include("include/http.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-type opcode() :: 0 | 1 | 2 | 8 | 9 | 10. +-type mask_key() :: 0..16#ffffffff. + +-record(state, { + version :: 0 | 7 | 8 | 13, + handler :: module(), + opts :: any(), + challenge = undefined :: undefined | binary() | {binary(), binary()}, + timeout = infinity :: timeout(), + timeout_ref = undefined, + messages = undefined :: undefined | {atom(), atom(), atom()}, + hibernate = false, + eop :: undefined | tuple(), %% hixie-76 specific. + origin = undefined :: undefined | binary() %% hixie-76 specific. +}). + +%% @doc Upgrade a HTTP request to the WebSocket protocol. +%% +%% You do not need to call this function manually. To upgrade to the WebSocket +%% protocol, you simply need to return {upgrade, protocol, {@module}} +%% in your cowboy_http_handler:init/3 handler function. +-spec upgrade(pid(), module(), any(), #http_req{}) -> closed | none(). +upgrade(ListenerPid, Handler, Opts, Req) -> + cowboy_listener:move_connection(ListenerPid, websocket, self()), + case catch websocket_upgrade(#state{handler=Handler, opts=Opts}, Req) of + {ok, State, Req2} -> handler_init(State, Req2); + {'EXIT', _Reason} -> upgrade_error(Req) + end. + +-spec websocket_upgrade(#state{}, #http_req{}) -> {ok, #state{}, #http_req{}}. +websocket_upgrade(State, Req) -> + {ConnTokens, Req2} + = cowboy_http_req:parse_header('Connection', Req), + true = lists:member(<<"upgrade">>, ConnTokens), + %% @todo Should probably send a 426 if the Upgrade header is missing. + {[<<"websocket">>], Req3} = cowboy_http_req:parse_header('Upgrade', Req2), + {Version, Req4} = cowboy_http_req:header(<<"Sec-Websocket-Version">>, Req3), + websocket_upgrade(Version, State, Req4). + +%% @todo Handle the Sec-Websocket-Protocol header. +%% @todo Reply a proper error, don't die, if a required header is undefined. +-spec websocket_upgrade(undefined | <<_:8>>, #state{}, #http_req{}) + -> {ok, #state{}, #http_req{}}. +%% No version given. Assuming hixie-76 draft. +%% +%% We need to wait to send a reply back before trying to read the +%% third part of the challenge key, because proxies will wait for +%% a reply before sending it. Therefore we calculate the challenge +%% key only in websocket_handshake/3. +websocket_upgrade(undefined, State, Req=#http_req{meta=Meta}) -> + {Origin, Req2} = cowboy_http_req:header(<<"Origin">>, Req), + {Key1, Req3} = cowboy_http_req:header(<<"Sec-Websocket-Key1">>, Req2), + {Key2, Req4} = cowboy_http_req:header(<<"Sec-Websocket-Key2">>, Req3), + false = lists:member(undefined, [Origin, Key1, Key2]), + EOP = binary:compile_pattern(<< 255 >>), + {ok, State#state{version=0, origin=Origin, challenge={Key1, Key2}, + eop=EOP}, Req4#http_req{meta=[{websocket_version, 0}|Meta]}}; +%% Versions 7 and 8. Implementation follows the hybi 7 through 17 drafts. +websocket_upgrade(Version, State, Req=#http_req{meta=Meta}) + when Version =:= <<"7">>; Version =:= <<"8">>; + Version =:= <<"13">> -> + {Key, Req2} = cowboy_http_req:header(<<"Sec-Websocket-Key">>, Req), + false = Key =:= undefined, + Challenge = hybi_challenge(Key), + IntVersion = list_to_integer(binary_to_list(Version)), + {ok, State#state{version=IntVersion, challenge=Challenge}, + Req2#http_req{meta=[{websocket_version, IntVersion}|Meta]}}. + +-spec handler_init(#state{}, #http_req{}) -> closed | none(). +handler_init(State=#state{handler=Handler, opts=Opts}, + Req=#http_req{transport=Transport}) -> + try Handler:websocket_init(Transport:name(), Req, Opts) of + {ok, Req2, HandlerState} -> + websocket_handshake(State, Req2, HandlerState); + {ok, Req2, HandlerState, hibernate} -> + websocket_handshake(State#state{hibernate=true}, + Req2, HandlerState); + {ok, Req2, HandlerState, Timeout} -> + websocket_handshake(State#state{timeout=Timeout}, + Req2, HandlerState); + {ok, Req2, HandlerState, Timeout, hibernate} -> + websocket_handshake(State#state{timeout=Timeout, + hibernate=true}, Req2, HandlerState); + {shutdown, Req2} -> + upgrade_denied(Req2) + catch Class:Reason -> + upgrade_error(Req), + error_logger:error_msg( + "** Handler ~p terminating in websocket_init/3~n" + " for the reason ~p:~p~n** Options were ~p~n" + "** Request was ~p~n** Stacktrace: ~p~n~n", + [Handler, Class, Reason, Opts, Req, erlang:get_stacktrace()]) + end. + +-spec upgrade_error(#http_req{}) -> closed. +upgrade_error(Req) -> + {ok, _Req2} = cowboy_http_req:reply(400, [], [], + Req#http_req{resp_state=waiting}), + closed. + +%% @see cowboy_http_protocol:ensure_response/1 +-spec upgrade_denied(#http_req{}) -> closed. +upgrade_denied(#http_req{resp_state=done}) -> + closed; +upgrade_denied(Req=#http_req{resp_state=waiting}) -> + {ok, _Req2} = cowboy_http_req:reply(400, [], [], Req), + closed; +upgrade_denied(#http_req{method='HEAD', resp_state=chunks}) -> + closed; +upgrade_denied(#http_req{socket=Socket, transport=Transport, + resp_state=chunks}) -> + Transport:send(Socket, <<"0\r\n\r\n">>), + closed. + +-spec websocket_handshake(#state{}, #http_req{}, any()) -> closed | none(). +websocket_handshake(State=#state{version=0, origin=Origin, + challenge={Key1, Key2}}, Req=#http_req{socket=Socket, + transport=Transport, raw_host=Host, port=Port, + raw_path=Path, raw_qs=QS}, HandlerState) -> + Location = hixie76_location(Transport:name(), Host, Port, Path, QS), + {ok, Req2} = cowboy_http_req:upgrade_reply( + <<"101 WebSocket Protocol Handshake">>, + [{<<"Upgrade">>, <<"WebSocket">>}, + {<<"Sec-Websocket-Location">>, Location}, + {<<"Sec-Websocket-Origin">>, Origin}], + Req#http_req{resp_state=waiting}), + %% Flush the resp_sent message before moving on. + receive {cowboy_http_req, resp_sent} -> ok after 0 -> ok end, + %% We replied with a proper response. Proxies should be happy enough, + %% we can now read the 8 last bytes of the challenge keys and send + %% the challenge response directly to the socket. + case cowboy_http_req:body(8, Req2) of + {ok, Key3, Req3} -> + Challenge = hixie76_challenge(Key1, Key2, Key3), + Transport:send(Socket, Challenge), + handler_before_loop(State#state{messages=Transport:messages()}, + Req3, HandlerState, <<>>); + _Any -> + closed %% If an error happened reading the body, stop there. + end; +websocket_handshake(State=#state{challenge=Challenge}, + Req=#http_req{transport=Transport}, HandlerState) -> + {ok, Req2} = cowboy_http_req:upgrade_reply( + 101, + [{<<"Upgrade">>, <<"websocket">>}, + {<<"Sec-Websocket-Accept">>, Challenge}], + Req#http_req{resp_state=waiting}), + %% Flush the resp_sent message before moving on. + receive {cowboy_http_req, resp_sent} -> ok after 0 -> ok end, + handler_before_loop(State#state{messages=Transport:messages()}, + Req2, HandlerState, <<>>). + +-spec handler_before_loop(#state{}, #http_req{}, any(), binary()) -> closed | none(). +handler_before_loop(State=#state{hibernate=true}, + Req=#http_req{socket=Socket, transport=Transport}, + HandlerState, SoFar) -> + Transport:setopts(Socket, [{active, once}]), + State2 = handler_loop_timeout(State), + erlang:hibernate(?MODULE, handler_loop, [State2#state{hibernate=false}, + Req, HandlerState, SoFar]); +handler_before_loop(State, Req=#http_req{socket=Socket, transport=Transport}, + HandlerState, SoFar) -> + Transport:setopts(Socket, [{active, once}]), + State2 = handler_loop_timeout(State), + handler_loop(State2, Req, HandlerState, SoFar). + +-spec handler_loop_timeout(#state{}) -> #state{}. +handler_loop_timeout(State=#state{timeout=infinity}) -> + State#state{timeout_ref=undefined}; +handler_loop_timeout(State=#state{timeout=Timeout, timeout_ref=PrevRef}) -> + _ = case PrevRef of undefined -> ignore; PrevRef -> + erlang:cancel_timer(PrevRef) end, + TRef = make_ref(), + erlang:send_after(Timeout, self(), {?MODULE, timeout, TRef}), + State#state{timeout_ref=TRef}. + +%% @private +-spec handler_loop(#state{}, #http_req{}, any(), binary()) -> closed | none(). +handler_loop(State=#state{messages={OK, Closed, Error}, timeout_ref=TRef}, + Req=#http_req{socket=Socket}, HandlerState, SoFar) -> + receive + {OK, Socket, Data} -> + websocket_data(State, Req, HandlerState, + << SoFar/binary, Data/binary >>); + {Closed, Socket} -> + handler_terminate(State, Req, HandlerState, {error, closed}); + {Error, Socket, Reason} -> + handler_terminate(State, Req, HandlerState, {error, Reason}); + {?MODULE, timeout, TRef} -> + websocket_close(State, Req, HandlerState, {normal, timeout}); + {?MODULE, timeout, OlderTRef} when is_reference(OlderTRef) -> + handler_loop(State, Req, HandlerState, SoFar); + Message -> + handler_call(State, Req, HandlerState, + SoFar, websocket_info, Message, fun handler_before_loop/4) + end. + +-spec websocket_data(#state{}, #http_req{}, any(), binary()) -> closed | none(). +%% No more data. +websocket_data(State, Req, HandlerState, <<>>) -> + handler_before_loop(State, Req, HandlerState, <<>>); +%% hixie-76 close frame. +websocket_data(State=#state{version=0}, Req, HandlerState, + << 255, 0, _Rest/binary >>) -> + websocket_close(State, Req, HandlerState, {normal, closed}); +%% hixie-76 data frame. We only support the frame type 0, same as the specs. +websocket_data(State=#state{version=0, eop=EOP}, Req, HandlerState, + Data = << 0, _/binary >>) -> + case binary:match(Data, EOP) of + {Pos, 1} -> + Pos2 = Pos - 1, + << 0, Payload:Pos2/binary, 255, Rest/bits >> = Data, + handler_call(State, Req, HandlerState, + Rest, websocket_handle, {text, Payload}, fun websocket_data/4); + nomatch -> + %% @todo We probably should allow limiting frame length. + handler_before_loop(State, Req, HandlerState, Data) + end; +%% incomplete hybi data frame. +websocket_data(State=#state{version=Version}, Req, HandlerState, Data) + when Version =/= 0, byte_size(Data) =:= 1 -> + handler_before_loop(State, Req, HandlerState, Data); +%% hybi data frame. +%% @todo Handle Fin. +websocket_data(State=#state{version=Version}, Req, HandlerState, Data) + when Version =/= 0 -> + << 1:1, 0:3, Opcode:4, Mask:1, PayloadLen:7, Rest/bits >> = Data, + case {PayloadLen, Rest} of + {126, _} when Opcode >= 8 -> websocket_close( + State, Req, HandlerState, {error, protocol}); + {127, _} when Opcode >= 8 -> websocket_close( + State, Req, HandlerState, {error, protocol}); + {126, << L:16, R/bits >>} -> websocket_before_unmask( + State, Req, HandlerState, Data, R, Opcode, Mask, L); + {126, Rest} -> websocket_before_unmask( + State, Req, HandlerState, Data, Rest, Opcode, Mask, undefined); + {127, << 0:1, L:63, R/bits >>} -> websocket_before_unmask( + State, Req, HandlerState, Data, R, Opcode, Mask, L); + {127, Rest} -> websocket_before_unmask( + State, Req, HandlerState, Data, Rest, Opcode, Mask, undefined); + {PayloadLen, Rest} -> websocket_before_unmask( + State, Req, HandlerState, Data, Rest, Opcode, Mask, PayloadLen) + end; +%% Something was wrong with the frame. Close the connection. +websocket_data(State, Req, HandlerState, _Bad) -> + websocket_close(State, Req, HandlerState, {error, badframe}). + +%% hybi routing depending on whether unmasking is needed. +-spec websocket_before_unmask(#state{}, #http_req{}, any(), binary(), + binary(), opcode(), 0 | 1, non_neg_integer() | undefined) + -> closed | none(). +websocket_before_unmask(State, Req, HandlerState, Data, + Rest, Opcode, Mask, PayloadLen) -> + case {Mask, PayloadLen} of + {0, 0} -> + websocket_dispatch(State, Req, HandlerState, Rest, Opcode, <<>>); + {1, N} when N + 4 > byte_size(Rest); N =:= undefined -> + %% @todo We probably should allow limiting frame length. + handler_before_loop(State, Req, HandlerState, Data); + {1, _N} -> + << MaskKey:32, Payload:PayloadLen/binary, Rest2/bits >> = Rest, + websocket_unmask(State, Req, HandlerState, Rest2, + Opcode, Payload, MaskKey) + end. + +%% hybi unmasking. +-spec websocket_unmask(#state{}, #http_req{}, any(), binary(), + opcode(), binary(), mask_key()) -> closed | none(). +websocket_unmask(State, Req, HandlerState, RemainingData, + Opcode, Payload, MaskKey) -> + websocket_unmask(State, Req, HandlerState, RemainingData, + Opcode, Payload, MaskKey, <<>>). + +-spec websocket_unmask(#state{}, #http_req{}, any(), binary(), + opcode(), binary(), mask_key(), binary()) -> closed | none(). +websocket_unmask(State, Req, HandlerState, RemainingData, + Opcode, << O:32, Rest/bits >>, MaskKey, Acc) -> + T = O bxor MaskKey, + websocket_unmask(State, Req, HandlerState, RemainingData, + Opcode, Rest, MaskKey, << Acc/binary, T:32 >>); +websocket_unmask(State, Req, HandlerState, RemainingData, + Opcode, << O:24 >>, MaskKey, Acc) -> + << MaskKey2:24, _:8 >> = << MaskKey:32 >>, + T = O bxor MaskKey2, + websocket_dispatch(State, Req, HandlerState, RemainingData, + Opcode, << Acc/binary, T:24 >>); +websocket_unmask(State, Req, HandlerState, RemainingData, + Opcode, << O:16 >>, MaskKey, Acc) -> + << MaskKey2:16, _:16 >> = << MaskKey:32 >>, + T = O bxor MaskKey2, + websocket_dispatch(State, Req, HandlerState, RemainingData, + Opcode, << Acc/binary, T:16 >>); +websocket_unmask(State, Req, HandlerState, RemainingData, + Opcode, << O:8 >>, MaskKey, Acc) -> + << MaskKey2:8, _:24 >> = << MaskKey:32 >>, + T = O bxor MaskKey2, + websocket_dispatch(State, Req, HandlerState, RemainingData, + Opcode, << Acc/binary, T:8 >>); +websocket_unmask(State, Req, HandlerState, RemainingData, + Opcode, <<>>, _MaskKey, Acc) -> + websocket_dispatch(State, Req, HandlerState, RemainingData, + Opcode, Acc). + +%% hybi dispatching. +-spec websocket_dispatch(#state{}, #http_req{}, any(), binary(), + opcode(), binary()) -> closed | none(). +%% @todo Fragmentation. +%~ websocket_dispatch(State, Req, HandlerState, RemainingData, 0, Payload) -> +%% Text frame. +websocket_dispatch(State, Req, HandlerState, RemainingData, 1, Payload) -> + handler_call(State, Req, HandlerState, RemainingData, + websocket_handle, {text, Payload}, fun websocket_data/4); +%% Binary frame. +websocket_dispatch(State, Req, HandlerState, RemainingData, 2, Payload) -> + handler_call(State, Req, HandlerState, RemainingData, + websocket_handle, {binary, Payload}, fun websocket_data/4); +%% Close control frame. +%% @todo Handle the optional Payload. +websocket_dispatch(State, Req, HandlerState, _RemainingData, 8, _Payload) -> + websocket_close(State, Req, HandlerState, {normal, closed}); +%% Ping control frame. Send a pong back and forward the ping to the handler. +websocket_dispatch(State, Req=#http_req{socket=Socket, transport=Transport}, + HandlerState, RemainingData, 9, Payload) -> + Len = hybi_payload_length(byte_size(Payload)), + Transport:send(Socket, << 1:1, 0:3, 10:4, 0:1, Len/bits, Payload/binary >>), + handler_call(State, Req, HandlerState, RemainingData, + websocket_handle, {ping, Payload}, fun websocket_data/4); +%% Pong control frame. +websocket_dispatch(State, Req, HandlerState, RemainingData, 10, Payload) -> + handler_call(State, Req, HandlerState, RemainingData, + websocket_handle, {pong, Payload}, fun websocket_data/4). + +-spec handler_call(#state{}, #http_req{}, any(), binary(), + atom(), any(), fun()) -> closed | none(). +handler_call(State=#state{handler=Handler, opts=Opts}, Req, HandlerState, + RemainingData, Callback, Message, NextState) -> + try Handler:Callback(Message, Req, HandlerState) of + {ok, Req2, HandlerState2} -> + NextState(State, Req2, HandlerState2, RemainingData); + {ok, Req2, HandlerState2, hibernate} -> + NextState(State#state{hibernate=true}, + Req2, HandlerState2, RemainingData); + {reply, Payload, Req2, HandlerState2} -> + websocket_send(Payload, State, Req2), + NextState(State, Req2, HandlerState2, RemainingData); + {reply, Payload, Req2, HandlerState2, hibernate} -> + websocket_send(Payload, State, Req2), + NextState(State#state{hibernate=true}, + Req2, HandlerState2, RemainingData); + {shutdown, Req2, HandlerState2} -> + websocket_close(State, Req2, HandlerState2, {normal, shutdown}) + catch Class:Reason -> + error_logger:error_msg( + "** Handler ~p terminating in ~p/3~n" + " for the reason ~p:~p~n** Message was ~p~n" + "** Options were ~p~n** Handler state was ~p~n" + "** Request was ~p~n** Stacktrace: ~p~n~n", + [Handler, Callback, Class, Reason, Message, Opts, + HandlerState, Req, erlang:get_stacktrace()]), + websocket_close(State, Req, HandlerState, {error, handler}) + end. + +-spec websocket_send(binary(), #state{}, #http_req{}) -> closed | ignore. +%% hixie-76 text frame. +websocket_send({text, Payload}, #state{version=0}, + #http_req{socket=Socket, transport=Transport}) -> + Transport:send(Socket, [0, Payload, 255]); +%% Ignore all unknown frame types for compatibility with hixie 76. +websocket_send(_Any, #state{version=0}, _Req) -> + ignore; +websocket_send({Type, Payload}, _State, + #http_req{socket=Socket, transport=Transport}) -> + Opcode = case Type of + text -> 1; + binary -> 2; + ping -> 9; + pong -> 10 + end, + Len = hybi_payload_length(iolist_size(Payload)), + Transport:send(Socket, [<< 1:1, 0:3, Opcode:4, 0:1, Len/bits >>, + Payload]). + +-spec websocket_close(#state{}, #http_req{}, any(), {atom(), atom()}) -> closed. +websocket_close(State=#state{version=0}, Req=#http_req{socket=Socket, + transport=Transport}, HandlerState, Reason) -> + Transport:send(Socket, << 255, 0 >>), + handler_terminate(State, Req, HandlerState, Reason); +%% @todo Send a Payload? Using Reason is usually good but we're quite careless. +websocket_close(State, Req=#http_req{socket=Socket, + transport=Transport}, HandlerState, Reason) -> + Transport:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), + handler_terminate(State, Req, HandlerState, Reason). + +-spec handler_terminate(#state{}, #http_req{}, + any(), atom() | {atom(), atom()}) -> closed. +handler_terminate(#state{handler=Handler, opts=Opts}, + Req, HandlerState, TerminateReason) -> + try + Handler:websocket_terminate(TerminateReason, Req, HandlerState) + catch Class:Reason -> + error_logger:error_msg( + "** Handler ~p terminating in websocket_terminate/3~n" + " for the reason ~p:~p~n** Initial reason was ~p~n" + "** Options were ~p~n** Handler state was ~p~n" + "** Request was ~p~n** Stacktrace: ~p~n~n", + [Handler, Class, Reason, TerminateReason, Opts, + HandlerState, Req, erlang:get_stacktrace()]) + end, + closed. + +%% hixie-76 specific. + +-spec hixie76_challenge(binary(), binary(), binary()) -> binary(). +hixie76_challenge(Key1, Key2, Key3) -> + IntKey1 = hixie76_key_to_integer(Key1), + IntKey2 = hixie76_key_to_integer(Key2), + erlang:md5(<< IntKey1:32, IntKey2:32, Key3/binary >>). + +-spec hixie76_key_to_integer(binary()) -> integer(). +hixie76_key_to_integer(Key) -> + Number = list_to_integer([C || << C >> <= Key, C >= $0, C =< $9]), + Spaces = length([C || << C >> <= Key, C =:= 32]), + Number div Spaces. + +-spec hixie76_location(atom(), binary(), inet:ip_port(), binary(), binary()) + -> binary(). +hixie76_location(Protocol, Host, Port, Path, <<>>) -> + << (hixie76_location_protocol(Protocol))/binary, "://", Host/binary, + (hixie76_location_port(Protocol, Port))/binary, Path/binary>>; +hixie76_location(Protocol, Host, Port, Path, QS) -> + << (hixie76_location_protocol(Protocol))/binary, "://", Host/binary, + (hixie76_location_port(Protocol, Port))/binary, Path/binary, "?", QS/binary >>. + +-spec hixie76_location_protocol(atom()) -> binary(). +hixie76_location_protocol(ssl) -> <<"wss">>; +hixie76_location_protocol(_) -> <<"ws">>. + +%% @todo We should add a secure/0 function to transports +%% instead of relying on their name. +-spec hixie76_location_port(atom(), inet:ip_port()) -> binary(). +hixie76_location_port(ssl, 443) -> + <<>>; +hixie76_location_port(tcp, 80) -> + <<>>; +hixie76_location_port(_, Port) -> + <<":", (list_to_binary(integer_to_list(Port)))/binary>>. + +%% hybi specific. + +-spec hybi_challenge(binary()) -> binary(). +hybi_challenge(Key) -> + Bin = << Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" >>, + base64:encode(crypto:sha(Bin)). + +-spec hybi_payload_length(0..16#7fffffffffffffff) + -> << _:7 >> | << _:23 >> | << _:71 >>. +hybi_payload_length(N) -> + case N of + N when N =< 125 -> << N:7 >>; + N when N =< 16#ffff -> << 126:7, N:16 >>; + N when N =< 16#7fffffffffffffff -> << 127:7, N:64 >> + end. + +%% Tests. + +-ifdef(TEST). + +hixie76_location_test() -> + ?assertEqual(<<"ws://localhost/path">>, + hixie76_location(tcp, <<"localhost">>, 80, <<"/path">>, <<>>)), + ?assertEqual(<<"ws://localhost:443/path">>, + hixie76_location(tcp, <<"localhost">>, 443, <<"/path">>, <<>>)), + ?assertEqual(<<"ws://localhost:8080/path">>, + hixie76_location(tcp, <<"localhost">>, 8080, <<"/path">>, <<>>)), + ?assertEqual(<<"ws://localhost:8080/path?dummy=2785">>, + hixie76_location(tcp, <<"localhost">>, 8080, <<"/path">>, <<"dummy=2785">>)), + ?assertEqual(<<"wss://localhost/path">>, + hixie76_location(ssl, <<"localhost">>, 443, <<"/path">>, <<>>)), + ?assertEqual(<<"wss://localhost:8443/path">>, + hixie76_location(ssl, <<"localhost">>, 8443, <<"/path">>, <<>>)), + ?assertEqual(<<"wss://localhost:8443/path?dummy=2785">>, + hixie76_location(ssl, <<"localhost">>, 8443, <<"/path">>, <<"dummy=2785">>)), + ok. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_websocket_handler.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_websocket_handler.erl new file mode 100644 index 0000000..2ea0a46 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_http_websocket_handler.erl @@ -0,0 +1,60 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% +%% 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. + +%% @doc Handler for HTTP WebSocket requests. +%% +%% WebSocket handlers must implement four callbacks: websocket_init/3, +%% websocket_handle/3, websocket_info/3 and +%% websocket_terminate/3. These callbacks will only be called if the +%% connection is upgraded to WebSocket in the HTTP handler's init/3 +%% callback. They are then called in that order, although +%% websocket_handle/3 will be called for each packet received, +%% and websocket_info for each message received. +%% +%% websocket_init/3 is meant for initialization. It receives +%% information about the transport and protocol used, along with the handler +%% options from the dispatch list. You can define a request-wide state here. +%% If you are going to want to compact the request, you should probably do it +%% here. +%% +%% websocket_handle/3 receives the data from the socket. It can reply +%% something, do nothing or close the connection. +%% +%% websocket_info/3 receives messages sent to the process. It has +%% the same reply format as websocket_handle/3 described above. Note +%% that unlike in a gen_server, when websocket_info/3 +%% replies something, it is always to the socket, not to the process that +%% originated the message. +%% +%% websocket_terminate/3 is meant for cleaning up. It also receives +%% the request and the state previously defined, along with a reason for +%% termination. +%% +%% All of websocket_init/3, websocket_handle/3 and +%% websocket_info/3 can decide to hibernate the process by adding +%% an extra element to the returned tuple, containing the atom +%% hibernate. Doing so helps save memory and improve CPU usage. +-module(cowboy_http_websocket_handler). + +-export([behaviour_info/1]). + +%% @private +-spec behaviour_info(_) + -> undefined | [{websocket_handle, 3} | {websocket_info, 3} + | {websocket_init, 3} | {websocket_terminate, 3}, ...]. +behaviour_info(callbacks) -> + [{websocket_init, 3}, {websocket_handle, 3}, + {websocket_info, 3}, {websocket_terminate, 3}]; +behaviour_info(_Other) -> + undefined. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_listener.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_listener.erl new file mode 100644 index 0000000..4565b31 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_listener.erl @@ -0,0 +1,174 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% +%% 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. + +%% @doc Public API for managing listeners. +-module(cowboy_listener). +-behaviour(gen_server). + +-export([start_link/0, stop/1, + add_connection/3, move_connection/3, remove_connection/2, wait/3]). %% API. +-export([init/1, handle_call/3, handle_cast/2, + handle_info/2, terminate/2, code_change/3]). %% gen_server. + +-record(state, { + req_pools = [] :: [{atom(), non_neg_integer()}], + reqs_table, + queue = [] +}). + +%% API. + +%% @private +%% +%% We set the process priority to high because cowboy_listener is the central +%% gen_server in Cowboy and is used to manage all the incoming connections. +%% Setting the process priority to high ensures the connection-related code +%% will always be executed when a connection needs it, allowing Cowboy to +%% scale far beyond what it would with a normal priority. +-spec start_link() -> {ok, pid()}. +start_link() -> + gen_server:start_link(?MODULE, [], [{spawn_opt, [{priority, high}]}]). + +%% @private +-spec stop(pid()) -> stopped. +stop(ServerPid) -> + gen_server:call(ServerPid, stop). + +%% @doc Add a connection to the given pool in the listener. +%% +%% Pools of connections are used to restrict the maximum number of connections +%% depending on their type. By default, Cowboy add all connections to the +%% pool default. It also checks for the maximum number of connections +%% in that pool before accepting again. +%% +%% When a process managing a connection dies, the process is removed from the +%% pool. If the socket has been sent to another process, it is up to the +%% protocol code to inform the listener of the new ConnPid by removing +%% the previous and adding the new one. +-spec add_connection(pid(), atom(), pid()) -> {ok, non_neg_integer()}. +add_connection(ServerPid, Pool, ConnPid) -> + gen_server:call(ServerPid, {add_connection, Pool, ConnPid}). + +%% @doc Move a connection from one pool to another. +-spec move_connection(pid(), atom(), pid()) -> ok. +move_connection(ServerPid, DestPool, ConnPid) -> + gen_server:cast(ServerPid, {move_connection, DestPool, ConnPid}). + +%% @doc Remove the given connection from its pool. +-spec remove_connection(pid(), pid()) -> ok. +remove_connection(ServerPid, ConnPid) -> + gen_server:cast(ServerPid, {remove_connection, ConnPid}). + +%% @doc Wait until the number of connections in the given pool gets below +%% the given threshold. +%% +%% This function will not return until the number of connections in the pool +%% gets below MaxConns. It makes use of gen_server:reply/2 +%% to make the process wait for a reply indefinitely. +-spec wait(pid(), atom(), non_neg_integer()) -> ok. +wait(ServerPid, Pool, MaxConns) -> + gen_server:call(ServerPid, {wait, Pool, MaxConns}, infinity). + +%% gen_server. + +%% @private +-spec init([]) -> {ok, #state{}}. +init([]) -> + ReqsTablePid = ets:new(requests_table, [set, private]), + {ok, #state{reqs_table=ReqsTablePid}}. + +%% @private +-spec handle_call(_, _, State) + -> {reply, ignored, State} | {stop, normal, stopped, State}. +handle_call({add_connection, Pool, ConnPid}, _From, State=#state{ + req_pools=Pools, reqs_table=ReqsTable}) -> + MonitorRef = erlang:monitor(process, ConnPid), + {NbConnsRet, Pools2} = case lists:keyfind(Pool, 1, Pools) of + false -> + {1, [{Pool, 1}|Pools]}; + {Pool, NbConns} -> + NbConns2 = NbConns + 1, + {NbConns2, [{Pool, NbConns2}|lists:keydelete(Pool, 1, Pools)]} + end, + ets:insert(ReqsTable, {ConnPid, {MonitorRef, Pool}}), + {reply, {ok, NbConnsRet}, State#state{req_pools=Pools2}}; +handle_call({wait, Pool, MaxConns}, From, State=#state{ + req_pools=Pools, queue=Queue}) -> + case lists:keyfind(Pool, 1, Pools) of + {Pool, NbConns} when NbConns > MaxConns -> + {noreply, State#state{queue=[From|Queue]}}; + _Any -> + {reply, ok, State} + end; +handle_call(stop, _From, State) -> + {stop, normal, stopped, State}; +handle_call(_Request, _From, State) -> + {reply, ignored, State}. + +%% @private +-spec handle_cast(_, State) -> {noreply, State}. +handle_cast({move_connection, DestPool, ConnPid}, State=#state{ + req_pools=Pools, reqs_table=ReqsTable}) -> + {MonitorRef, SrcPool} = ets:lookup_element(ReqsTable, ConnPid, 2), + ets:insert(ReqsTable, {ConnPid, {MonitorRef, DestPool}}), + {SrcPool, SrcNbConns} = lists:keyfind(SrcPool, 1, Pools), + DestNbConns = case lists:keyfind(DestPool, 1, Pools) of + false -> 1; + {DestPool, NbConns} -> NbConns + 1 + end, + Pools2 = lists:keydelete(SrcPool, 1, lists:keydelete(DestPool, 1, Pools)), + Pools3 = [{SrcPool, SrcNbConns - 1}, {DestPool, DestNbConns}|Pools2], + {noreply, State#state{req_pools=Pools3}}; +handle_cast({remove_connection, ConnPid}, State) -> + State2 = remove_pid(ConnPid, State), + {noreply, State2}; +handle_cast(_Msg, State) -> + {noreply, State}. + +%% @private +-spec handle_info(_, State) -> {noreply, State}. +handle_info({'DOWN', _Ref, process, Pid, _Info}, State) -> + State2 = remove_pid(Pid, State), + {noreply, State2}; +handle_info(_Info, State) -> + {noreply, State}. + +%% @private +-spec terminate(_, _) -> ok. +terminate(_Reason, _State) -> + ok. + +%% @private +-spec code_change(_, State, _) -> {ok, State}. +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%% Internal. + +%% @private +-spec remove_pid(pid(), State) -> State. +remove_pid(Pid, State=#state{ + req_pools=Pools, reqs_table=ReqsTable, queue=Queue}) -> + {MonitorRef, Pool} = ets:lookup_element(ReqsTable, Pid, 2), + erlang:demonitor(MonitorRef, [flush]), + {Pool, NbConns} = lists:keyfind(Pool, 1, Pools), + Pools2 = [{Pool, NbConns - 1}|lists:keydelete(Pool, 1, Pools)], + ets:delete(ReqsTable, Pid), + case Queue of + [] -> + State#state{req_pools=Pools2}; + [Client|Queue2] -> + gen_server:reply(Client, ok), + State#state{req_pools=Pools2, queue=Queue2} + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_listener_sup.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_listener_sup.erl new file mode 100644 index 0000000..aca2b0b --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_listener_sup.erl @@ -0,0 +1,45 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% +%% 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. + +%% @private +-module(cowboy_listener_sup). +-behaviour(supervisor). + +-export([start_link/5]). %% API. +-export([init/1]). %% supervisor. + +%% API. + +-spec start_link(non_neg_integer(), module(), any(), module(), any()) + -> {ok, pid()}. +start_link(NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts) -> + {ok, SupPid} = supervisor:start_link(?MODULE, []), + {ok, ListenerPid} = supervisor:start_child(SupPid, + {cowboy_listener, {cowboy_listener, start_link, []}, + permanent, 5000, worker, [cowboy_listener]}), + {ok, ReqsPid} = supervisor:start_child(SupPid, + {cowboy_requests_sup, {cowboy_requests_sup, start_link, []}, + permanent, 5000, supervisor, [cowboy_requests_sup]}), + {ok, _PoolPid} = supervisor:start_child(SupPid, + {cowboy_acceptors_sup, {cowboy_acceptors_sup, start_link, [ + NbAcceptors, Transport, TransOpts, + Protocol, ProtoOpts, ListenerPid, ReqsPid + ]}, permanent, 5000, supervisor, [cowboy_acceptors_sup]}), + {ok, SupPid}. + +%% supervisor. + +-spec init([]) -> {ok, {{one_for_all, 10, 10}, []}}. +init([]) -> + {ok, {{one_for_all, 10, 10}, []}}. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_multipart.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_multipart.erl new file mode 100644 index 0000000..0bd123a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_multipart.erl @@ -0,0 +1,249 @@ +%% Copyright (c) 2011, Anthony Ramine +%% +%% 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. + +%% @doc Multipart parser. +-module(cowboy_multipart). + +-type part_parser() :: any(). +-type parser(T) :: fun((binary()) -> T). +-type more(T) :: T | {more, parser(T)}. +-type part_result() :: any(). +-type headers() :: any(). +-type http_headers() :: [{atom() | binary(), binary()}]. +-type body_cont() :: any(). +-type cont(T) :: fun(() -> T). +-type body_result() :: any(). +-type end_of_part() :: {end_of_part, cont(more(part_result()))}. +-type disposition() :: {binary(), [{binary(), binary()}]}. + +-export([parser/1, content_disposition/1]). + +-include_lib("eunit/include/eunit.hrl"). + +%% API. + +%% @doc Return a multipart parser for the given boundary. +-spec parser(binary()) -> part_parser(). +parser(Boundary) when is_binary(Boundary) -> + fun (Bin) when is_binary(Bin) -> parse(Bin, Boundary) end. + +%% @doc Parse a content disposition. +%% @todo Parse the MIME header instead of the HTTP one. +-spec content_disposition(binary()) -> disposition(). +content_disposition(Data) -> + cowboy_http:token_ci(Data, + fun (_Rest, <<>>) -> {error, badarg}; + (Rest, Disposition) -> + cowboy_http:content_type_params(Rest, + fun (Params) -> {Disposition, Params} end, []) + end). + +%% Internal. + +%% @doc Entry point of the multipart parser, skips over the preamble if any. +-spec parse(binary(), binary()) -> more(part_result()). +parse(Bin, Boundary) when byte_size(Bin) >= byte_size(Boundary) + 2 -> + BoundarySize = byte_size(Boundary), + Pattern = pattern(Boundary), + case Bin of + <<"--", Boundary:BoundarySize/binary, Rest/binary>> -> + % Data starts with initial boundary, skip preamble parsing. + parse_boundary_tail(Rest, Pattern); + _ -> + % Parse preamble. + skip(Bin, Pattern) + end; +parse(Bin, Boundary) -> + % Not enough data to know if the data begins with a boundary. + more(Bin, fun (NewBin) -> parse(NewBin, Boundary) end). + +-type pattern() :: {binary:cp(), non_neg_integer()}. + +%% @doc Return a compiled binary pattern with its size in bytes. +%% The pattern is the boundary prepended with "\r\n--". +-spec pattern(binary()) -> pattern(). +pattern(Boundary) -> + MatchPattern = <<"\r\n--", Boundary/binary>>, + {binary:compile_pattern(MatchPattern), byte_size(MatchPattern)}. + +%% @doc Parse remaining characters of a line beginning with the boundary. +%% If followed by "--", eof is returned and parsing is finished. +-spec parse_boundary_tail(binary(), pattern()) -> more(part_result()). +parse_boundary_tail(Bin, Pattern) when byte_size(Bin) >= 2 -> + case Bin of + <<"--", _Rest/binary>> -> + % Boundary is followed by "--", end parsing. + eof; + _ -> + % No dash after boundary, proceed with unknown chars and lwsp + % removal. + parse_boundary_eol(Bin, Pattern) + end; +parse_boundary_tail(Bin, Pattern) -> + % Boundary may be followed by "--", need more data. + more(Bin, fun (NewBin) -> parse_boundary_tail(NewBin, Pattern) end). + +%% @doc Skip whitespace and unknown chars until CRLF. +-spec parse_boundary_eol(binary(), pattern()) -> more(part_result()). +parse_boundary_eol(Bin, Pattern) -> + case binary:match(Bin, <<"\r\n">>) of + {CrlfStart, _Length} -> + % End of line found, remove optional whitespace. + <<_:CrlfStart/binary, Rest/binary>> = Bin, + Fun = fun (Rest2) -> parse_boundary_crlf(Rest2, Pattern) end, + cowboy_http:whitespace(Rest, Fun); + nomatch -> + % CRLF not found in the given binary. + RestStart = lists:max([byte_size(Bin) - 1, 0]), + <<_:RestStart/binary, Rest/binary>> = Bin, + more(Rest, fun (NewBin) -> parse_boundary_eol(NewBin, Pattern) end) + end. + +-spec parse_boundary_crlf(binary(), pattern()) -> more(part_result()). +parse_boundary_crlf(<<"\r\n", Rest/binary>>, Pattern) -> + % The binary is at least 2 bytes long as this function is only called by + % parse_boundary_eol/3 when CRLF has been found so a more tuple will never + % be returned from here. + parse_headers(Rest, Pattern); +parse_boundary_crlf(Bin, Pattern) -> + % Unspecified behaviour here: RFC 2046 doesn't say what to do when LWSP is + % not followed directly by a new line. In this implementation it is + % considered part of the boundary so EOL needs to be searched again. + parse_boundary_eol(Bin, Pattern). + +-spec parse_headers(binary(), pattern()) -> more(part_result()). +parse_headers(Bin, Pattern) -> + parse_headers(Bin, Pattern, []). + +-spec parse_headers(binary(), pattern(), http_headers()) -> more(part_result()). +parse_headers(Bin, Pattern, Acc) -> + case erlang:decode_packet(httph_bin, Bin, []) of + {ok, {http_header, _, Name, _, Value}, Rest} -> + parse_headers(Rest, Pattern, [{Name, Value} | Acc]); + {ok, http_eoh, Rest} -> + Headers = lists:reverse(Acc), + {headers, Headers, fun () -> parse_body(Rest, Pattern) end}; + {ok, {http_error, _}, _} -> + % Skip malformed parts. + skip(Bin, Pattern); + {more, _} -> + more(Bin, fun (NewBin) -> parse_headers(NewBin, Pattern, Acc) end) + end. + +-spec parse_body(binary(), pattern()) -> more(body_result()). +parse_body(Bin, Pattern = {P, PSize}) when byte_size(Bin) >= PSize -> + case binary:match(Bin, P) of + {0, _Length} -> + <<_:PSize/binary, Rest/binary>> = Bin, + end_of_part(Rest, Pattern); + {BoundaryStart, _Length} -> + % Boundary found, this is the latest partial body that will be + % returned for this part. + <> = Bin, + FResult = end_of_part(Rest, Pattern), + {body, PBody, fun () -> FResult end}; + nomatch -> + PartialLength = byte_size(Bin) - PSize + 1, + <> = Bin, + {body, PBody, fun () -> parse_body(Rest, Pattern) end} + end; +parse_body(Bin, Pattern) -> + more(Bin, fun (NewBin) -> parse_body(NewBin, Pattern) end). + +-spec end_of_part(binary(), pattern()) -> end_of_part(). +end_of_part(Bin, Pattern) -> + {end_of_part, fun () -> parse_boundary_tail(Bin, Pattern) end}. + +-spec skip(binary(), pattern()) -> more(part_result()). +skip(Bin, Pattern = {P, PSize}) -> + case binary:match(Bin, P) of + {BoundaryStart, _Length} -> + % Boundary found, proceed with parsing of the next part. + RestStart = BoundaryStart + PSize, + <<_:RestStart/binary, Rest/binary>> = Bin, + parse_boundary_tail(Rest, Pattern); + nomatch -> + % Boundary not found, need more data. + RestStart = lists:max([byte_size(Bin) - PSize + 1, 0]), + <<_:RestStart/binary, Rest/binary>> = Bin, + more(Rest, fun (NewBin) -> skip(NewBin, Pattern) end) + end. + +-spec more(binary(), parser(T)) -> {more, parser(T)}. +more(<<>>, F) -> + {more, F}; +more(Bin, InnerF) -> + F = fun (NewData) when is_binary(NewData) -> + InnerF(<>) + end, + {more, F}. + +%% Tests. + +-ifdef(TEST). + +multipart_test_() -> + %% {Body, Result} + Tests = [ + {<<"--boundary--">>, []}, + {<<"preamble\r\n--boundary--">>, []}, + {<<"--boundary--\r\nepilogue">>, []}, + {<<"\r\n--boundary\r\nA:b\r\nC:d\r\n\r\n\r\n--boundary--">>, + [{[{<<"A">>, <<"b">>}, {<<"C">>, <<"d">>}], <<>>}]}, + { + << + "--boundary\r\nX-Name:answer\r\n\r\n42" + "\r\n--boundary\r\nServer:Cowboy\r\n\r\nIt rocks!\r\n" + "\r\n--boundary--" + >>, + [ + {[{<<"X-Name">>, <<"answer">>}], <<"42">>}, + {[{'Server', <<"Cowboy">>}], <<"It rocks!\r\n">>} + ] + } + ], + [{title(V), fun () -> R = acc_multipart(V) end} || {V, R} <- Tests]. + +acc_multipart(V) -> + acc_multipart((parser(<<"boundary">>))(V), []). + +acc_multipart({headers, Headers, Cont}, Acc) -> + acc_multipart(Cont(), [{Headers, []}|Acc]); +acc_multipart({body, Body, Cont}, [{Headers, BodyAcc}|Acc]) -> + acc_multipart(Cont(), [{Headers, [Body|BodyAcc]}|Acc]); +acc_multipart({end_of_part, Cont}, [{Headers, BodyAcc}|Acc]) -> + Body = list_to_binary(lists:reverse(BodyAcc)), + acc_multipart(Cont(), [{Headers, Body}|Acc]); +acc_multipart(eof, Acc) -> + lists:reverse(Acc). + +content_disposition_test_() -> + %% {Disposition, Result} + Tests = [ + {<<"form-data; name=id">>, {<<"form-data">>, [{<<"name">>, <<"id">>}]}}, + {<<"inline">>, {<<"inline">>, []}}, + {<<"attachment; \tfilename=brackets-slides.pdf">>, + {<<"attachment">>, [{<<"filename">>, <<"brackets-slides.pdf">>}]}} + ], + [{title(V), fun () -> R = content_disposition(V) end} || {V, R} <- Tests]. + +title(Bin) -> + Title = lists:foldl( + fun ({T, R}, V) -> re:replace(V, T, R, [global]) end, + Bin, + [{"\t", "\\\\t"}, {"\r", "\\\\r"}, {"\n", "\\\\n"}] + ), + iolist_to_binary(Title). + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_protocol.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_protocol.erl new file mode 100644 index 0000000..34bb1a1 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_protocol.erl @@ -0,0 +1,61 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% Copyright (c) 2011, Michiel Hakvoort +%% +%% 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. + +%% @doc Cowboy protocol. +%% +%% A Cowboy protocol must implement one callback: start_link/4. +%% +%% start_link/4 is meant for the initialization of the +%% protocol process. +%% It receives the pid to the listener's gen_server, the client socket, +%% the module name of the chosen transport and the options defined when +%% starting the listener. The start_link/4 function must follow +%% the supervisor start function specification. +%% +%% After initializing your protocol, it is recommended to call the +%% function cowboy:accept_ack/1 with the ListenerPid as argument, +%% as it will ensure Cowboy has been able to fully initialize the socket. +%% Anything you do past this point is up to you! +%% +%% If you need to change some socket options, like enabling raw mode +%% for example, you can call the Transport:setopts/2 function. +%% It is the protocol's responsability to manage the socket usage, +%% there should be no need for an user to specify that kind of options +%% while starting a listener. +%% +%% You should definitely look at the cowboy_http_protocol module for +%% a great example of fast request handling if you need to. +%% Otherwise it's probably safe to use {active, once} mode +%% and handle everything as it comes. +%% +%% Note that while you technically can run a protocol handler directly +%% as a gen_server or a gen_fsm, it's probably not a good idea, +%% as the only call you'll ever receive from Cowboy is the +%% start_link/4 call. On the other hand, feel free to write +%% a very basic protocol handler which then forwards requests to a +%% gen_server or gen_fsm. By doing so however you must take care to +%% supervise their processes as Cowboy only knows about the protocol +%% handler itself. +-module(cowboy_protocol). + +-export([behaviour_info/1]). + +%% @private +-spec behaviour_info(_) + -> undefined | [{start_link, 4}, ...]. +behaviour_info(callbacks) -> + [{start_link, 4}]; +behaviour_info(_Other) -> + undefined. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_requests_sup.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_requests_sup.erl new file mode 100644 index 0000000..87d5352 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_requests_sup.erl @@ -0,0 +1,38 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% +%% 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. + +%% @private +-module(cowboy_requests_sup). +-behaviour(supervisor). + +-export([start_link/0, start_request/5]). %% API. +-export([init/1]). %% supervisor. + +%% API. + +-spec start_link() -> {ok, pid()}. +start_link() -> + supervisor:start_link(?MODULE, []). + +-spec start_request(pid(), inet:socket(), module(), module(), any()) + -> {ok, pid()}. +start_request(ListenerPid, Socket, Transport, Protocol, Opts) -> + Protocol:start_link(ListenerPid, Socket, Transport, Opts). + +%% supervisor. + +-spec init([]) -> {ok, {{simple_one_for_one, 0, 1}, [{_, _, _, _, _, _}, ...]}}. +init([]) -> + {ok, {{simple_one_for_one, 0, 1}, [{?MODULE, {?MODULE, start_request, []}, + temporary, brutal_kill, worker, [?MODULE]}]}}. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_ssl_transport.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_ssl_transport.erl new file mode 100644 index 0000000..bf8b1fb --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_ssl_transport.erl @@ -0,0 +1,164 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% +%% 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. + +%% @doc SSL transport API. +%% +%% Wrapper around ssl implementing the Cowboy transport API. +%% +%% This transport requires the crypto, public_key +%% and ssl applications to be started. If they aren't started, +%% it will try to start them itself before opening a port to listen. +%% Applications aren't stopped when the listening socket is closed, though. +%% +%% @see ssl +-module(cowboy_ssl_transport). +-export([name/0, messages/0, listen/1, accept/2, recv/3, send/2, setopts/2, + controlling_process/2, peername/1, close/1]). + +%% @doc Name of this transport API, ssl. +-spec name() -> ssl. +name() -> ssl. + +%% @doc Atoms used in the process messages sent by this API. +%% +%% They identify incoming data, closed connection and errors when receiving +%% data in active mode. +-spec messages() -> {ssl, ssl_closed, ssl_error}. +messages() -> {ssl, ssl_closed, ssl_error}. + +%% @doc Setup a socket to listen on the given port on the local host. +%% +%% The available options are: +%%
+%%
port
Mandatory. TCP port number to open.
+%%
backlog
Maximum length of the pending connections queue. +%% Defaults to 1024.
+%%
ip
Interface to listen on. Listen on all interfaces +%% by default.
+%%
certfile
Mandatory. Path to a file containing the user's +%% certificate.
+%%
keyfile
Mandatory. Path to the file containing the user's +%% private PEM encoded key.
+%%
cacertfile
Optional. Path to file containing PEM encoded +%% CA certificates (trusted certificates used for verifying a peer +%% certificate).
+%%
password
Mandatory. String containing the user's password. +%% All private keyfiles must be password protected currently.
+%%
+%% +%% @see ssl:listen/2 +%% @todo The password option shouldn't be mandatory. +-spec listen([{port, inet:ip_port()} | {certfile, string()} + | {keyfile, string()} | {password, string()} + | {cacertfile, string()} | {ip, inet:ip_address()}]) + -> {ok, ssl:sslsocket()} | {error, atom()}. +listen(Opts) -> + require([crypto, public_key, ssl]), + {port, Port} = lists:keyfind(port, 1, Opts), + Backlog = proplists:get_value(backlog, Opts, 1024), + {certfile, CertFile} = lists:keyfind(certfile, 1, Opts), + {keyfile, KeyFile} = lists:keyfind(keyfile, 1, Opts), + {password, Password} = lists:keyfind(password, 1, Opts), + ListenOpts0 = [binary, {active, false}, + {backlog, Backlog}, {packet, raw}, {reuseaddr, true}, + {certfile, CertFile}, {keyfile, KeyFile}, {password, Password}], + ListenOpts1 = + case lists:keyfind(ip, 1, Opts) of + false -> ListenOpts0; + Ip -> [Ip|ListenOpts0] + end, + ListenOpts = + case lists:keyfind(cacertfile, 1, Opts) of + false -> ListenOpts1; + CACertFile -> [CACertFile|ListenOpts1] + end, + ssl:listen(Port, ListenOpts). + +%% @doc Accept an incoming connection on a listen socket. +%% +%% Note that this function does both the transport accept and +%% the SSL handshake. +%% +%% @see ssl:transport_accept/2 +%% @see ssl:ssl_accept/2 +-spec accept(ssl:sslsocket(), timeout()) + -> {ok, ssl:sslsocket()} | {error, closed | timeout | atom()}. +accept(LSocket, Timeout) -> + case ssl:transport_accept(LSocket, Timeout) of + {ok, CSocket} -> + ssl_accept(CSocket, Timeout); + {error, Reason} -> + {error, Reason} + end. + +%% @doc Receive a packet from a socket in passive mode. +%% @see ssl:recv/3 +-spec recv(ssl:sslsocket(), non_neg_integer(), timeout()) + -> {ok, any()} | {error, closed | atom()}. +recv(Socket, Length, Timeout) -> + ssl:recv(Socket, Length, Timeout). + +%% @doc Send a packet on a socket. +%% @see ssl:send/2 +-spec send(ssl:sslsocket(), iolist()) -> ok | {error, atom()}. +send(Socket, Packet) -> + ssl:send(Socket, Packet). + +%% @doc Set one or more options for a socket. +%% @see ssl:setopts/2 +-spec setopts(ssl:sslsocket(), list()) -> ok | {error, atom()}. +setopts(Socket, Opts) -> + ssl:setopts(Socket, Opts). + +%% @doc Assign a new controlling process Pid to Socket. +%% @see ssl:controlling_process/2 +-spec controlling_process(ssl:sslsocket(), pid()) + -> ok | {error, closed | not_owner | atom()}. +controlling_process(Socket, Pid) -> + ssl:controlling_process(Socket, Pid). + +%% @doc Return the address and port for the other end of a connection. +%% @see ssl:peername/1 +-spec peername(ssl:sslsocket()) + -> {ok, {inet:ip_address(), inet:ip_port()}} | {error, atom()}. +peername(Socket) -> + ssl:peername(Socket). + +%% @doc Close a TCP socket. +%% @see ssl:close/1 +-spec close(ssl:sslsocket()) -> ok. +close(Socket) -> + ssl:close(Socket). + +%% Internal. + +-spec require(list(module())) -> ok. +require([]) -> + ok; +require([App|Tail]) -> + case application:start(App) of + ok -> ok; + {error, {already_started, App}} -> ok + end, + require(Tail). + +-spec ssl_accept(ssl:sslsocket(), timeout()) + -> {ok, ssl:sslsocket()} | {error, closed | timeout | atom()}. +ssl_accept(Socket, Timeout) -> + case ssl:ssl_accept(Socket, Timeout) of + ok -> + {ok, Socket}; + {error, Reason} -> + {error, Reason} + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_sup.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_sup.erl new file mode 100644 index 0000000..34591bc --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_sup.erl @@ -0,0 +1,36 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% +%% 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. + +%% @private +-module(cowboy_sup). +-behaviour(supervisor). + +-export([start_link/0]). %% API. +-export([init/1]). %% supervisor. + +-define(SUPERVISOR, ?MODULE). + +%% API. + +-spec start_link() -> {ok, pid()}. +start_link() -> + supervisor:start_link({local, ?SUPERVISOR}, ?MODULE, []). + +%% supervisor. + +-spec init([]) -> {ok, {{one_for_one, 10, 10}, [{_, _, _, _, _, _}, ...]}}. +init([]) -> + Procs = [{cowboy_clock, {cowboy_clock, start_link, []}, + permanent, 5000, worker, [cowboy_clock]}], + {ok, {{one_for_one, 10, 10}, Procs}}. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_tcp_transport.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_tcp_transport.erl new file mode 100644 index 0000000..c1dad62 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_tcp_transport.erl @@ -0,0 +1,106 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% +%% 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. + +%% @doc TCP transport API. +%% +%% Wrapper around gen_tcp implementing the Cowboy transport API. +%% +%% @see gen_tcp +-module(cowboy_tcp_transport). + +-export([name/0, messages/0, listen/1, accept/2, recv/3, send/2, setopts/2, + controlling_process/2, peername/1, close/1]). + +%% @doc Name of this transport API, tcp. +-spec name() -> tcp. +name() -> tcp. + +%% @doc Atoms used in the process messages sent by this API. +%% +%% They identify incoming data, closed connection and errors when receiving +%% data in active mode. +-spec messages() -> {tcp, tcp_closed, tcp_error}. +messages() -> {tcp, tcp_closed, tcp_error}. + +%% @doc Setup a socket to listen on the given port on the local host. +%% +%% The available options are: +%%
+%%
port
Mandatory. TCP port number to open.
+%%
backlog
Maximum length of the pending connections queue. +%% Defaults to 1024.
+%%
ip
Interface to listen on. Listen on all interfaces +%% by default.
+%%
+%% +%% @see gen_tcp:listen/2 +-spec listen([{port, inet:ip_port()} | {ip, inet:ip_address()}]) + -> {ok, inet:socket()} | {error, atom()}. +listen(Opts) -> + {port, Port} = lists:keyfind(port, 1, Opts), + Backlog = proplists:get_value(backlog, Opts, 1024), + ListenOpts0 = [binary, {active, false}, + {backlog, Backlog}, {packet, raw}, {reuseaddr, true}], + ListenOpts = + case lists:keyfind(ip, 1, Opts) of + false -> ListenOpts0; + Ip -> [Ip|ListenOpts0] + end, + gen_tcp:listen(Port, ListenOpts). + +%% @doc Accept an incoming connection on a listen socket. +%% @see gen_tcp:accept/2 +-spec accept(inet:socket(), timeout()) + -> {ok, inet:socket()} | {error, closed | timeout | atom()}. +accept(LSocket, Timeout) -> + gen_tcp:accept(LSocket, Timeout). + +%% @doc Receive a packet from a socket in passive mode. +%% @see gen_tcp:recv/3 +-spec recv(inet:socket(), non_neg_integer(), timeout()) + -> {ok, any()} | {error, closed | atom()}. +recv(Socket, Length, Timeout) -> + gen_tcp:recv(Socket, Length, Timeout). + +%% @doc Send a packet on a socket. +%% @see gen_tcp:send/2 +-spec send(inet:socket(), iolist()) -> ok | {error, atom()}. +send(Socket, Packet) -> + gen_tcp:send(Socket, Packet). + +%% @doc Set one or more options for a socket. +%% @see inet:setopts/2 +-spec setopts(inet:socket(), list()) -> ok | {error, atom()}. +setopts(Socket, Opts) -> + inet:setopts(Socket, Opts). + +%% @doc Assign a new controlling process Pid to Socket. +%% @see gen_tcp:controlling_process/2 +-spec controlling_process(inet:socket(), pid()) + -> ok | {error, closed | not_owner | atom()}. +controlling_process(Socket, Pid) -> + gen_tcp:controlling_process(Socket, Pid). + +%% @doc Return the address and port for the other end of a connection. +%% @see inet:peername/1 +-spec peername(inet:socket()) + -> {ok, {inet:ip_address(), inet:ip_port()}} | {error, atom()}. +peername(Socket) -> + inet:peername(Socket). + +%% @doc Close a TCP socket. +%% @see gen_tcp:close/1 +-spec close(inet:socket()) -> ok. +close(Socket) -> + gen_tcp:close(Socket). diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/chunked_handler.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/chunked_handler.erl new file mode 100644 index 0000000..d246d51 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/chunked_handler.erl @@ -0,0 +1,17 @@ +%% Feel free to use, reuse and abuse the code in this file. + +-module(chunked_handler). +-behaviour(cowboy_http_handler). +-export([init/3, handle/2, terminate/2]). + +init({_Transport, http}, Req, _Opts) -> + {ok, Req, undefined}. + +handle(Req, State) -> + {ok, Req2} = cowboy_http_req:chunked_reply(200, Req), + cowboy_http_req:chunk("chunked_handler\r\n", Req2), + cowboy_http_req:chunk("works fine!", Req2), + {ok, Req2, State}. + +terminate(_Req, _State) -> + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/dispatcher_prop.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/dispatcher_prop.erl new file mode 100644 index 0000000..b6a1c92 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/dispatcher_prop.erl @@ -0,0 +1,68 @@ +%% Copyright (c) 2011, Magnus Klaar +%% Copyright (c) 2011, Loïc Hoguin +%% +%% 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. + +-module(dispatcher_prop). +-include_lib("proper/include/proper.hrl"). + +%% Generators. + +hostname_head_char() -> + oneof([choose($a, $z), choose($A, $Z), choose($0, $9)]). + +hostname_char() -> + oneof([choose($a, $z), choose($A, $Z), choose($0, $9), $-]). + +hostname_label() -> + ?SUCHTHAT(Label, [hostname_head_char()|list(hostname_char())], + length(Label) < 64). + +hostname() -> + ?SUCHTHAT(Hostname, + ?LET(Labels, list(hostname_label()), string:join(Labels, ".")), + length(Hostname) > 0 andalso length(Hostname) =< 255). + +port_number() -> + choose(1, 16#ffff). + +port_str() -> + oneof(["", ?LET(Port, port_number(), ":" ++ integer_to_list(Port))]). + +server() -> + ?LET({Hostname, PortStr}, {hostname(), port_str()}, + list_to_binary(Hostname ++ PortStr)). + +%% Properties. + +prop_split_host_symmetric() -> + ?FORALL(Server, server(), + begin case cowboy_dispatcher:split_host(Server) of + {Tokens, RawHost, undefined} -> + (Server == RawHost) and (Server == binary_join(Tokens, ".")); + {Tokens, RawHost, Port} -> + PortBin = (list_to_binary(":" ++ integer_to_list(Port))), + (Server == << RawHost/binary, PortBin/binary >>) + and (Server == << (binary_join(Tokens, "."))/binary, + PortBin/binary >>) + end end). + +%% Internal. + +%% Contributed by MononcQc on #erlounge. +binary_join(Flowers, Leaf) -> + case Flowers of + [] -> <<>>; + [Petal|Pot] -> iolist_to_binary( + [Petal | [[Leaf | Pollen] || Pollen <- Pot]]) + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_SUITE.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_SUITE.erl new file mode 100644 index 0000000..bad91a8 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_SUITE.erl @@ -0,0 +1,613 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% Copyright (c) 2011, Anthony Ramine +%% +%% 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. + +-module(http_SUITE). + +-include_lib("common_test/include/ct.hrl"). + +-export([all/0, groups/0, init_per_suite/1, end_per_suite/1, + init_per_group/2, end_per_group/2]). %% ct. +-export([chunked_response/1, headers_dupe/1, headers_huge/1, + keepalive_nl/1, max_keepalive/1, nc_rand/1, nc_zero/1, + pipeline/1, raw/1, set_resp_header/1, set_resp_overwrite/1, + set_resp_body/1, stream_body_set_resp/1, response_as_req/1, + static_mimetypes_function/1, static_attribute_etag/1, + static_function_etag/1, multipart/1]). %% http. +-export([http_200/1, http_404/1, handler_errors/1, + file_200/1, file_403/1, dir_403/1, file_404/1, + file_400/1]). %% http and https. +-export([http_10_hostless/1]). %% misc. +-export([rest_simple/1, rest_keepalive/1, rest_keepalive_post/1]). %% rest. + +%% ct. + +all() -> + [{group, http}, {group, https}, {group, misc}, {group, rest}]. + +groups() -> + BaseTests = [http_200, http_404, handler_errors, + file_200, file_403, dir_403, file_404, file_400], + [{http, [], [chunked_response, headers_dupe, headers_huge, + keepalive_nl, max_keepalive, nc_rand, nc_zero, pipeline, raw, + set_resp_header, set_resp_overwrite, + set_resp_body, response_as_req, stream_body_set_resp, + static_mimetypes_function, static_attribute_etag, + static_function_etag, multipart] ++ BaseTests}, + {https, [], BaseTests}, + {misc, [], [http_10_hostless]}, + {rest, [], [rest_simple, rest_keepalive, rest_keepalive_post]}]. + +init_per_suite(Config) -> + application:start(inets), + application:start(cowboy), + Config. + +end_per_suite(_Config) -> + application:stop(cowboy), + application:stop(inets), + ok. + +init_per_group(http, Config) -> + Port = 33080, + Config1 = init_static_dir(Config), + cowboy:start_listener(http, 100, + cowboy_tcp_transport, [{port, Port}], + cowboy_http_protocol, [{max_keepalive, 50}, + {dispatch, init_http_dispatch(Config1)}] + ), + [{scheme, "http"}, {port, Port}|Config1]; +init_per_group(https, Config) -> + Port = 33081, + Config1 = init_static_dir(Config), + application:start(crypto), + application:start(public_key), + application:start(ssl), + DataDir = ?config(data_dir, Config), + cowboy:start_listener(https, 100, + cowboy_ssl_transport, [ + {port, Port}, {certfile, DataDir ++ "cert.pem"}, + {keyfile, DataDir ++ "key.pem"}, {password, "cowboy"}], + cowboy_http_protocol, [{dispatch, init_https_dispatch(Config1)}] + ), + [{scheme, "https"}, {port, Port}|Config1]; +init_per_group(misc, Config) -> + Port = 33082, + cowboy:start_listener(misc, 100, + cowboy_tcp_transport, [{port, Port}], + cowboy_http_protocol, [{dispatch, [{'_', [ + {[], http_handler, []} + ]}]}]), + [{port, Port}|Config]; +init_per_group(rest, Config) -> + Port = 33083, + cowboy:start_listener(reset, 100, + cowboy_tcp_transport, [{port, Port}], + cowboy_http_protocol, [{dispatch, [{'_', [ + {[<<"simple">>], rest_simple_resource, []}, + {[<<"forbidden_post">>], rest_forbidden_resource, [true]}, + {[<<"simple_post">>], rest_forbidden_resource, [false]} + ]}]}]), + [{port, Port}|Config]. + +end_per_group(https, Config) -> + cowboy:stop_listener(https), + application:stop(ssl), + application:stop(public_key), + application:stop(crypto), + end_static_dir(Config), + ok; +end_per_group(http, Config) -> + cowboy:stop_listener(http), + end_static_dir(Config); +end_per_group(Listener, _Config) -> + cowboy:stop_listener(Listener), + ok. + +%% Dispatch configuration. + +init_http_dispatch(Config) -> + [ + {[<<"localhost">>], [ + {[<<"chunked_response">>], chunked_handler, []}, + {[<<"init_shutdown">>], http_handler_init_shutdown, []}, + {[<<"long_polling">>], http_handler_long_polling, []}, + {[<<"headers">>, <<"dupe">>], http_handler, + [{headers, [{<<"Connection">>, <<"close">>}]}]}, + {[<<"set_resp">>, <<"header">>], http_handler_set_resp, + [{headers, [{<<"Vary">>, <<"Accept">>}]}]}, + {[<<"set_resp">>, <<"overwrite">>], http_handler_set_resp, + [{headers, [{<<"Server">>, <<"DesireDrive/1.0">>}]}]}, + {[<<"set_resp">>, <<"body">>], http_handler_set_resp, + [{body, <<"A flameless dance does not equal a cycle">>}]}, + {[<<"stream_body">>, <<"set_resp">>], http_handler_stream_body, + [{reply, set_resp}, {body, <<"stream_body_set_resp">>}]}, + {[<<"static">>, '...'], cowboy_http_static, + [{directory, ?config(static_dir, Config)}, + {mimetypes, [{<<".css">>, [<<"text/css">>]}]}]}, + {[<<"static_mimetypes_function">>, '...'], cowboy_http_static, + [{directory, ?config(static_dir, Config)}, + {mimetypes, {fun(Path, data) when is_binary(Path) -> + [<<"text/html">>] end, data}}]}, + {[<<"handler_errors">>], http_handler_errors, []}, + {[<<"static_attribute_etag">>, '...'], cowboy_http_static, + [{directory, ?config(static_dir, Config)}, + {etag, {attributes, [filepath, filesize, inode, mtime]}}]}, + {[<<"static_function_etag">>, '...'], cowboy_http_static, + [{directory, ?config(static_dir, Config)}, + {etag, {fun static_function_etag/2, etag_data}}]}, + {[<<"multipart">>], http_handler_multipart, []}, + {[], http_handler, []} + ]} + ]. + +init_https_dispatch(Config) -> + init_http_dispatch(Config). + + +init_static_dir(Config) -> + Dir = filename:join(?config(priv_dir, Config), "static"), + Level1 = fun(Name) -> filename:join(Dir, Name) end, + ok = file:make_dir(Dir), + ok = file:write_file(Level1("test_file"), "test_file\n"), + ok = file:write_file(Level1("test_file.css"), "test_file.css\n"), + ok = file:write_file(Level1("test_noread"), "test_noread\n"), + ok = file:change_mode(Level1("test_noread"), 8#0333), + ok = file:write_file(Level1("test.html"), "test.html\n"), + ok = file:make_dir(Level1("test_dir")), + [{static_dir, Dir}|Config]. + +end_static_dir(Config) -> + Dir = ?config(static_dir, Config), + Level1 = fun(Name) -> filename:join(Dir, Name) end, + ok = file:delete(Level1("test_file")), + ok = file:delete(Level1("test_file.css")), + ok = file:delete(Level1("test_noread")), + ok = file:delete(Level1("test.html")), + ok = file:del_dir(Level1("test_dir")), + ok = file:del_dir(Dir), + Config. + +%% http. + +chunked_response(Config) -> + {ok, {{"HTTP/1.1", 200, "OK"}, _Headers, "chunked_handler\r\nworks fine!"}} = + httpc:request(build_url("/chunked_response", Config)). + +headers_dupe(Config) -> + {port, Port} = lists:keyfind(port, 1, Config), + {ok, Socket} = gen_tcp:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + ok = gen_tcp:send(Socket, "GET /headers/dupe HTTP/1.1\r\n" + "Host: localhost\r\nConnection: keep-alive\r\n\r\n"), + {ok, Data} = gen_tcp:recv(Socket, 0, 6000), + {_Start, _Length} = binary:match(Data, <<"Connection: close">>), + nomatch = binary:match(Data, <<"Connection: keep-alive">>), + {error, closed} = gen_tcp:recv(Socket, 0, 1000). + +headers_huge(Config) -> + Cookie = lists:flatten(["whatever_man_biiiiiiiiiiiig_cookie_me_want_77=" + "Wed Apr 06 2011 10:38:52 GMT-0500 (CDT)" || _N <- lists:seq(1, 40)]), + {_Packet, 200} = raw_req(["GET / HTTP/1.0\r\nHost: localhost\r\n" + "Set-Cookie: ", Cookie, "\r\n\r\n"], Config). + +keepalive_nl(Config) -> + {port, Port} = lists:keyfind(port, 1, Config), + {ok, Socket} = gen_tcp:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + ok = keepalive_nl_loop(Socket, 10), + ok = gen_tcp:close(Socket). + +keepalive_nl_loop(_Socket, 0) -> + ok; +keepalive_nl_loop(Socket, N) -> + ok = gen_tcp:send(Socket, "GET / HTTP/1.1\r\n" + "Host: localhost\r\nConnection: keep-alive\r\n\r\n"), + {ok, Data} = gen_tcp:recv(Socket, 0, 6000), + {0, 12} = binary:match(Data, <<"HTTP/1.1 200">>), + nomatch = binary:match(Data, <<"Connection: close">>), + ok = gen_tcp:send(Socket, "\r\n"), %% extra nl + keepalive_nl_loop(Socket, N - 1). + +max_keepalive(Config) -> + {port, Port} = lists:keyfind(port, 1, Config), + {ok, Socket} = gen_tcp:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + ok = max_keepalive_loop(Socket, 50), + {error, closed} = gen_tcp:recv(Socket, 0, 1000). + +max_keepalive_loop(_Socket, 0) -> + ok; +max_keepalive_loop(Socket, N) -> + ok = gen_tcp:send(Socket, "GET / HTTP/1.1\r\n" + "Host: localhost\r\nConnection: keep-alive\r\n\r\n"), + {ok, Data} = gen_tcp:recv(Socket, 0, 6000), + {0, 12} = binary:match(Data, <<"HTTP/1.1 200">>), + case N of + 1 -> {_, _} = binary:match(Data, <<"Connection: close">>); + N -> nomatch = binary:match(Data, <<"Connection: close">>) + end, + keepalive_nl_loop(Socket, N - 1). + +multipart(Config) -> + Url = build_url("/multipart", Config), + Body = << + "This is a preamble." + "\r\n--OHai\r\nX-Name:answer\r\n\r\n42" + "\r\n--OHai\r\nServer:Cowboy\r\n\r\nIt rocks!\r\n" + "\r\n--OHai--" + "This is an epiloque." + >>, + Request = {Url, [], "multipart/x-makes-no-sense; boundary=OHai", Body}, + {ok, {{"HTTP/1.1", 200, "OK"}, _Headers, Response}} = + httpc:request(post, Request, [], [{body_format, binary}]), + Parts = binary_to_term(Response), + Parts = [ + {[{<<"X-Name">>, <<"answer">>}], <<"42">>}, + {[{'Server', <<"Cowboy">>}], <<"It rocks!\r\n">>} + ]. + +nc_rand(Config) -> + nc_reqs(Config, "/dev/urandom"). + +nc_zero(Config) -> + nc_reqs(Config, "/dev/zero"). + +nc_reqs(Config, Input) -> + Cat = os:find_executable("cat"), + Nc = os:find_executable("nc"), + case {Cat, Nc} of + {false, _} -> + {skip, {notfound, cat}}; + {_, false} -> + {skip, {notfound, nc}}; + _Good -> + %% Throw garbage at the server then check if it's still up. + {port, Port} = lists:keyfind(port, 1, Config), + [nc_run_req(Port, Input) || _N <- lists:seq(1, 100)], + Packet = "GET / HTTP/1.0\r\nHost: localhost\r\n\r\n", + {Packet, 200} = raw_req(Packet, Config) + end. + +nc_run_req(Port, Input) -> + os:cmd("cat " ++ Input ++ " | nc localhost " ++ integer_to_list(Port)). + +pipeline(Config) -> + {port, Port} = lists:keyfind(port, 1, Config), + {ok, Socket} = gen_tcp:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + ok = gen_tcp:send(Socket, + "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n" + "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n" + "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n" + "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n" + "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"), + Data = pipeline_recv(Socket, <<>>), + Reqs = binary:split(Data, << "\r\n\r\nhttp_handler" >>, [global, trim]), + 5 = length(Reqs), + pipeline_check(Reqs). + +pipeline_check([]) -> + ok; +pipeline_check([Req|Tail]) -> + << "HTTP/1.1 200", _Rest/bits >> = Req, + pipeline_check(Tail). + +pipeline_recv(Socket, SoFar) -> + case gen_tcp:recv(Socket, 0, 6000) of + {ok, Data} -> + pipeline_recv(Socket, << SoFar/binary, Data/binary >>); + {error, closed} -> + ok = gen_tcp:close(Socket), + SoFar + end. + +raw_req(Packet, Config) -> + {port, Port} = lists:keyfind(port, 1, Config), + {ok, Socket} = gen_tcp:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + ok = gen_tcp:send(Socket, Packet), + Res = case gen_tcp:recv(Socket, 0, 6000) of + {ok, << "HTTP/1.1 ", Str:24/bits, _Rest/bits >>} -> + list_to_integer(binary_to_list(Str)); + {error, Reason} -> + Reason + end, + gen_tcp:close(Socket), + {Packet, Res}. + +%% Send a raw request. Return the response code and the full response. +raw_resp(Request, Config) -> + {port, Port} = lists:keyfind(port, 1, Config), + Transport = case ?config(scheme, Config) of + "http" -> gen_tcp; + "https" -> ssl + end, + {ok, Socket} = Transport:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + ok = Transport:send(Socket, Request), + {StatusCode, Response} = case recv_loop(Transport, Socket, <<>>) of + {ok, << "HTTP/1.1 ", Str:24/bits, _Rest/bits >> = Bin} -> + {list_to_integer(binary_to_list(Str)), Bin}; + {ok, Bin} -> + {badresp, Bin}; + {error, Reason} -> + {Reason, <<>>} + end, + Transport:close(Socket), + {Response, StatusCode}. + +recv_loop(Transport, Socket, Acc) -> + case Transport:recv(Socket, 0, 6000) of + {ok, Data} -> + recv_loop(Transport, Socket, <>); + {error, closed} -> + ok = Transport:close(Socket), + {ok, Acc}; + {error, Reason} -> + {error, Reason} + end. + + + +raw(Config) -> + Huge = [$0 || _N <- lists:seq(1, 5000)], + Tests = [ + {"\r\n\r\n\r\n\r\n\r\nGET / HTTP/1.1\r\nHost: localhost\r\n\r\n", 200}, + {"\n", 400}, + {"Garbage\r\n\r\n", 400}, + {"\r\n\r\n\r\n\r\n\r\n\r\n", 400}, + {"GET / HTTP/1.1\r\nHost: dev-extend.eu\r\n\r\n", 400}, + {"", closed}, + {"\r\n", closed}, + {"\r\n\r\n", closed}, + {"GET / HTTP/1.1", closed}, + {"GET / HTTP/1.1\r\n", 408}, + {"GET / HTTP/1.1\r\nHost: localhost", 408}, + {"GET / HTTP/1.1\r\nHost: localhost\r\n", 408}, + {"GET / HTTP/1.1\r\nHost: localhost\r\n\r", 408}, + {"GET http://localhost/ HTTP/1.1\r\n\r\n", 501}, + {"GET / HTTP/1.2\r\nHost: localhost\r\n\r\n", 505}, + {"GET /init_shutdown HTTP/1.1\r\nHost: localhost\r\n\r\n", 666}, + {"GET /long_polling HTTP/1.1\r\nHost: localhost\r\n\r\n", 102}, + {Huge, 413}, + {"GET / HTTP/1.1\r\n" ++ Huge, 413} + ], + [{Packet, StatusCode} = raw_req(Packet, Config) + || {Packet, StatusCode} <- Tests]. + +set_resp_header(Config) -> + {port, Port} = lists:keyfind(port, 1, Config), + {ok, Socket} = gen_tcp:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + ok = gen_tcp:send(Socket, "GET /set_resp/header HTTP/1.1\r\n" + "Host: localhost\r\nConnection: close\r\n\r\n"), + {ok, Data} = gen_tcp:recv(Socket, 0, 6000), + {_, _} = binary:match(Data, <<"Vary: Accept">>), + {_, _} = binary:match(Data, <<"Set-Cookie: ">>). + +set_resp_overwrite(Config) -> + {port, Port} = lists:keyfind(port, 1, Config), + {ok, Socket} = gen_tcp:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + ok = gen_tcp:send(Socket, "GET /set_resp/overwrite HTTP/1.1\r\n" + "Host: localhost\r\nConnection: close\r\n\r\n"), + {ok, Data} = gen_tcp:recv(Socket, 0, 6000), + {_Start, _Length} = binary:match(Data, <<"Server: DesireDrive/1.0">>). + +set_resp_body(Config) -> + {port, Port} = lists:keyfind(port, 1, Config), + {ok, Socket} = gen_tcp:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + ok = gen_tcp:send(Socket, "GET /set_resp/body HTTP/1.1\r\n" + "Host: localhost\r\nConnection: close\r\n\r\n"), + {ok, Data} = gen_tcp:recv(Socket, 0, 6000), + {_Start, _Length} = binary:match(Data, <<"\r\n\r\n" + "A flameless dance does not equal a cycle">>). + +response_as_req(Config) -> + Packet = +"HTTP/1.0 302 Found +Location: http://www.google.co.il/ +Cache-Control: private +Content-Type: text/html; charset=UTF-8 +Set-Cookie: PREF=ID=568f67013d4a7afa:FF=0:TM=1323014101:LM=1323014101:S=XqctDWC65MzKT0zC; expires=Tue, 03-Dec-2013 15:55:01 GMT; path=/; domain=.google.com +Date: Sun, 04 Dec 2011 15:55:01 GMT +Server: gws +Content-Length: 221 +X-XSS-Protection: 1; mode=block +X-Frame-Options: SAMEORIGIN + + +302 Moved +

302 Moved

+The document has moved +here. +", + {Packet, 400} = raw_req(Packet, Config). + +stream_body_set_resp(Config) -> + {Packet, 200} = raw_resp( + "GET /stream_body/set_resp HTTP/1.1\r\n" + "Host: localhost\r\nConnection: close\r\n\r\n", Config), + {_Start, _Length} = binary:match(Packet, <<"stream_body_set_resp">>). + +static_mimetypes_function(Config) -> + TestURL = build_url("/static_mimetypes_function/test.html", Config), + {ok, {{"HTTP/1.1", 200, "OK"}, Headers1, "test.html\n"}} = + httpc:request(TestURL), + "text/html" = ?config("content-type", Headers1). + +handler_errors(Config) -> + Request = fun(Case) -> + raw_resp(["GET /handler_errors?case=", Case, " HTTP/1.1\r\n", + "Host: localhost\r\n\r\n"], Config) end, + + {_Packet1, 500} = Request("init_before_reply"), + + {Packet2, 200} = Request("init_after_reply"), + nomatch = binary:match(Packet2, <<"HTTP/1.1 500">>), + + {Packet3, 200} = Request("init_reply_handle_error"), + nomatch = binary:match(Packet3, <<"HTTP/1.1 500">>), + + {_Packet4, 500} = Request("handle_before_reply"), + + {Packet5, 200} = Request("handle_after_reply"), + nomatch = binary:match(Packet5, <<"HTTP/1.1 500">>), + + {Packet6, 200} = raw_resp([ + "GET / HTTP/1.1\r\n", + "Host: localhost\r\n", + "Connection: keep-alive\r\n\r\n", + "GET /handler_errors?case=handle_after_reply\r\n", + "Host: localhost\r\n\r\n"], Config), + nomatch = binary:match(Packet6, <<"HTTP/1.1 500">>), + + {Packet7, 200} = raw_resp([ + "GET / HTTP/1.1\r\n", + "Host: localhost\r\n", + "Connection: keep-alive\r\n\r\n", + "GET /handler_errors?case=handle_before_reply HTTP/1.1\r\n", + "Host: localhost\r\n\r\n"], Config), + {{_, _}, _} = {binary:match(Packet7, <<"HTTP/1.1 500">>), Packet7}, + + done. + +static_attribute_etag(Config) -> + TestURL = build_url("/static_attribute_etag/test.html", Config), + {ok, {{"HTTP/1.1", 200, "OK"}, Headers1, "test.html\n"}} = + httpc:request(TestURL), + false = ?config("etag", Headers1) =:= undefined, + {ok, {{"HTTP/1.1", 200, "OK"}, Headers2, "test.html\n"}} = + httpc:request(TestURL), + true = ?config("etag", Headers1) =:= ?config("etag", Headers2). + +static_function_etag(Config) -> + TestURL = build_url("/static_function_etag/test.html", Config), + {ok, {{"HTTP/1.1", 200, "OK"}, Headers1, "test.html\n"}} = + httpc:request(TestURL), + false = ?config("etag", Headers1) =:= undefined, + {ok, {{"HTTP/1.1", 200, "OK"}, Headers2, "test.html\n"}} = + httpc:request(TestURL), + true = ?config("etag", Headers1) =:= ?config("etag", Headers2). + +static_function_etag(Arguments, etag_data) -> + {_, Filepath} = lists:keyfind(filepath, 1, Arguments), + {_, _Filesize} = lists:keyfind(filesize, 1, Arguments), + {_, _INode} = lists:keyfind(inode, 1, Arguments), + {_, _Modified} = lists:keyfind(mtime, 1, Arguments), + ChecksumCommand = lists:flatten(io_lib:format("sha1sum ~s", [Filepath])), + [Checksum|_] = string:tokens(os:cmd(ChecksumCommand), " "), + iolist_to_binary(Checksum). + +%% http and https. + +build_url(Path, Config) -> + {scheme, Scheme} = lists:keyfind(scheme, 1, Config), + {port, Port} = lists:keyfind(port, 1, Config), + Scheme ++ "://localhost:" ++ integer_to_list(Port) ++ Path. + +http_200(Config) -> + {ok, {{"HTTP/1.1", 200, "OK"}, _Headers, "http_handler"}} = + httpc:request(build_url("/", Config)). + +http_404(Config) -> + {ok, {{"HTTP/1.1", 404, "Not Found"}, _Headers, _Body}} = + httpc:request(build_url("/not/found", Config)). + +file_200(Config) -> + {ok, {{"HTTP/1.1", 200, "OK"}, Headers, "test_file\n"}} = + httpc:request(build_url("/static/test_file", Config)), + "application/octet-stream" = ?config("content-type", Headers), + + {ok, {{"HTTP/1.1", 200, "OK"}, Headers1, "test_file.css\n"}} = + httpc:request(build_url("/static/test_file.css", Config)), + "text/css" = ?config("content-type", Headers1). + +file_403(Config) -> + {ok, {{"HTTP/1.1", 403, "Forbidden"}, _Headers, _Body}} = + httpc:request(build_url("/static/test_noread", Config)). + +dir_403(Config) -> + {ok, {{"HTTP/1.1", 403, "Forbidden"}, _Headers, _Body}} = + httpc:request(build_url("/static/test_dir", Config)), + {ok, {{"HTTP/1.1", 403, "Forbidden"}, _Headers, _Body}} = + httpc:request(build_url("/static/test_dir/", Config)). + +file_404(Config) -> + {ok, {{"HTTP/1.1", 404, "Not Found"}, _Headers, _Body}} = + httpc:request(build_url("/static/not_found", Config)). + +file_400(Config) -> + {ok, {{"HTTP/1.1", 400, "Bad Request"}, _Headers, _Body}} = + httpc:request(build_url("/static/%2f", Config)), + {ok, {{"HTTP/1.1", 400, "Bad Request"}, _Headers1, _Body1}} = + httpc:request(build_url("/static/%2e", Config)), + {ok, {{"HTTP/1.1", 400, "Bad Request"}, _Headers2, _Body2}} = + httpc:request(build_url("/static/%2e%2e", Config)). +%% misc. + +http_10_hostless(Config) -> + Packet = "GET / HTTP/1.0\r\n\r\n", + {Packet, 200} = raw_req(Packet, Config). + +%% rest. + +rest_simple(Config) -> + Packet = "GET /simple HTTP/1.1\r\nHost: localhost\r\n\r\n", + {Packet, 200} = raw_req(Packet, Config). + +rest_keepalive(Config) -> + {port, Port} = lists:keyfind(port, 1, Config), + {ok, Socket} = gen_tcp:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + ok = rest_keepalive_loop(Socket, 100), + ok = gen_tcp:close(Socket). + +rest_keepalive_loop(_Socket, 0) -> + ok; +rest_keepalive_loop(Socket, N) -> + ok = gen_tcp:send(Socket, "GET /simple HTTP/1.1\r\n" + "Host: localhost\r\nConnection: keep-alive\r\n\r\n"), + {ok, Data} = gen_tcp:recv(Socket, 0, 6000), + {0, 12} = binary:match(Data, <<"HTTP/1.1 200">>), + nomatch = binary:match(Data, <<"Connection: close">>), + rest_keepalive_loop(Socket, N - 1). + +rest_keepalive_post(Config) -> + {port, Port} = lists:keyfind(port, 1, Config), + {ok, Socket} = gen_tcp:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + ok = rest_keepalive_post_loop(Socket, 10, forbidden_post), + ok = gen_tcp:close(Socket). + +rest_keepalive_post_loop(_Socket, 0, _) -> + ok; +rest_keepalive_post_loop(Socket, N, simple_post) -> + ok = gen_tcp:send(Socket, "POST /simple_post HTTP/1.1\r\n" + "Host: localhost\r\nConnection: keep-alive\r\n" + "Content-Length: 5\r\nContent-Type: text/plain\r\n\r\n12345"), + {ok, Data} = gen_tcp:recv(Socket, 0, 6000), + {0, 12} = binary:match(Data, <<"HTTP/1.1 303">>), + nomatch = binary:match(Data, <<"Connection: close">>), + rest_keepalive_post_loop(Socket, N - 1, forbidden_post); +rest_keepalive_post_loop(Socket, N, forbidden_post) -> + ok = gen_tcp:send(Socket, "POST /forbidden_post HTTP/1.1\r\n" + "Host: localhost\r\nConnection: keep-alive\r\n" + "Content-Length: 5\r\nContent-Type: text/plain\r\n\r\n12345"), + {ok, Data} = gen_tcp:recv(Socket, 0, 6000), + {0, 12} = binary:match(Data, <<"HTTP/1.1 403">>), + nomatch = binary:match(Data, <<"Connection: close">>), + rest_keepalive_post_loop(Socket, N - 1, simple_post). diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_SUITE_data/cert.pem b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_SUITE_data/cert.pem new file mode 100644 index 0000000..a772007 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_SUITE_data/cert.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICKTCCAZICCQCl9gdHk5NqUjANBgkqhkiG9w0BAQUFADBZMQswCQYDVQQGEwJB +VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTEwNDA4MTMxNTE3WhcN +MTEwNTA4MTMxNTE3WjBZMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0 +ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDDAls +b2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOjgFPS0dP4d8F1e +bNJPB+kAjM2FyTZGmkFCLUYONTPrdGOUIHL/UOGtU22BQzlskE+a6/j2Kg72tm8x +4X7yf+6s7CdRe086idNx9+GymZ64ZTnly33rD3AJffbBeWHwT2e9fuBeFk9WGC8v +kqECFZyqf7+znS0o48oBNcx3ePB5AgMBAAEwDQYJKoZIhvcNAQEFBQADgYEASTkv +oHuZyO8DgT8bIE6W3yM2fvlNshkhh7Thgpf32qQoVOxRU9EF0KpuJCCAHQHQNQlI +nf9Zc4UzOrLhxZBGocNhkkn4WLw2ysto/7+/+9xHah0M0l4auHLQagVLCoOsHUn2 +JX+A2NrbvuX5wnUrZGOdgY70tvMBeU/xLtp3af8= +-----END CERTIFICATE----- diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_SUITE_data/key.pem b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_SUITE_data/key.pem new file mode 100644 index 0000000..0b699cc --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_SUITE_data/key.pem @@ -0,0 +1,18 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,F11262DB77BB804C + +jOJ+ft/dihIxz7CTuuK47fCTGdX7xMLANmA7mRg8y9OYhNZQiCz5GjcWLqe0NNl5 +qXPW0uvT/9B5O9o21Y2i/CKU1BqRLuXHXDsjHg7RGaSH6wIavWt+lR+I1sjieFbX +VByK1KHXjEU704DEILKJIA9gVzoYAgMzo+FTw2e/2jusXntxk8HXyF5zKTzjHBtI +NQGweJqTmfZjX3SgPP4Co/ShrA6fUG0uTp1HwbByJnwtAeT3xWJrAD4QSn7+qrlv +3qmEIqVXsvLrfZRY1WZ4uIsbLK8wkvxboSIoIK55VV9R2zRbwQULon6QJwKYujAr +J2WUYkHHQOMpaAzUmalaT+8GUt8/A1oSK4BdiSZywsMMm46/hDadXBzFg+dPL5g2 +Td+7/L0S6tUVWq4+YBp5EalZH6VQ4cqPYDJZUZ9xt6+yY7V5748lSdA7cHCROnbG +bKbSW9WbF7MPDHCjvCAfq+s1dafHJgyIOlMg2bm7V8eHWAA0xKQ/o7i5EyEyaKYR +UXGeAf+KfXcclEZ77v2RCXZvd6ceWkifm59qWv/3TCYaHiS2Aa3lVToMKTwYzzXQ +p5X5os6wv3IAi2nGyAIOoSDisdHmFteZNXNQsw0n3XCAYfsNMk+r5/r5YqDffURH +c8SMOCP4BIPoZ/abi/gnEntGqsx1YALg0aosHwHGDJ/l+QJC6u6PZk310YzRw4GL +K9+wscFgEub2OO+R83Vkfesj4tYzgOjab7+92a/soHdW0zhGejlvehODOgNZ6NUG +MPQlT+qpF9Jh5IThYXupXXFzJzQe3O/qVXy89m69JGa+AWRvbu+M/A== +-----END RSA PRIVATE KEY----- diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler.erl new file mode 100644 index 0000000..76a85d4 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler.erl @@ -0,0 +1,19 @@ +%% Feel free to use, reuse and abuse the code in this file. + +-module(http_handler). +-behaviour(cowboy_http_handler). +-export([init/3, handle/2, terminate/2]). + +-record(state, {headers, body}). + +init({_Transport, http}, Req, Opts) -> + Headers = proplists:get_value(headers, Opts, []), + Body = proplists:get_value(body, Opts, "http_handler"), + {ok, Req, #state{headers=Headers, body=Body}}. + +handle(Req, State=#state{headers=Headers, body=Body}) -> + {ok, Req2} = cowboy_http_req:reply(200, Headers, Body, Req), + {ok, Req2, State}. + +terminate(_Req, _State) -> + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_errors.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_errors.erl new file mode 100644 index 0000000..1c23207 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_errors.erl @@ -0,0 +1,40 @@ +%% Feel free to use, reuse and abuse the code in this file. + +-module(http_handler_errors). +-behaviour(cowboy_http_handler). +-export([init/3, handle/2, terminate/2]). + +init({_Transport, http}, Req, _Opts) -> + {Case, Req1} = cowboy_http_req:qs_val(<<"case">>, Req), + case_init(Case, Req1). + +case_init(<<"init_before_reply">> = Case, _Req) -> + erlang:error(Case); + +case_init(<<"init_after_reply">> = Case, Req) -> + {ok, _Req1} = cowboy_http_req:reply(200, [], "http_handler_crashes", Req), + erlang:error(Case); + +case_init(<<"init_reply_handle_error">> = Case, Req) -> + {ok, Req1} = cowboy_http_req:reply(200, [], "http_handler_crashes", Req), + {ok, Req1, Case}; + +case_init(<<"handle_before_reply">> = Case, Req) -> + {ok, Req, Case}; + +case_init(<<"handle_after_reply">> = Case, Req) -> + {ok, Req, Case}. + + +handle(_Req, <<"init_reply_handle_error">> = Case) -> + erlang:error(Case); + +handle(_Req, <<"handle_before_reply">> = Case) -> + erlang:error(Case); + +handle(Req, <<"handle_after_reply">> = Case) -> + {ok, _Req1} = cowboy_http_req:reply(200, [], "http_handler_crashes", Req), + erlang:error(Case). + +terminate(_Req, _State) -> + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_init_shutdown.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_init_shutdown.erl new file mode 100644 index 0000000..ac63b44 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_init_shutdown.erl @@ -0,0 +1,17 @@ +%% Feel free to use, reuse and abuse the code in this file. + +-module(http_handler_init_shutdown). +-behaviour(cowboy_http_handler). +-export([init/3, handle/2, terminate/2]). + +init({_Transport, http}, Req, _Opts) -> + {ok, Req2} = cowboy_http_req:reply(<<"666 Init Shutdown Testing">>, + [{'Connection', <<"close">>}], Req), + {shutdown, Req2, undefined}. + +handle(Req, State) -> + {ok, Req2} = cowboy_http_req:reply(200, [], "Hello world!", Req), + {ok, Req2, State}. + +terminate(_Req, _State) -> + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_long_polling.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_long_polling.erl new file mode 100644 index 0000000..e838619 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_long_polling.erl @@ -0,0 +1,22 @@ +%% Feel free to use, reuse and abuse the code in this file. + +-module(http_handler_long_polling). +-behaviour(cowboy_http_handler). +-export([init/3, handle/2, info/3, terminate/2]). + +init({_Transport, http}, Req, _Opts) -> + erlang:send_after(500, self(), timeout), + {loop, Req, 9, 5000, hibernate}. + +handle(_Req, _State) -> + exit(badarg). + +info(timeout, Req, 0) -> + {ok, Req2} = cowboy_http_req:reply(102, Req), + {ok, Req2, 0}; +info(timeout, Req, State) -> + erlang:send_after(500, self(), timeout), + {loop, Req, State - 1, hibernate}. + +terminate(_Req, _State) -> + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_multipart.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_multipart.erl new file mode 100644 index 0000000..f5f7919 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_multipart.erl @@ -0,0 +1,29 @@ +%% Feel free to use, reuse and abuse the code in this file. + +-module(http_handler_multipart). +-behaviour(cowboy_http_handler). +-export([init/3, handle/2, terminate/2]). + +init({_Transport, http}, Req, []) -> + {ok, Req, {}}. + +handle(Req, State) -> + {Result, Req2} = acc_multipart(Req, []), + {ok, Req3} = cowboy_http_req:reply(200, [], term_to_binary(Result), Req2), + {ok, Req3, State}. + +terminate(_Req, _State) -> + ok. + +acc_multipart(Req, Acc) -> + {Result, Req2} = cowboy_http_req:multipart_data(Req), + acc_multipart(Req2, Acc, Result). + +acc_multipart(Req, Acc, {headers, Headers}) -> + acc_multipart(Req, [{Headers, []}|Acc]); +acc_multipart(Req, [{Headers, BodyAcc}|Acc], {body, Data}) -> + acc_multipart(Req, [{Headers, [Data|BodyAcc]}|Acc]); +acc_multipart(Req, [{Headers, BodyAcc}|Acc], end_of_part) -> + acc_multipart(Req, [{Headers, list_to_binary(lists:reverse(BodyAcc))}|Acc]); +acc_multipart(Req, Acc, eof) -> + {lists:reverse(Acc), Req}. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_set_resp.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_set_resp.erl new file mode 100644 index 0000000..83d48c0 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_set_resp.erl @@ -0,0 +1,33 @@ +%% Feel free to use, reuse and abuse the code in this file. + +-module(http_handler_set_resp). +-behaviour(cowboy_http_handler). +-export([init/3, handle/2, terminate/2]). + +init({_Transport, http}, Req, Opts) -> + Headers = proplists:get_value(headers, Opts, []), + Body = proplists:get_value(body, Opts, <<"http_handler_set_resp">>), + {ok, Req2} = lists:foldl(fun({Name, Value}, {ok, R}) -> + cowboy_http_req:set_resp_header(Name, Value, R) + end, {ok, Req}, Headers), + {ok, Req3} = cowboy_http_req:set_resp_body(Body, Req2), + {ok, Req4} = cowboy_http_req:set_resp_header( + <<"X-Cowboy-Test">>, <<"ok">>, Req3), + {ok, Req5} = cowboy_http_req:set_resp_cookie( + <<"cake">>, <<"lie">>, [], Req4), + {ok, Req5, undefined}. + +handle(Req, State) -> + case cowboy_http_req:has_resp_header(<<"X-Cowboy-Test">>, Req) of + false -> {ok, Req, State}; + true -> + case cowboy_http_req:has_resp_body(Req) of + false -> {ok, Req, State}; + true -> + {ok, Req2} = cowboy_http_req:reply(200, Req), + {ok, Req2, State} + end + end. + +terminate(_Req, _State) -> + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_stream_body.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_stream_body.erl new file mode 100644 index 0000000..c90f746 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/http_handler_stream_body.erl @@ -0,0 +1,24 @@ +%% Feel free to use, reuse and abuse the code in this file. + +-module(http_handler_stream_body). +-behaviour(cowboy_http_handler). +-export([init/3, handle/2, terminate/2]). + +-record(state, {headers, body, reply}). + +init({_Transport, http}, Req, Opts) -> + Headers = proplists:get_value(headers, Opts, []), + Body = proplists:get_value(body, Opts, "http_handler_stream_body"), + Reply = proplists:get_value(reply, Opts), + {ok, Req, #state{headers=Headers, body=Body, reply=Reply}}. + +handle(Req, State=#state{headers=_Headers, body=Body, reply=set_resp}) -> + {ok, Transport, Socket} = cowboy_http_req:transport(Req), + SFun = fun() -> Transport:send(Socket, Body), sent end, + SLen = iolist_size(Body), + {ok, Req2} = cowboy_http_req:set_resp_body_fun(SLen, SFun, Req), + {ok, Req3} = cowboy_http_req:reply(200, Req2), + {ok, Req3, State}. + +terminate(_Req, _State) -> + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/proper_SUITE.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/proper_SUITE.erl new file mode 100644 index 0000000..440aa5f --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/proper_SUITE.erl @@ -0,0 +1,37 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% +%% 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. + +-module(proper_SUITE). + +-include_lib("common_test/include/ct.hrl"). + +-export([all/0, groups/0]). %% ct. +-export([dispatcher_split_host/1]). %% cowboy_dispatcher. + +%% ct. + +all() -> + [{group, dispatcher}]. + +groups() -> + [{dispatcher, [], [dispatcher_split_host]}]. + +%% cowboy_dispatcher. + +dispatcher_split_host(_Config) -> + true = proper:quickcheck(dispatcher_prop:prop_split_host_symmetric(), + [{on_output, fun(Format, Data) -> + io:format(user, Format, Data), %% Console. + io:format(Format, Data) %% Logs. + end}]). diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/rest_forbidden_resource.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/rest_forbidden_resource.erl new file mode 100644 index 0000000..90dee84 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/rest_forbidden_resource.erl @@ -0,0 +1,40 @@ +-module(rest_forbidden_resource). +-export([init/3, rest_init/2, allowed_methods/2, forbidden/2, + content_types_provided/2, content_types_accepted/2, + post_is_create/2, create_path/2, to_text/2, from_text/2]). + +init(_Transport, _Req, _Opts) -> + {upgrade, protocol, cowboy_http_rest}. + +rest_init(Req, [Forbidden]) -> + {ok, Req, Forbidden}. + +allowed_methods(Req, State) -> + {['GET', 'HEAD', 'POST'], Req, State}. + +forbidden(Req, State=true) -> + {true, Req, State}; +forbidden(Req, State=false) -> + {false, Req, State}. + +content_types_provided(Req, State) -> + {[{{<<"text">>, <<"plain">>, []}, to_text}], Req, State}. + +content_types_accepted(Req, State) -> + {[{{<<"text">>, <<"plain">>, []}, from_text}], Req, State}. + +post_is_create(Req, State) -> + {true, Req, State}. + +create_path(Req, State) -> + {Path, Req2} = cowboy_http_req:raw_path(Req), + {Path, Req2, State}. + +to_text(Req, State) -> + {<<"This is REST!">>, Req, State}. + +from_text(Req, State) -> + {true, Req, State}. + + + diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/rest_simple_resource.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/rest_simple_resource.erl new file mode 100644 index 0000000..e2c573c --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/rest_simple_resource.erl @@ -0,0 +1,12 @@ +-module(rest_simple_resource). +-export([init/3, content_types_provided/2, get_text_plain/2]). + +init(_Transport, _Req, _Opts) -> + {upgrade, protocol, cowboy_http_rest}. + +content_types_provided(Req, State) -> + {[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}. + +get_text_plain(Req, State) -> + {<<"This is REST!">>, Req, State}. + diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/websocket_handler.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/websocket_handler.erl new file mode 100644 index 0000000..abb4967 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/websocket_handler.erl @@ -0,0 +1,38 @@ +%% Feel free to use, reuse and abuse the code in this file. + +-module(websocket_handler). +-behaviour(cowboy_http_handler). +-behaviour(cowboy_http_websocket_handler). +-export([init/3, handle/2, terminate/2]). +-export([websocket_init/3, websocket_handle/3, + websocket_info/3, websocket_terminate/3]). + +init(_Any, _Req, _Opts) -> + {upgrade, protocol, cowboy_http_websocket}. + +handle(_Req, _State) -> + exit(badarg). + +terminate(_Req, _State) -> + exit(badarg). + +websocket_init(_TransportName, Req, _Opts) -> + erlang:start_timer(1000, self(), <<"websocket_init">>), + Req2 = cowboy_http_req:compact(Req), + {ok, Req2, undefined}. + +websocket_handle({text, Data}, Req, State) -> + {reply, {text, Data}, Req, State}; +websocket_handle({binary, Data}, Req, State) -> + {reply, {binary, Data}, Req, State}; +websocket_handle(_Frame, Req, State) -> + {ok, Req, State}. + +websocket_info({timeout, _Ref, Msg}, Req, State) -> + erlang:start_timer(1000, self(), <<"websocket_handle">>), + {reply, {text, Msg}, Req, State}; +websocket_info(_Info, Req, State) -> + {ok, Req, State}. + +websocket_terminate(_Reason, _Req, _State) -> + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/websocket_handler_init_shutdown.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/websocket_handler_init_shutdown.erl new file mode 100644 index 0000000..aa9e056 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/websocket_handler_init_shutdown.erl @@ -0,0 +1,30 @@ +%% Feel free to use, reuse and abuse the code in this file. + +-module(websocket_handler_init_shutdown). +-behaviour(cowboy_http_handler). +-behaviour(cowboy_http_websocket_handler). +-export([init/3, handle/2, terminate/2]). +-export([websocket_init/3, websocket_handle/3, + websocket_info/3, websocket_terminate/3]). + +init(_Any, _Req, _Opts) -> + {upgrade, protocol, cowboy_http_websocket}. + +handle(_Req, _State) -> + exit(badarg). + +terminate(_Req, _State) -> + exit(badarg). + +websocket_init(_TransportName, Req, _Opts) -> + {ok, Req2} = cowboy_http_req:reply(403, Req), + {shutdown, Req2}. + +websocket_handle(_Frame, _Req, _State) -> + exit(badarg). + +websocket_info(_Info, _Req, _State) -> + exit(badarg). + +websocket_terminate(_Reason, _Req, _State) -> + exit(badarg). diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/ws_SUITE.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/ws_SUITE.erl new file mode 100644 index 0000000..136833f --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/ws_SUITE.erl @@ -0,0 +1,318 @@ +%% Copyright (c) 2011, Loïc Hoguin +%% +%% 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. + +-module(ws_SUITE). + +-include_lib("common_test/include/ct.hrl"). + +-export([all/0, groups/0, init_per_suite/1, end_per_suite/1, + init_per_group/2, end_per_group/2]). %% ct. +-export([ws0/1, ws8/1, ws8_single_bytes/1, ws8_init_shutdown/1, + ws13/1, ws_timeout_hibernate/1]). %% ws. + +%% ct. + +all() -> + [{group, ws}]. + +groups() -> + BaseTests = [ws0, ws8, ws8_single_bytes, ws8_init_shutdown, ws13, + ws_timeout_hibernate], + [{ws, [], BaseTests}]. + +init_per_suite(Config) -> + application:start(inets), + application:start(cowboy), + Config. + +end_per_suite(_Config) -> + application:stop(cowboy), + application:stop(inets), + ok. + +init_per_group(ws, Config) -> + Port = 33080, + cowboy:start_listener(ws, 100, + cowboy_tcp_transport, [{port, Port}], + cowboy_http_protocol, [{dispatch, init_dispatch()}] + ), + [{port, Port}|Config]. + +end_per_group(Listener, _Config) -> + cowboy:stop_listener(Listener), + ok. + +%% Dispatch configuration. + +init_dispatch() -> + [ + {[<<"localhost">>], [ + {[<<"websocket">>], websocket_handler, []}, + {[<<"ws_timeout_hibernate">>], ws_timeout_hibernate_handler, []}, + {[<<"ws_init_shutdown">>], websocket_handler_init_shutdown, []} + ]} + ]. + +%% ws and wss. + +%% This test makes sure the code works even if we wait for a reply +%% before sending the third challenge key in the GET body. +%% +%% This ensures that Cowboy will work fine with proxies on hixie. +ws0(Config) -> + {port, Port} = lists:keyfind(port, 1, Config), + {ok, Socket} = gen_tcp:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + ok = gen_tcp:send(Socket, + "GET /websocket HTTP/1.1\r\n" + "Host: localhost\r\n" + "Connection: Upgrade\r\n" + "Upgrade: WebSocket\r\n" + "Origin: http://localhost\r\n" + "Sec-Websocket-Key1: Y\" 4 1Lj!957b8@0H756!i\r\n" + "Sec-Websocket-Key2: 1711 M;4\\74 80<6\r\n" + "\r\n"), + {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), + {ok, {http_response, {1, 1}, 101, "WebSocket Protocol Handshake"}, Rest} + = erlang:decode_packet(http, Handshake, []), + [Headers, <<>>] = websocket_headers( + erlang:decode_packet(httph, Rest, []), []), + {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), + {'Upgrade', "WebSocket"} = lists:keyfind('Upgrade', 1, Headers), + {"sec-websocket-location", "ws://localhost/websocket"} + = lists:keyfind("sec-websocket-location", 1, Headers), + {"sec-websocket-origin", "http://localhost"} + = lists:keyfind("sec-websocket-origin", 1, Headers), + ok = gen_tcp:send(Socket, <<15,245,8,18,2,204,133,33>>), + {ok, Body} = gen_tcp:recv(Socket, 0, 6000), + <<169,244,191,103,146,33,149,59,74,104,67,5,99,118,171,236>> = Body, + ok = gen_tcp:send(Socket, << 0, "client_msg", 255 >>), + {ok, << 0, "client_msg", 255 >>} = gen_tcp:recv(Socket, 0, 6000), + {ok, << 0, "websocket_init", 255 >>} = gen_tcp:recv(Socket, 0, 6000), + {ok, << 0, "websocket_handle", 255 >>} = gen_tcp:recv(Socket, 0, 6000), + {ok, << 0, "websocket_handle", 255 >>} = gen_tcp:recv(Socket, 0, 6000), + {ok, << 0, "websocket_handle", 255 >>} = gen_tcp:recv(Socket, 0, 6000), + %% We try to send another HTTP request to make sure + %% the server closed the request. + ok = gen_tcp:send(Socket, [ + << 255, 0 >>, %% Close websocket command. + "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n" %% Server should ignore it. + ]), + {ok, << 255, 0 >>} = gen_tcp:recv(Socket, 0, 6000), + {error, closed} = gen_tcp:recv(Socket, 0, 6000), + ok. + +ws8(Config) -> + {port, Port} = lists:keyfind(port, 1, Config), + {ok, Socket} = gen_tcp:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + ok = gen_tcp:send(Socket, [ + "GET /websocket HTTP/1.1\r\n" + "Host: localhost\r\n" + "Connection: Upgrade\r\n" + "Upgrade: websocket\r\n" + "Sec-WebSocket-Origin: http://localhost\r\n" + "Sec-WebSocket-Version: 8\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "\r\n"]), + {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), + {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} + = erlang:decode_packet(http, Handshake, []), + [Headers, <<>>] = websocket_headers( + erlang:decode_packet(httph, Rest, []), []), + {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), + {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), + {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} + = lists:keyfind("sec-websocket-accept", 1, Headers), + ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d, + 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>), + {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>} + = gen_tcp:recv(Socket, 0, 6000), + {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>} + = gen_tcp:recv(Socket, 0, 6000), + {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} + = gen_tcp:recv(Socket, 0, 6000), + {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} + = gen_tcp:recv(Socket, 0, 6000), + {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} + = gen_tcp:recv(Socket, 0, 6000), + ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 0:8 >>), %% ping + {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong + ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close + {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), + {error, closed} = gen_tcp:recv(Socket, 0, 6000), + ok. + +ws8_single_bytes(Config) -> + {port, Port} = lists:keyfind(port, 1, Config), + {ok, Socket} = gen_tcp:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + ok = gen_tcp:send(Socket, [ + "GET /websocket HTTP/1.1\r\n" + "Host: localhost\r\n" + "Connection: Upgrade\r\n" + "Upgrade: websocket\r\n" + "Sec-WebSocket-Origin: http://localhost\r\n" + "Sec-WebSocket-Version: 8\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "\r\n"]), + {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), + {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} + = erlang:decode_packet(http, Handshake, []), + [Headers, <<>>] = websocket_headers( + erlang:decode_packet(httph, Rest, []), []), + {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), + {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), + {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} + = lists:keyfind("sec-websocket-accept", 1, Headers), + ok = gen_tcp:send(Socket, << 16#81 >>), %% send one byte + ok = timer:sleep(100), %% sleep for a period + ok = gen_tcp:send(Socket, << 16#85 >>), %% send another and so on + ok = timer:sleep(100), + ok = gen_tcp:send(Socket, << 16#37 >>), + ok = timer:sleep(100), + ok = gen_tcp:send(Socket, << 16#fa >>), + ok = timer:sleep(100), + ok = gen_tcp:send(Socket, << 16#21 >>), + ok = timer:sleep(100), + ok = gen_tcp:send(Socket, << 16#3d >>), + ok = timer:sleep(100), + ok = gen_tcp:send(Socket, << 16#7f >>), + ok = timer:sleep(100), + ok = gen_tcp:send(Socket, << 16#9f >>), + ok = timer:sleep(100), + ok = gen_tcp:send(Socket, << 16#4d >>), + ok = timer:sleep(100), + ok = gen_tcp:send(Socket, << 16#51 >>), + ok = timer:sleep(100), + ok = gen_tcp:send(Socket, << 16#58 >>), + {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>} + = gen_tcp:recv(Socket, 0, 6000), + {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>} + = gen_tcp:recv(Socket, 0, 6000), + {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} + = gen_tcp:recv(Socket, 0, 6000), + {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} + = gen_tcp:recv(Socket, 0, 6000), + {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} + = gen_tcp:recv(Socket, 0, 6000), + ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 0:8 >>), %% ping + {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong + ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close + {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), + {error, closed} = gen_tcp:recv(Socket, 0, 6000), + ok. + +ws_timeout_hibernate(Config) -> + {port, Port} = lists:keyfind(port, 1, Config), + {ok, Socket} = gen_tcp:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + ok = gen_tcp:send(Socket, [ + "GET /ws_timeout_hibernate HTTP/1.1\r\n" + "Host: localhost\r\n" + "Connection: Upgrade\r\n" + "Upgrade: websocket\r\n" + "Sec-WebSocket-Origin: http://localhost\r\n" + "Sec-WebSocket-Version: 8\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "\r\n"]), + {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), + {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} + = erlang:decode_packet(http, Handshake, []), + [Headers, <<>>] = websocket_headers( + erlang:decode_packet(httph, Rest, []), []), + {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), + {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), + {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} + = lists:keyfind("sec-websocket-accept", 1, Headers), + {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), + {error, closed} = gen_tcp:recv(Socket, 0, 6000), + ok. + +ws8_init_shutdown(Config) -> + {port, Port} = lists:keyfind(port, 1, Config), + {ok, Socket} = gen_tcp:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + ok = gen_tcp:send(Socket, [ + "GET /ws_init_shutdown HTTP/1.1\r\n" + "Host: localhost\r\n" + "Connection: Upgrade\r\n" + "Upgrade: websocket\r\n" + "Sec-WebSocket-Origin: http://localhost\r\n" + "Sec-WebSocket-Version: 8\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "\r\n"]), + {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), + {ok, {http_response, {1, 1}, 403, "Forbidden"}, _Rest} + = erlang:decode_packet(http, Handshake, []), + {error, closed} = gen_tcp:recv(Socket, 0, 6000), + ok. + +ws13(Config) -> + {port, Port} = lists:keyfind(port, 1, Config), + {ok, Socket} = gen_tcp:connect("localhost", Port, + [binary, {active, false}, {packet, raw}]), + ok = gen_tcp:send(Socket, [ + "GET /websocket HTTP/1.1\r\n" + "Host: localhost\r\n" + "Connection: Upgrade\r\n" + "Origin: http://localhost\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Upgrade: websocket\r\n" + "\r\n"]), + {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), + {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} + = erlang:decode_packet(http, Handshake, []), + [Headers, <<>>] = websocket_headers( + erlang:decode_packet(httph, Rest, []), []), + {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), + {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), + {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} + = lists:keyfind("sec-websocket-accept", 1, Headers), + %% text + ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d, + 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>), + {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>} + = gen_tcp:recv(Socket, 0, 6000), + %% binary (empty) + ok = gen_tcp:send(Socket, << 1:1, 0:3, 2:4, 0:8 >>), + {ok, << 1:1, 0:3, 2:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), + %% binary + ok = gen_tcp:send(Socket, << 16#82, 16#85, 16#37, 16#fa, 16#21, 16#3d, + 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>), + {ok, << 1:1, 0:3, 2:4, 0:1, 5:7, "Hello" >>} + = gen_tcp:recv(Socket, 0, 6000), + %% Receives. + {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>} + = gen_tcp:recv(Socket, 0, 6000), + {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} + = gen_tcp:recv(Socket, 0, 6000), + {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} + = gen_tcp:recv(Socket, 0, 6000), + {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} + = gen_tcp:recv(Socket, 0, 6000), + ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 0:8 >>), %% ping + {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong + ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close + {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), + {error, closed} = gen_tcp:recv(Socket, 0, 6000), + ok. + +websocket_headers({ok, http_eoh, Rest}, Acc) -> + [Acc, Rest]; +websocket_headers({ok, {http_header, _I, Key, _R, Value}, Rest}, Acc) -> + F = fun(S) when is_atom(S) -> S; (S) -> string:to_lower(S) end, + websocket_headers(erlang:decode_packet(httph, Rest, []), + [{F(Key), Value}|Acc]). diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/ws_timeout_hibernate_handler.erl b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/ws_timeout_hibernate_handler.erl new file mode 100644 index 0000000..777948a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/cowboy-git/test/ws_timeout_hibernate_handler.erl @@ -0,0 +1,29 @@ +%% Feel free to use, reuse and abuse the code in this file. + +-module(ws_timeout_hibernate_handler). +-behaviour(cowboy_http_handler). +-behaviour(cowboy_http_websocket_handler). +-export([init/3, handle/2, terminate/2]). +-export([websocket_init/3, websocket_handle/3, + websocket_info/3, websocket_terminate/3]). + +init(_Any, _Req, _Opts) -> + {upgrade, protocol, cowboy_http_websocket}. + +handle(_Req, _State) -> + exit(badarg). + +terminate(_Req, _State) -> + exit(badarg). + +websocket_init(_TransportName, Req, _Opts) -> + {ok, Req, undefined, 1000, hibernate}. + +websocket_handle(_Frame, Req, State) -> + {ok, Req, State, hibernate}. + +websocket_info(_Info, Req, State) -> + {ok, Req, State, hibernate}. + +websocket_terminate(_Reason, _Req, _State) -> + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/hash.mk b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/hash.mk new file mode 100644 index 0000000..5071907 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/hash.mk @@ -0,0 +1 @@ +UPSTREAM_SHORT_HASH:=4b93c2d diff --git a/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/package.mk b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/package.mk new file mode 100644 index 0000000..fd29da9 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/cowboy-wrapper/package.mk @@ -0,0 +1,24 @@ +APP_NAME:=cowboy + +UPSTREAM_GIT:=https://github.com/rabbitmq/cowboy.git +UPSTREAM_REVISION:=4b93c2d19a10e5d9cee +RETAIN_ORIGINAL_VERSION:=true +WRAPPER_PATCHES:=\ + 0001-R12-fake-iodata-type.patch \ + 0002-R12-drop-all-references-to-boolean-type.patch \ + 0003-R12-drop-all-references-to-reference-type.patch \ + 0004-R12-drop-references-to-iodata-type.patch \ + 0005-R12-drop-references-to-Default-any-type.patch \ + 0006-Use-erlang-integer_to_list-and-lists-max-instead-of-.patch \ + 0007-R12-type-definitions-must-be-ordered.patch \ + 0008-sec-websocket-protocol.patch + +# Path include/http.hrl is needed during compilation +INCLUDE_DIRS+=$(CLONE_DIR) + +ORIGINAL_APP_FILE:=$(CLONE_DIR)/src/$(APP_NAME).app.src +DO_NOT_GENERATE_APP_FILE=true + +define construct_app_commands + cp $(CLONE_DIR)/LICENSE $(APP_DIR)/LICENSE-ISC-Cowboy +endef diff --git a/rabbitmq-server-3.3.5/plugins-src/do-package.mk b/rabbitmq-server-3.3.5/plugins-src/do-package.mk new file mode 100644 index 0000000..a26d446 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/do-package.mk @@ -0,0 +1,574 @@ +# This file produces the makefile fragment associated with a package. +# It includes the package's package.mk, interprets all of the +# variables that package.mk might have set, and then visits any +# dependencies of the package that have not already been visited. +# +# PACKAGE_DIR should be set to the canonical path of the package. + +# Mark that this package has been visited, so we can avoid doing it again +DONE_$(PACKAGE_DIR):=true + +# Declare the standard per-package targets +.PHONY: $(PACKAGE_DIR)+dist $(PACKAGE_DIR)+clean $(PACKAGE_DIR)+clean-recursive + +$(PACKAGE_DIR)+dist:: $(PACKAGE_DIR)/dist/.done + +$(PACKAGE_DIR)+srcdist:: $(PACKAGE_DIR)/srcdist/.done + +$(PACKAGE_DIR)+clean:: + +$(PACKAGE_DIR)+clean-with-deps:: $(PACKAGE_DIR)+clean + +# Hook into the "all package" targets used by the main public-umbrella +# makefile +all-packages:: $(PACKAGE_DIR)/dist/.done +clean-all-packages:: $(PACKAGE_DIR)+clean + +ifndef NON_INTEGRATED_$(PACKAGE_DIR) + +PACKAGE_NAME=$(notdir $(abspath $(PACKAGE_DIR))) + +# Set all the per-package vars to their default values + +# The packages upon which this package depends +DEPS:= + +# The name of the erlang application produced by the package +APP_NAME=$(call package_to_app_name,$(PACKAGE_NAME)) + +# The location of the .app file which is used as the basis for the +# .app file which goes into the .ez +ORIGINAL_APP_FILE=$(EBIN_DIR)/$(APP_NAME).app + +# The location of the source for that file (before the modules list is +# generated). Ignored if DO_NOT_GENERATE_APP_FILE is set. +ORIGINAL_APP_SOURCE=$(PACKAGE_DIR)/src/$(APP_NAME).app.src + +# Set to prevent generation of the app file. +DO_NOT_GENERATE_APP_FILE:= + +# Should the .ez files for this package, its dependencies, and its +# source distribution be included in RabbitMQ releases, and should we test +# this plugin when invoking "make test" in the umbrella? +RELEASABLE:= + +# The options to pass to erlc when compiling .erl files in this +# package +PACKAGE_ERLC_OPTS=$(ERLC_OPTS) + +# The directories containing Erlang source files +SOURCE_DIRS:=$(PACKAGE_DIR)/src + +# The Erlang source files to compile and include in the package .ez file +SOURCE_ERLS=$(strip $(foreach D,$(SOURCE_DIRS),$(wildcard $(D)/*.erl))) + +# The directories containing Erlang *.hrl files to include in the +# package .ez file. +INCLUDE_DIRS:=$(PACKAGE_DIR)/include + +# The Erlang .hrl files to include in the package .ez file. +INCLUDE_HRLS=$(strip $(foreach D,$(INCLUDE_DIRS),$(wildcard $(D)/*.hrl))) + +# The location of the directory containing the .app file. This is +# also where the .beam files produced by compiling SOURCE_ERLS will +# go. +EBIN_DIR:=$(PACKAGE_DIR)/ebin + +# The .beam files for the application. +EBIN_BEAMS=$(patsubst %,$(EBIN_DIR)/%.beam,$(notdir $(basename $(SOURCE_ERLS)))) + +# Erlang expressions which will be invoked during testing (not in the +# broker). +STANDALONE_TEST_COMMANDS:= + +# Erlang expressions which will be invoked within the broker during +# testing. +WITH_BROKER_TEST_COMMANDS:= + +# Config file to give to the test broker. +WITH_BROKER_TEST_CONFIG:= + +# Test scripts which should be invokedduring testing +STANDALONE_TEST_SCRIPTS:= + +# Test scripts which should be invoked alongside a running broker +# during testing +WITH_BROKER_TEST_SCRIPTS:= + +# Test scripts which should be invoked to configure the broker before testing +WITH_BROKER_SETUP_SCRIPTS:= + +# When cleaning, should we also remove the cloned directory for +# wrappers? +PRESERVE_CLONE_DIR?= + +# The directory within the package that contains tests +TEST_DIR=$(PACKAGE_DIR)/test + +# The directories containing .erl files for tests +TEST_SOURCE_DIRS=$(TEST_DIR)/src + +# The .erl files for tests +TEST_SOURCE_ERLS=$(strip $(foreach D,$(TEST_SOURCE_DIRS),$(wildcard $(D)/*.erl))) + +# Where to put .beam files produced by compiling TEST_SOURCE_ERLS +TEST_EBIN_DIR=$(TEST_DIR)/ebin + +# The .beam files produced by compiling TEST_SOURCE_ERLS +TEST_EBIN_BEAMS=$(patsubst %,$(TEST_EBIN_DIR)/%.beam,$(notdir $(basename $(TEST_SOURCE_ERLS)))) + +# Wrapper package variables + +# The git URL to clone from. Setting this variable marks the package +# as a wrapper package. +UPSTREAM_GIT:= + +# The Mercurial URL to clone from. Setting this variable marks the +# package as a wrapper package. +UPSTREAM_HG:= + +UPSTREAM_TYPE=$(if $(UPSTREAM_GIT),git)$(if $(UPSTREAM_HG),hg) + +# The upstream revision to clone. Leave empty for default or master +UPSTREAM_REVISION:= + +# Where to clone the upstream repository to +CLONE_DIR=$(PACKAGE_DIR)/$(patsubst %-wrapper,%,$(PACKAGE_NAME))-$(UPSTREAM_TYPE) + +# The source directories contained in the cloned repositories. These +# are appended to SOURCE_DIRS. +UPSTREAM_SOURCE_DIRS=$(CLONE_DIR)/src + +# The include directories contained in the cloned repositories. These +# are appended to INCLUDE_DIRS. +UPSTREAM_INCLUDE_DIRS=$(CLONE_DIR)/include + +# Patches to apply to the upstream codebase after cloning, if any +WRAPPER_PATCHES:= + +# The version number to assign to the build artifacts +PACKAGE_VERSION=$(VERSION) + +# Should the app version incorporate the version from the original +# .app file? +RETAIN_ORIGINAL_VERSION:= + +# The original version that should be incorporated into the package +# version if RETAIN_ORIGINAL_VERSION is set. If empty, the original +# version will be extracted from ORIGINAL_APP_FILE. +ORIGINAL_VERSION:= + +# For customising construction of the build application directory. +CONSTRUCT_APP_PREREQS:= +construct_app_commands= + +package_rules= + +# Now let the package makefile fragment do its stuff +include $(PACKAGE_DIR)/package.mk + +# package_rules provides a convenient way to force prompt expansion +# of variables, including expansion in commands that would otherwise +# be deferred. +# +# If package_rules is defined by the package makefile, we expand it +# and eval it. The point here is to get around the fact that make +# defers expansion of commands. But if we use package variables in +# targets, as we naturally want to do, deferred expansion doesn't +# work: They might have been trampled on by a later package. Because +# we expand package_rules here, references to package varialbes will +# get expanded with the values we expect. +# +# The downside is that any variable references for which expansion +# really should be deferred need to be protected by doulbing up the +# dollar. E.g., inside package_rules, you should write $$@, not $@. +# +# We use the same trick again below. +ifdef package_rules +$(eval $(package_rules)) +endif + +# Some variables used for brevity below. Packages can't set these. +APP_FILE=$(PACKAGE_DIR)/build/$(APP_NAME).app.$(PACKAGE_VERSION) +APP_DONE=$(PACKAGE_DIR)/build/app/.done.$(PACKAGE_VERSION) +APP_DIR=$(PACKAGE_DIR)/build/app/$(APP_NAME)-$(PACKAGE_VERSION) +EZ_FILE=$(PACKAGE_DIR)/dist/$(APP_NAME)-$(PACKAGE_VERSION).ez +DEPS_FILE=$(PACKAGE_DIR)/build/deps.mk + + +# Convert the DEPS package names to canonical paths +DEP_PATHS:=$(foreach DEP,$(DEPS),$(call package_to_path,$(DEP))) + +# Handle RETAIN_ORIGINAL_VERSION / ORIGINAL_VERSION +ifdef RETAIN_ORIGINAL_VERSION + +# Automatically acquire ORIGINAL_VERSION from ORIGINAL_APP_FILE +ifndef ORIGINAL_VERSION + +# The generated ORIGINAL_VERSION setting goes in build/version.mk +$(eval $(call safe_include,$(PACKAGE_DIR)/build/version.mk)) + +$(PACKAGE_DIR)/build/version.mk: $(ORIGINAL_APP_FILE) + sed -n -e 's|^.*{vsn, *"\([^"]*\)".*$$|ORIGINAL_VERSION:=\1|p' <$< >$@ + +$(APP_FILE): $(PACKAGE_DIR)/build/version.mk + +endif # ifndef ORIGINAL_VERSION + +PACKAGE_VERSION:=$(ORIGINAL_VERSION)-rmq$(VERSION) + +endif # ifdef RETAIN_ORIGINAL_VERSION + +# Handle wrapper packages +ifneq ($(UPSTREAM_TYPE),) + +SOURCE_DIRS+=$(UPSTREAM_SOURCE_DIRS) +INCLUDE_DIRS+=$(UPSTREAM_INCLUDE_DIRS) + +define package_rules + +ifdef UPSTREAM_GIT +$(CLONE_DIR)/.done: + rm -rf $(CLONE_DIR) + git clone $(UPSTREAM_GIT) $(CLONE_DIR) + # Work around weird github breakage (bug 25264) + cd $(CLONE_DIR) && git pull + $(if $(UPSTREAM_REVISION),cd $(CLONE_DIR) && git checkout $(UPSTREAM_REVISION)) + $(if $(WRAPPER_PATCHES),$(foreach F,$(WRAPPER_PATCHES),patch -d $(CLONE_DIR) -p1 <$(PACKAGE_DIR)/$(F) &&) :) + touch $$@ +endif # UPSTREAM_GIT + +ifdef UPSTREAM_HG +$(CLONE_DIR)/.done: + rm -rf $(CLONE_DIR) + hg clone -r $(or $(UPSTREAM_REVISION),default) $(UPSTREAM_HG) $(CLONE_DIR) + $(if $(WRAPPER_PATCHES),$(foreach F,$(WRAPPER_PATCHES),patch -d $(CLONE_DIR) -p1 <$(PACKAGE_DIR)/$(F) &&) :) + touch $$@ +endif # UPSTREAM_HG + +# When we clone, we need to remake anything derived from the app file +# (e.g. build/version.mk). +$(ORIGINAL_APP_FILE): $(CLONE_DIR)/.done + +# We include the commit hash into the package version, via hash.mk +# (not in build/ because we want it to survive +# make PRESERVE_CLONE_DIR=true clean +# for obvious reasons) +$(eval $(call safe_include,$(PACKAGE_DIR)/hash.mk)) + +$(PACKAGE_DIR)/hash.mk: $(CLONE_DIR)/.done + @mkdir -p $$(@D) +ifdef UPSTREAM_GIT + echo UPSTREAM_SHORT_HASH:=`git --git-dir=$(CLONE_DIR)/.git log -n 1 HEAD | grep commit | cut -b 8-14` >$$@ +endif +ifdef UPSTREAM_HG + echo UPSTREAM_SHORT_HASH:=`hg id -R $(CLONE_DIR) -i | cut -c -7` >$$@ +endif + +$(APP_FILE): $(PACKAGE_DIR)/hash.mk + +PACKAGE_VERSION:=$(PACKAGE_VERSION)-$(UPSTREAM_TYPE)$(UPSTREAM_SHORT_HASH) + +$(PACKAGE_DIR)+clean:: + [ "x" != "x$(PRESERVE_CLONE_DIR)" ] || rm -rf $(CLONE_DIR) hash.mk +endef # package_rules +$(eval $(package_rules)) + +endif # UPSTREAM_TYPE + +# Generate a rule to compile .erl files from the directory $(1) into +# directory $(2), taking extra erlc options from $(3) +define package_source_dir_targets +$(2)/%.beam: $(1)/%.erl $(PACKAGE_DIR)/build/dep-apps/.done | $(DEPS_FILE) + @mkdir -p $$(@D) + ERL_LIBS=$(PACKAGE_DIR)/build/dep-apps $(ERLC) $(PACKAGE_ERLC_OPTS) $(foreach D,$(INCLUDE_DIRS),-I $(D)) -pa $$(@D) -o $$(@D) $(3) $$< + +endef + +$(eval $(foreach D,$(SOURCE_DIRS),$(call package_source_dir_targets,$(D),$(EBIN_DIR),))) +$(eval $(foreach D,$(TEST_SOURCE_DIRS),$(call package_source_dir_targets,$(D),$(TEST_EBIN_DIR),-pa $(EBIN_DIR)))) + +# Commands to run the broker for tests +# +# $(1): The value for RABBITMQ_SERVER_START_ARGS +# $(2): Extra env var settings when invoking the rabbitmq-server script +# $(3): Extra .ezs to copy into the plugins dir +define run_broker + rm -rf $(TEST_TMPDIR) + mkdir -p $(foreach D,log plugins $(NODENAME),$(TEST_TMPDIR)/$(D)) + cp -p $(PACKAGE_DIR)/dist/*.ez $(TEST_TMPDIR)/plugins + $(call copy,$(3),$(TEST_TMPDIR)/plugins) + rm -f $(TEST_TMPDIR)/plugins/rabbit_common*.ez + for plugin in \ + $$$$(RABBITMQ_PLUGINS_DIR=$(TEST_TMPDIR)/plugins \ + RABBITMQ_ENABLED_PLUGINS_FILE=$(TEST_TMPDIR)/enabled_plugins \ + $(UMBRELLA_BASE_DIR)/rabbitmq-server/scripts/rabbitmq-plugins list -m); do \ + RABBITMQ_PLUGINS_DIR=$(TEST_TMPDIR)/plugins \ + RABBITMQ_ENABLED_PLUGINS_FILE=$(TEST_TMPDIR)/enabled_plugins \ + $(UMBRELLA_BASE_DIR)/rabbitmq-server/scripts/rabbitmq-plugins \ + enable $$$$plugin; \ + done + RABBITMQ_PLUGINS_DIR=$(TEST_TMPDIR)/plugins \ + RABBITMQ_ENABLED_PLUGINS_FILE=$(TEST_TMPDIR)/enabled_plugins \ + RABBITMQ_LOG_BASE=$(TEST_TMPDIR)/log \ + RABBITMQ_MNESIA_BASE=$(TEST_TMPDIR)/$(NODENAME) \ + RABBITMQ_PID_FILE=$(TEST_TMPDIR)/$(NODENAME).pid \ + RABBITMQ_NODENAME=$(NODENAME) \ + RABBITMQ_SERVER_START_ARGS=$(1) \ + $(2) $(UMBRELLA_BASE_DIR)/rabbitmq-server/scripts/rabbitmq-server +endef + +# Commands to run the package's test suite +# +# $(1): Extra .ezs to copy into the plugins dir +define run_with_broker_tests +$(if $(WITH_BROKER_TEST_COMMANDS)$(WITH_BROKER_TEST_SCRIPTS),$(call run_with_broker_tests_aux,$1)) +endef + +define run_with_broker_tests_aux + $(call run_broker,'-pa $(TEST_EBIN_DIR) -coverage directories ["$(EBIN_DIR)"$(COMMA)"$(TEST_EBIN_DIR)"]',RABBITMQ_CONFIG_FILE=$(WITH_BROKER_TEST_CONFIG),$(1)) & + $(UMBRELLA_BASE_DIR)/rabbitmq-server/scripts/rabbitmqctl -n $(NODENAME) wait $(TEST_TMPDIR)/$(NODENAME).pid + echo > $(TEST_TMPDIR)/rabbit-test-output && \ + if $(foreach SCRIPT,$(WITH_BROKER_SETUP_SCRIPTS),$(SCRIPT) &&) \ + $(foreach CMD,$(WITH_BROKER_TEST_COMMANDS), \ + echo >> $(TEST_TMPDIR)/rabbit-test-output && \ + echo "$(CMD)." \ + | tee -a $(TEST_TMPDIR)/rabbit-test-output \ + | $(ERL_CALL) $(ERL_CALL_OPTS) \ + | tee -a $(TEST_TMPDIR)/rabbit-test-output \ + | egrep "{ok, (ok|passed)}" >/dev/null &&) \ + $(foreach SCRIPT,$(WITH_BROKER_TEST_SCRIPTS),$(SCRIPT) &&) : ; \ + then \ + touch $(TEST_TMPDIR)/.passed ; \ + echo "\nPASSED\n" ; \ + else \ + cat $(TEST_TMPDIR)/rabbit-test-output ; \ + echo "\n\nFAILED\n" ; \ + fi + sleep 1 + echo "rabbit_misc:report_cover(), init:stop()." | $(ERL_CALL) $(ERL_CALL_OPTS) + sleep 1 + test -f $(TEST_TMPDIR)/.passed +endef + +# The targets common to all integrated packages +define package_rules + +# Put all relevant ezs into the dist dir for this package, including +# the main ez file produced by this package +# +# When the package version changes, our .ez filename will change, and +# we need to regenerate the dist directory. So the dependency needs +# to go via a stamp file that incorporates the version in its name. +# But we need a target with a fixed name for other packages to depend +# on. And it can't be a phony, as a phony will always get rebuilt. +# Hence the need for two stamp files here. +$(PACKAGE_DIR)/dist/.done: $(PACKAGE_DIR)/dist/.done.$(PACKAGE_VERSION) + touch $$@ + +$(PACKAGE_DIR)/dist/.done.$(PACKAGE_VERSION): $(PACKAGE_DIR)/build/dep-ezs/.done $(APP_DONE) + rm -rf $$(@D) + mkdir -p $$(@D) + cd $(dir $(APP_DIR)) && zip -q -r $$(abspath $(EZ_FILE)) $(notdir $(APP_DIR)) + $$(call copy,$$(wildcard $$($$@ + +ifndef DO_NOT_GENERATE_APP_FILE + +# Generate the .app file. Note that this is a separate step from above +# so that the plugin still works correctly when symlinked as a directory +$(ORIGINAL_APP_FILE): $(ORIGINAL_APP_SOURCE) $(SOURCE_ERLS) $(UMBRELLA_BASE_DIR)/generate_app + @mkdir -p $$(@D) + escript $(UMBRELLA_BASE_DIR)/generate_app $$< $$@ $(SOURCE_DIRS) + +$(PACKAGE_DIR)+clean:: + rm -f $(ORIGINAL_APP_FILE) + +endif + +# Unpack the ezs from dependency packages, so that their contents are +# accessible to erlc +$(PACKAGE_DIR)/build/dep-apps/.done: $(PACKAGE_DIR)/build/dep-ezs/.done + rm -rf $$(@D) + mkdir -p $$(@D) + @echo [elided] unzip ezs + @cd $$(@D) && $$(foreach EZ,$$(wildcard $(PACKAGE_DIR)/build/dep-ezs/*.ez),unzip -q $$(abspath $$(EZ)) &&) : + touch $$@ + +# Dependency autogeneration. This is complicated slightly by the need +# to generate a dependency file which is path-independent. +$(DEPS_FILE): $(SOURCE_ERLS) $(INCLUDE_HRLS) $(TEST_SOURCE_ERLS) + @mkdir -p $$(@D) + @echo [elided] generate deps + @$$(if $$^,echo $$(subst : ,:,$$(foreach F,$$^,$$(abspath $$(F)):)) | escript $(abspath $(UMBRELLA_BASE_DIR)/generate_deps) $$@ '$$$$(EBIN_DIR)',echo >$$@) + @echo [elided] fix test deps + @$$(foreach F,$(TEST_EBIN_BEAMS),sed -e 's|^$$$$(EBIN_DIR)/$$(notdir $$(F)):|$$$$(TEST_EBIN_DIR)/$$(notdir $$(F)):|' $$@ > $$@.tmp && mv $$@.tmp $$@ && ) : + sed -e 's|$$@|$$$$(DEPS_FILE)|' $$@ > $$@.tmp && mv $$@.tmp $$@ + +$(eval $(call safe_include,$(DEPS_FILE))) + +$(PACKAGE_DIR)/srcdist/.done: $(PACKAGE_DIR)/srcdist/.done.$(PACKAGE_VERSION) + touch $$@ + +$(PACKAGE_DIR)/srcdist/.done.$(PACKAGE_VERSION): + mkdir -p $(PACKAGE_DIR)/build/srcdist/ + rsync -a --exclude '.hg*' --exclude '.git*' --exclude 'build' $(PACKAGE_DIR) $(PACKAGE_DIR)/build/srcdist/$(APP_NAME)-$(PACKAGE_VERSION) + mkdir -p $(PACKAGE_DIR)/srcdist/ + tar cjf $(PACKAGE_DIR)/srcdist/$(APP_NAME)-$(PACKAGE_VERSION)-src.tar.bz2 -C $(PACKAGE_DIR)/build/srcdist/ $(APP_NAME)-$(PACKAGE_VERSION) + touch $$@ + +$(PACKAGE_DIR)+clean:: + rm -rf $(EBIN_DIR)/*.beam $(TEST_EBIN_DIR)/*.beam $(PACKAGE_DIR)/dist $(PACKAGE_DIR)/srcdist $(PACKAGE_DIR)/build $(PACKAGE_DIR)/erl_crash.dump + +$(PACKAGE_DIR)+clean-with-deps:: $(foreach P,$(DEP_PATHS),$(P)+clean-with-deps) + +ifdef RELEASABLE +all-releasable:: $(PACKAGE_DIR)/dist/.done + +copy-releasable:: $(PACKAGE_DIR)/dist/.done + cp $(PACKAGE_DIR)/dist/*.ez $(PLUGINS_DIST_DIR) + +copy-srcdist:: $(PLUGINS_SRC_DIST_DIR)/$(PACKAGE_DIR)/.srcdist_done + +endif + +$(PLUGINS_SRC_DIST_DIR)/$(PACKAGE_DIR)/.srcdist_done:: $(ORIGINAL_APP_FILE) $(foreach P,$(DEP_PATHS),$(PLUGINS_SRC_DIST_DIR)/$(P)/.srcdist_done) + rsync -a --exclude '.hg*' --exclude '.git*' $(PACKAGE_DIR) $(PLUGINS_SRC_DIST_DIR)/ + [ -f $(PACKAGE_DIR)/license_info ] && cp $(PACKAGE_DIR)/license_info $(PLUGINS_SRC_DIST_DIR)/licensing/license_info_$(PACKAGE_NAME) || true + find $(PACKAGE_DIR) -maxdepth 1 -name 'LICENSE-*' -exec cp '{}' $(PLUGINS_SRC_DIST_DIR)/licensing/ \; + touch $(PLUGINS_SRC_DIST_DIR)/$(PACKAGE_DIR)/.srcdist_done + +# A hook to allow packages to verify that prerequisites are satisfied +# before running. +.PHONY: $(PACKAGE_DIR)+pre-run +$(PACKAGE_DIR)+pre-run:: + +# Run erlang with the package, its tests, and all its dependencies +# available. +.PHONY: $(PACKAGE_DIR)+run +$(PACKAGE_DIR)+run: $(PACKAGE_DIR)/dist/.done $(TEST_EBIN_BEAMS) $(PACKAGE_DIR)+pre-run + ERL_LIBS=$(PACKAGE_DIR)/dist $(ERL) $(ERL_OPTS) -pa $(TEST_EBIN_DIR) + +# Run the broker with the package, its tests, and all its dependencies +# available. +.PHONY: $(PACKAGE_DIR)+run-in-broker +$(PACKAGE_DIR)+run-in-broker: $(PACKAGE_DIR)/dist/.done $(RABBITMQ_SERVER_PATH)/dist/.done $(TEST_EBIN_BEAMS) + $(call run_broker,'-pa $(TEST_EBIN_DIR)',RABBITMQ_ALLOW_INPUT=true) + +# A hook to allow packages to verify that prerequisites are satisfied +# before running tests. +.PHONY: $(PACKAGE_DIR)+pre-test +$(PACKAGE_DIR)+pre-test:: + +# Runs the package's tests that operate within (or in conjuction with) +# a running broker. +.PHONY: $(PACKAGE_DIR)+in-broker-test +$(PACKAGE_DIR)+in-broker-test: $(PACKAGE_DIR)/dist/.done $(RABBITMQ_SERVER_PATH)/dist/.done $(TEST_EBIN_BEAMS) $(PACKAGE_DIR)+pre-test $(PACKAGE_DIR)+standalone-test $(if $(RELEASABLE),$(call chain_test,$(PACKAGE_DIR)+in-broker-test)) + $(call run_with_broker_tests) + +# Running the coverage tests requires Erlang/OTP R14. Note that +# coverage only covers the in-broker tests. +.PHONY: $(PACKAGE_DIR)+coverage +$(PACKAGE_DIR)+coverage: $(PACKAGE_DIR)/dist/.done $(COVERAGE_PATH)/dist/.done $(TEST_EBIN_BEAMS) $(PACKAGE_DIR)+pre-test + $(call run_with_broker_tests,$(COVERAGE_PATH)/dist/*.ez) + +# Runs the package's tests that don't need a running broker +.PHONY: $(PACKAGE_DIR)+standalone-test +$(PACKAGE_DIR)+standalone-test: $(PACKAGE_DIR)/dist/.done $(TEST_EBIN_BEAMS) $(PACKAGE_DIR)+pre-test $(if $(RELEASABLE),$(call chain_test,$(PACKAGE_DIR)+standalone-test)) + $$(if $(STANDALONE_TEST_COMMANDS),\ + $$(foreach CMD,$(STANDALONE_TEST_COMMANDS),\ + ERL_LIBS=$(PACKAGE_DIR)/dist $(ERL) -noinput $(ERL_OPTS) -pa $(TEST_EBIN_DIR) -sname standalone_test -eval "init:stop(case $$(CMD) of ok -> 0; passed -> 0; _Else -> 1 end)" &&\ + )\ + :) + $$(if $(STANDALONE_TEST_SCRIPTS),$$(foreach SCRIPT,$(STANDALONE_TEST_SCRIPTS),$$(SCRIPT) &&) :) + +# Run all the package's tests +.PHONY: $(PACKAGE_DIR)+test +$(PACKAGE_DIR)+test:: $(PACKAGE_DIR)+standalone-test $(PACKAGE_DIR)+in-broker-test + +.PHONY: $(PACKAGE_DIR)+check-xref +$(PACKAGE_DIR)+check-xref: $(PACKAGE_DIR)/dist/.done + UNPACKDIR=$$$$(mktemp -d $(TMPDIR)/tmp.XXXXXXXXXX) && \ + for ez in $$$$(find $(PACKAGE_DIR)/dist -type f -name "*.ez"); do \ + unzip -q $$$${ez} -d $$$${UNPACKDIR}; \ + done && \ + rm -rf $$$${UNPACKDIR}/rabbit_common-* && \ + ln -sf $$$$(pwd)/$(RABBITMQ_SERVER_PATH)/ebin $$$${UNPACKDIR} && \ + OK=true && \ + { $(UMBRELLA_BASE_DIR)/check_xref $(PACKAGE_DIR) $$$${UNPACKDIR} || OK=false; } && \ + rm -rf $$$${UNPACKDIR} && \ + $$$${OK} + +check-xref-packages:: $(PACKAGE_DIR)+check-xref + +endef +$(eval $(package_rules)) + +# Recursing into dependency packages has to be the last thing we do +# because it will trample all over the per-package variables. + +# Recurse into dependency packages +$(foreach DEP_PATH,$(DEP_PATHS),$(eval $(call do_package,$(DEP_PATH)))) + +else # NON_INTEGRATED_$(PACKAGE_DIR) + +define package_rules + +# When the package version changes, our .ez filename will change, and +# we need to regenerate the dist directory. So the dependency needs +# to go via a stamp file that incorporates the version in its name. +# But we need a target with a fixed name for other packages to depend +# on. And it can't be a phony, as a phony will always get rebuilt. +# Hence the need for two stamp files here. +$(PACKAGE_DIR)/dist/.done: $(PACKAGE_DIR)/dist/.done.$(VERSION) + touch $$@ + +# Non-integrated packages (rabbitmq-server and rabbitmq-erlang-client) +# present a dilemma. We could re-make the package every time we need +# it. But that will cause a huge amount of unnecessary rebuilding. +# Or we could not worry about rebuilding non-integrated packages. +# That's good for those developing plugins, but not for those who want +# to work on the broker and erlang client in the context of the +# plugins. So instead, we use a conservative approximation to the +# dependency structure within the package, to tell when to re-run the +# makefile. +$(PACKAGE_DIR)/dist/.done.$(VERSION): $(PACKAGE_DIR)/Makefile $(wildcard $(PACKAGE_DIR)/*.mk) $(wildcard $(PACKAGE_DIR)/src/*.erl) $(wildcard $(PACKAGE_DIR)/include/*.hrl) $(wildcard $(PACKAGE_DIR)/*.py) $(foreach DEP,$(NON_INTEGRATED_DEPS_$(PACKAGE_DIR)),$(call package_to_path,$(DEP))/dist/.done) + rm -rf $$(@D) + $$(MAKE) -C $(PACKAGE_DIR) + mkdir -p $$(@D) + touch $$@ + +# When building plugins-src we want to "make clean", but some +# non-integrated packages will not be there. Don't fall over in that case. +$(PACKAGE_DIR)+clean:: + if [ -d $(PACKAGE_DIR) ] ; then $$(MAKE) -C $(PACKAGE_DIR) clean ; fi + rm -rf $(PACKAGE_DIR)/dist + +endef +$(eval $(package_rules)) + +endif # NON_INTEGRATED_$(PACKAGE_DIR) diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/.srcdist_done b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/.srcdist_done new file mode 100644 index 0000000..e69de29 diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/LICENSE-MIT-eldap b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/LICENSE-MIT-eldap new file mode 100644 index 0000000..1f62009 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/LICENSE-MIT-eldap @@ -0,0 +1,21 @@ + +Copyright (c) 2010, Torbjorn Tornkvist + +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/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/Makefile b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/Makefile new file mode 100644 index 0000000..482105a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/Makefile @@ -0,0 +1 @@ +include ../umbrella.mk diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-appify.patch b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-appify.patch new file mode 100644 index 0000000..90ad3d2 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-appify.patch @@ -0,0 +1,14 @@ +diff -Naur eldap.orig/ebin/eldap.app eldap/ebin/eldap.app +--- eldap.orig/ebin/eldap.app 1970-01-01 01:00:00.000000000 +0100 ++++ eldap/ebin/eldap.app 2011-01-20 12:47:04.377399296 +0000 +@@ -0,0 +1,10 @@ ++{application, eldap, ++ [{description, "LDAP Client Library"}, ++ {vsn, "0.01"}, ++ {modules, [ ++ eldap, ++ 'ELDAPv3' ++ ]}, ++ {registered, []}, ++ {applications, [kernel, stdlib]} ++ ]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/.done b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/.done new file mode 100644 index 0000000..e69de29 diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/LICENSE b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/LICENSE new file mode 100644 index 0000000..1f62009 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) 2010, Torbjorn Tornkvist + +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/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/Makefile b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/Makefile new file mode 100644 index 0000000..f5ecba4 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/Makefile @@ -0,0 +1,7 @@ + +all: + (cd src;$(MAKE)) + +clean: + (cd src;$(MAKE) clean) + diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/README b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/README new file mode 100644 index 0000000..e1bde9d --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/README @@ -0,0 +1,33 @@ +Hi, + +This is 'eldap', the Erlang LDAP library. + +It exports an API that can do all possible operations +you may want to do against an LDAP server. The code has +been tested to work at some point, but only the bind +and search operations are running daily in our products, +so there may be bugs lurking in some parts of the code. + +To just use eldap for doing authentication, do like in: + + {ok,X} = eldap:open(["ldap.mycorp.com"], []). + eldap:simple_bind(X, "uid=tobbe,ou=People,dc=mycorp,dc=com", "passwd"). + +In the doc/README.example you'll find a trace from a +Erlang shell session as an example on how to setup a +connection, authenticate (bind) and perform a search. +Note that by using the option {ssl, true}, you should +be able to setup an SSL tunnel (LDAPS) if your Erlang +system has been configured with SSL. + +In the test directory there are some hints and examples +on how to test the code and how to setup and populate +an OpenLDAP server. The 'eldap' code has been tested +agains OpenLDAP, IPlanet and ActiveDirectory servers. + +If you plan to incorporate this code into your system +I suggest that you build a server/supervisor harnesk +that uses 'eldap' (as we have done in our products). + +Good luck ! +/Tobbe diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/doc/README.example b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/doc/README.example new file mode 100644 index 0000000..b96d5ef --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/doc/README.example @@ -0,0 +1,44 @@ +1> {_,S} = eldap:open(["192.168.128.47"], []). +{ok,<0.30.0>} +2> eldap:simple_bind(S,"cn=Torbjorn Tornkvist,cn=Users,dc=bluetail,dc=com","qwe123"). +ok +3> Base = {base, "dc=bluetail,dc=com"}. +{base,"dc=bluetail,dc=com"} +4> Scope = {scope, eldap:wholeSubtree()}. +{scope,wholeSubtree} +5> Filter = {filter, eldap:equalityMatch("sAMAccountName", "tobbe")}. +{filter,{equalityMatch,{'AttributeValueAssertion',"sAMAccountName","tobbe"}}} +6> Search = [Base, Scope, Filter]. +[{base,"dc=bluetail,dc=com"}, + {scope,wholeSubtree}, + {filter,{equalityMatch,{'AttributeValueAssertion',"sAMAccountName","tobbe"}}}] +7> eldap:search(S, Search). +{ok,{eldap_search_result,[{eldap_entry, + "CN=Torbjorn Tornkvist,CN=Users,DC=bluetail,DC=com", + [{"manager", + ["CN=Tord Larsson,CN=Users,DC=bluetail,DC=com"]}, + {"memberOf", + ["CN=TestGroup2,CN=Users,DC=bluetail,DC=com", + "CN=TestGroup,CN=Users,DC=bluetail,DC=com", + "CN=Pre-Windows 2000 Compatible Access,CN=Builtin,DC=bluetail,DC=com", + "CN=Server Operators,CN=Builtin,DC=bluetail,DC=com"]}, + {"accountExpires",["0"]}, + {"adminCount",["1"]}, + {"badPasswordTime",["127119104851642448"]}, + {"badPwdCount",["0"]}, + {"codePage",["0"]}, + {"cn",["Torbjorn Tornkvist"]}, + {"company",["Alteon Web Systems"]}, + {"countryCode",["0"]}, + {"department",["Bluetail"]}, + {"displayName",["Torbjorn Tornkvist"]}, + {"mail",["tobbe@bluetail.com"]}, + {"givenName",["Torbjorn"]}, + {"instanceType",["4"]}, + {"lastLogoff",["0"]}, + {"lastLogon",["127119109376267104"]}, + {"logonCount",[...]}, + {"msNPAllowDialin"|...}, + {...}|...]}], + [["ldap://bluetail.com/CN=Configuration,DC=bluetail,DC=com"]]}} +8> diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/doc/short-desc b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/doc/short-desc new file mode 100644 index 0000000..e236da3 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/doc/short-desc @@ -0,0 +1 @@ +This is 'eldap', the Erlang LDAP library. diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/ebin/eldap.app b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/ebin/eldap.app new file mode 100644 index 0000000..3c4e87e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/ebin/eldap.app @@ -0,0 +1,10 @@ +{application, eldap, + [{description, "LDAP Client Library"}, + {vsn, "0.01"}, + {modules, [ + eldap, + 'ELDAPv3' + ]}, + {registered, []}, + {applications, [kernel, stdlib]} + ]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/include/eldap.hrl b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/include/eldap.hrl new file mode 100644 index 0000000..ee5ad2f --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/include/eldap.hrl @@ -0,0 +1,32 @@ +-ifndef( _ELDAP_HRL ). +-define( _ELDAP_HRL , 1 ). + +%%% +%%% Search input parameters +%%% +-record(eldap_search, { + base = [], % Baseobject + filter = [], % Search conditions + scope, % Search scope + attributes = [], % Attributes to be returned + types_only = false, % Return types+values or types + timeout = 0 % Timelimit for search + }). + +%%% +%%% Returned search result +%%% +-record(eldap_search_result, { + entries = [], % List of #eldap_entry{} records + referrals = [] % List of referrals + }). + +%%% +%%% LDAP entry +%%% +-record(eldap_entry, { + object_name = "", % The DN for the entry + attributes = [] % List of {Attribute, Value} pairs + }). + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/src/ELDAPv3.asn b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/src/ELDAPv3.asn new file mode 100644 index 0000000..0cfac48 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/src/ELDAPv3.asn @@ -0,0 +1,291 @@ +-- LDAPv3 ASN.1 specification, taken from RFC 2251 + +-- Lightweight-Directory-Access-Protocol-V3 DEFINITIONS +ELDAPv3 DEFINITIONS +IMPLICIT TAGS ::= + +BEGIN + +LDAPMessage ::= SEQUENCE { + messageID MessageID, + protocolOp CHOICE { + bindRequest BindRequest, + bindResponse BindResponse, + unbindRequest UnbindRequest, + searchRequest SearchRequest, + searchResEntry SearchResultEntry, + searchResDone SearchResultDone, + searchResRef SearchResultReference, + modifyRequest ModifyRequest, + modifyResponse ModifyResponse, + addRequest AddRequest, + addResponse AddResponse, + delRequest DelRequest, + delResponse DelResponse, + modDNRequest ModifyDNRequest, + modDNResponse ModifyDNResponse, + compareRequest CompareRequest, + compareResponse CompareResponse, + abandonRequest AbandonRequest, + extendedReq ExtendedRequest, + extendedResp ExtendedResponse }, + controls [0] Controls OPTIONAL } + +MessageID ::= INTEGER (0 .. maxInt) + +maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) -- + +LDAPString ::= OCTET STRING + +LDAPOID ::= OCTET STRING + +LDAPDN ::= LDAPString + +RelativeLDAPDN ::= LDAPString + +AttributeType ::= LDAPString + +AttributeDescription ::= LDAPString + + + + +-- Wahl, et. al. Standards Track [Page 44] +-- +-- RFC 2251 LDAPv3 December 1997 + + +AttributeDescriptionList ::= SEQUENCE OF + AttributeDescription + +AttributeValue ::= OCTET STRING + +AttributeValueAssertion ::= SEQUENCE { + attributeDesc AttributeDescription, + assertionValue AssertionValue } + +AssertionValue ::= OCTET STRING + +Attribute ::= SEQUENCE { + type AttributeDescription, + vals SET OF AttributeValue } + +MatchingRuleId ::= LDAPString + +LDAPResult ::= SEQUENCE { + resultCode ENUMERATED { + success (0), + operationsError (1), + protocolError (2), + timeLimitExceeded (3), + sizeLimitExceeded (4), + compareFalse (5), + compareTrue (6), + authMethodNotSupported (7), + strongAuthRequired (8), + -- 9 reserved -- + referral (10), -- new + adminLimitExceeded (11), -- new + unavailableCriticalExtension (12), -- new + confidentialityRequired (13), -- new + saslBindInProgress (14), -- new + noSuchAttribute (16), + undefinedAttributeType (17), + inappropriateMatching (18), + constraintViolation (19), + attributeOrValueExists (20), + invalidAttributeSyntax (21), + -- 22-31 unused -- + noSuchObject (32), + aliasProblem (33), + invalidDNSyntax (34), + -- 35 reserved for undefined isLeaf -- + aliasDereferencingProblem (36), + -- 37-47 unused -- + inappropriateAuthentication (48), + +-- Wahl, et. al. Standards Track [Page 45] +-- +-- RFC 2251 LDAPv3 December 1997 + + + invalidCredentials (49), + insufficientAccessRights (50), + busy (51), + unavailable (52), + unwillingToPerform (53), + loopDetect (54), + -- 55-63 unused -- + namingViolation (64), + objectClassViolation (65), + notAllowedOnNonLeaf (66), + notAllowedOnRDN (67), + entryAlreadyExists (68), + objectClassModsProhibited (69), + -- 70 reserved for CLDAP -- + affectsMultipleDSAs (71), -- new + -- 72-79 unused -- + other (80) }, + -- 81-90 reserved for APIs -- + matchedDN LDAPDN, + errorMessage LDAPString, + referral [3] Referral OPTIONAL } + +Referral ::= SEQUENCE OF LDAPURL + +LDAPURL ::= LDAPString -- limited to characters permitted in URLs + +Controls ::= SEQUENCE OF Control + +Control ::= SEQUENCE { + controlType LDAPOID, + criticality BOOLEAN DEFAULT FALSE, + controlValue OCTET STRING OPTIONAL } + +BindRequest ::= [APPLICATION 0] SEQUENCE { + version INTEGER (1 .. 127), + name LDAPDN, + authentication AuthenticationChoice } + +AuthenticationChoice ::= CHOICE { + simple [0] OCTET STRING, + -- 1 and 2 reserved + sasl [3] SaslCredentials } + +SaslCredentials ::= SEQUENCE { + mechanism LDAPString, + credentials OCTET STRING OPTIONAL } + +BindResponse ::= [APPLICATION 1] SEQUENCE { + +-- Wahl, et. al. Standards Track [Page 46] +-- +-- RFC 2251 LDAPv3 December 1997 + + + COMPONENTS OF LDAPResult, + serverSaslCreds [7] OCTET STRING OPTIONAL } + +UnbindRequest ::= [APPLICATION 2] NULL + +SearchRequest ::= [APPLICATION 3] SEQUENCE { + baseObject LDAPDN, + scope ENUMERATED { + baseObject (0), + singleLevel (1), + wholeSubtree (2) }, + derefAliases ENUMERATED { + neverDerefAliases (0), + derefInSearching (1), + derefFindingBaseObj (2), + derefAlways (3) }, + sizeLimit INTEGER (0 .. maxInt), + timeLimit INTEGER (0 .. maxInt), + typesOnly BOOLEAN, + filter Filter, + attributes AttributeDescriptionList } + +Filter ::= CHOICE { + and [0] SET OF Filter, + or [1] SET OF Filter, + not [2] Filter, + equalityMatch [3] AttributeValueAssertion, + substrings [4] SubstringFilter, + greaterOrEqual [5] AttributeValueAssertion, + lessOrEqual [6] AttributeValueAssertion, + present [7] AttributeDescription, + approxMatch [8] AttributeValueAssertion, + extensibleMatch [9] MatchingRuleAssertion } + +SubstringFilter ::= SEQUENCE { + type AttributeDescription, + -- at least one must be present + substrings SEQUENCE OF CHOICE { + initial [0] LDAPString, + any [1] LDAPString, + final [2] LDAPString } } + +MatchingRuleAssertion ::= SEQUENCE { + matchingRule [1] MatchingRuleId OPTIONAL, + type [2] AttributeDescription OPTIONAL, + matchValue [3] AssertionValue, + dnAttributes [4] BOOLEAN DEFAULT FALSE } + +-- Wahl, et. al. Standards Track [Page 47] +-- +-- RFC 2251 LDAPv3 December 1997 + +SearchResultEntry ::= [APPLICATION 4] SEQUENCE { + objectName LDAPDN, + attributes PartialAttributeList } + +PartialAttributeList ::= SEQUENCE OF SEQUENCE { + type AttributeDescription, + vals SET OF AttributeValue } + +SearchResultReference ::= [APPLICATION 19] SEQUENCE OF LDAPURL + +SearchResultDone ::= [APPLICATION 5] LDAPResult + +ModifyRequest ::= [APPLICATION 6] SEQUENCE { + object LDAPDN, + modification SEQUENCE OF SEQUENCE { + operation ENUMERATED { + add (0), + delete (1), + replace (2) }, + modification AttributeTypeAndValues } } + +AttributeTypeAndValues ::= SEQUENCE { + type AttributeDescription, + vals SET OF AttributeValue } + +ModifyResponse ::= [APPLICATION 7] LDAPResult + +AddRequest ::= [APPLICATION 8] SEQUENCE { + entry LDAPDN, + attributes AttributeList } + +AttributeList ::= SEQUENCE OF SEQUENCE { + type AttributeDescription, + vals SET OF AttributeValue } + +AddResponse ::= [APPLICATION 9] LDAPResult + +DelRequest ::= [APPLICATION 10] LDAPDN + +DelResponse ::= [APPLICATION 11] LDAPResult + +ModifyDNRequest ::= [APPLICATION 12] SEQUENCE { + entry LDAPDN, + newrdn RelativeLDAPDN, + deleteoldrdn BOOLEAN, + newSuperior [0] LDAPDN OPTIONAL } + +ModifyDNResponse ::= [APPLICATION 13] LDAPResult + +-- Wahl, et. al. Standards Track [Page 48] +-- +-- RFC 2251 LDAPv3 December 1997 + + +CompareRequest ::= [APPLICATION 14] SEQUENCE { + entry LDAPDN, + ava AttributeValueAssertion } + +CompareResponse ::= [APPLICATION 15] LDAPResult + +AbandonRequest ::= [APPLICATION 16] MessageID + +ExtendedRequest ::= [APPLICATION 23] SEQUENCE { + requestName [0] LDAPOID, + requestValue [1] OCTET STRING OPTIONAL } + +ExtendedResponse ::= [APPLICATION 24] SEQUENCE { + COMPONENTS OF LDAPResult, + responseName [10] LDAPOID OPTIONAL, + response [11] OCTET STRING OPTIONAL } + +END + + diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/src/Makefile b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/src/Makefile new file mode 100644 index 0000000..dc15604 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/src/Makefile @@ -0,0 +1,26 @@ + +ERLC = erlc +EBIN_DIR = ../ebin +ERLC_FLAGS += -I ./src -I ../include +debug_info +ERL_OBJECTS := ${EBIN_DIR}/eldap.beam ${EBIN_DIR}/ELDAPv3.beam ${EBIN_DIR}/eldap_fsm.beam + +.SUFFIXES: .asn .erl .beam + +$(EBIN_DIR)/%.beam: %.erl + $(ERLC) $(ERLC_FLAGS) -o $(EBIN_DIR) $< + +.PHONY: all +all: asn $(ERL_OBJECTS) + +.PHONY: asn +asn: ELDAPv3.erl ../ebin/ELDAPv3.beam + +ELDAPv3.erl: ELDAPv3.asn + ${ERLC} ELDAPv3.asn + mv ELDAPv3.beam ${EBIN_DIR} + +.PHONY: clean +clean: + -rm $(ERL_OBJECTS) ELDAPv3.erl ELDAPv3.asn1db ELDAPv3.hrl + + diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/src/eldap.erl b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/src/eldap.erl new file mode 100644 index 0000000..b8422f2 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/src/eldap.erl @@ -0,0 +1,1078 @@ +-module(eldap). +%%% -------------------------------------------------------------------- +%%% Created: 12 Oct 2000 by Tobbe +%%% Function: Erlang client LDAP implementation according RFC 2251,2253 +%%% and 2255. The interface is based on RFC 1823, and +%%% draft-ietf-asid-ldap-c-api-00.txt +%%% +%%% Copyright (c) 2010 Torbjorn Tornkvist +%%% See MIT-LICENSE at the top dir for licensing information. +%%% -------------------------------------------------------------------- +-vc('$Id$ '). +-export([open/1,open/2,simple_bind/3,controlling_process/2, + baseObject/0,singleLevel/0,wholeSubtree/0,close/1, + equalityMatch/2,greaterOrEqual/2,lessOrEqual/2, + approxMatch/2,search/2,substrings/2,present/1, + 'and'/1,'or'/1,'not'/1,modify/3, mod_add/2, mod_delete/2, + mod_replace/2, add/3, delete/2, modify_dn/5,parse_dn/1, + parse_ldap_url/1]). + +-import(lists,[concat/1]). + +-include("ELDAPv3.hrl"). +-include("eldap.hrl"). + +-define(LDAP_VERSION, 3). +-define(LDAP_PORT, 389). +-define(LDAPS_PORT, 636). + +-record(eldap, {version = ?LDAP_VERSION, + host, % Host running LDAP server + port = ?LDAP_PORT, % The LDAP server port + fd, % Socket filedescriptor. + binddn = "", % Name of the entry to bind as + passwd, % Password for (above) entry + id = 0, % LDAP Request ID + log, % User provided log function + timeout = infinity, % Request timeout + anon_auth = false, % Allow anonymous authentication + use_tls = false % LDAP/LDAPS + }). + +%%% For debug purposes +%%-define(PRINT(S, A), io:fwrite("~w(~w): " ++ S, [?MODULE,?LINE|A])). +-define(PRINT(S, A), true). + +-define(elog(S, A), error_logger:info_msg("~w(~w): "++S,[?MODULE,?LINE|A])). + +%%% ==================================================================== +%%% Exported interface +%%% ==================================================================== + +%%% -------------------------------------------------------------------- +%%% open(Hosts [,Opts] ) +%%% -------------------- +%%% Setup a connection to on of the Hosts in the argument +%%% list. Stop at the first successful connection attempt. +%%% Valid Opts are: Where: +%%% +%%% {port, Port} - Port is the port number +%%% {log, F} - F(LogLevel, FormatString, ListOfArgs) +%%% {timeout, milliSec} - request timeout +%%% +%%% -------------------------------------------------------------------- +open(Hosts) -> + open(Hosts, []). + +open(Hosts, Opts) when list(Hosts), list(Opts) -> + Self = self(), + Pid = spawn_link(fun() -> init(Hosts, Opts, Self) end), + recv(Pid). + +%%% -------------------------------------------------------------------- +%%% Shutdown connection (and process) asynchronous. +%%% -------------------------------------------------------------------- + +close(Handle) when pid(Handle) -> + send(Handle, close). + +%%% -------------------------------------------------------------------- +%%% Set who we should link ourselves to +%%% -------------------------------------------------------------------- + +controlling_process(Handle, Pid) when pid(Handle),pid(Pid) -> + link(Pid), + send(Handle, {cnt_proc, Pid}), + recv(Handle). + +%%% -------------------------------------------------------------------- +%%% Authenticate ourselves to the Directory +%%% using simple authentication. +%%% +%%% Dn - The name of the entry to bind as +%%% Passwd - The password to be used +%%% +%%% Returns: ok | {error, Error} +%%% -------------------------------------------------------------------- +simple_bind(Handle, Dn, Passwd) when pid(Handle) -> + send(Handle, {simple_bind, Dn, Passwd}), + recv(Handle). + +%%% -------------------------------------------------------------------- +%%% Add an entry. The entry field MUST NOT exist for the AddRequest +%%% to succeed. The parent of the entry MUST exist. +%%% Example: +%%% +%%% add(Handle, +%%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com", +%%% [{"objectclass", ["person"]}, +%%% {"cn", ["Bill Valentine"]}, +%%% {"sn", ["Valentine"]}, +%%% {"telephoneNumber", ["545 555 00"]}] +%%% ) +%%% -------------------------------------------------------------------- +add(Handle, Entry, Attributes) when pid(Handle),list(Entry),list(Attributes) -> + send(Handle, {add, Entry, add_attrs(Attributes)}), + recv(Handle). + +%%% Do sanity check ! +add_attrs(Attrs) -> + F = fun({Type,Vals}) when list(Type),list(Vals) -> + %% Confused ? Me too... :-/ + {'AddRequest_attributes',Type, Vals} + end, + case catch lists:map(F, Attrs) of + {'EXIT', _} -> throw({error, attribute_values}); + Else -> Else + end. + +%%% -------------------------------------------------------------------- +%%% Delete an entry. The entry consists of the DN of +%%% the entry to be deleted. +%%% Example: +%%% +%%% delete(Handle, +%%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com" +%%% ) +%%% -------------------------------------------------------------------- +delete(Handle, Entry) when pid(Handle), list(Entry) -> + send(Handle, {delete, Entry}), + recv(Handle). + +%%% -------------------------------------------------------------------- +%%% Modify an entry. Given an entry a number of modification +%%% operations can be performed as one atomic operation. +%%% Example: +%%% +%%% modify(Handle, +%%% "cn=Torbjorn Tornkvist, ou=people, o=Bluetail AB, dc=bluetail, dc=com", +%%% [replace("telephoneNumber", ["555 555 00"]), +%%% add("description", ["LDAP hacker"])] +%%% ) +%%% -------------------------------------------------------------------- +modify(Handle, Object, Mods) when pid(Handle), list(Object), list(Mods) -> + send(Handle, {modify, Object, Mods}), + recv(Handle). + +%%% +%%% Modification operations. +%%% Example: +%%% replace("telephoneNumber", ["555 555 00"]) +%%% +mod_add(Type, Values) when list(Type), list(Values) -> m(add, Type, Values). +mod_delete(Type, Values) when list(Type), list(Values) -> m(delete, Type, Values). +mod_replace(Type, Values) when list(Type), list(Values) -> m(replace, Type, Values). + +m(Operation, Type, Values) -> + #'ModifyRequest_modification_SEQOF'{ + operation = Operation, + modification = #'AttributeTypeAndValues'{ + type = Type, + vals = Values}}. + +%%% -------------------------------------------------------------------- +%%% Modify an entry. Given an entry a number of modification +%%% operations can be performed as one atomic operation. +%%% Example: +%%% +%%% modify_dn(Handle, +%%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com", +%%% "cn=Ben Emerson", +%%% true, +%%% "" +%%% ) +%%% -------------------------------------------------------------------- +modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) + when pid(Handle),list(Entry),list(NewRDN),atom(DelOldRDN),list(NewSup) -> + send(Handle, {modify_dn, Entry, NewRDN, + bool_p(DelOldRDN), optional(NewSup)}), + recv(Handle). + +%%% Sanity checks ! + +bool_p(Bool) when Bool==true;Bool==false -> Bool. + +optional([]) -> asn1_NOVALUE; +optional(Value) -> Value. + +%%% -------------------------------------------------------------------- +%%% Synchronous search of the Directory returning a +%%% requested set of attributes. +%%% +%%% Example: +%%% +%%% Filter = eldap:substrings("sn", [{any,"o"}]), +%%% eldap:search(S, [{base, "dc=bluetail, dc=com"}, +%%% {filter, Filter}, +%%% {attributes,["cn"]}])), +%%% +%%% Returned result: {ok, #eldap_search_result{}} +%%% +%%% Example: +%%% +%%% {ok,{eldap_search_result, +%%% [{eldap_entry, +%%% "cn=Magnus Froberg, dc=bluetail, dc=com", +%%% [{"cn",["Magnus Froberg"]}]}, +%%% {eldap_entry, +%%% "cn=Torbjorn Tornkvist, dc=bluetail, dc=com", +%%% [{"cn",["Torbjorn Tornkvist"]}]}], +%%% []}} +%%% +%%% -------------------------------------------------------------------- +search(Handle, A) when pid(Handle), record(A, eldap_search) -> + call_search(Handle, A); +search(Handle, L) when pid(Handle), list(L) -> + case catch parse_search_args(L) of + {error, Emsg} -> {error, Emsg}; + A when record(A, eldap_search) -> call_search(Handle, A) + end. + +call_search(Handle, A) -> + send(Handle, {search, A}), + recv(Handle). + +parse_search_args(Args) -> + parse_search_args(Args, #eldap_search{scope = wholeSubtree}). + +parse_search_args([{base, Base}|T],A) -> + parse_search_args(T,A#eldap_search{base = Base}); +parse_search_args([{filter, Filter}|T],A) -> + parse_search_args(T,A#eldap_search{filter = Filter}); +parse_search_args([{scope, Scope}|T],A) -> + parse_search_args(T,A#eldap_search{scope = Scope}); +parse_search_args([{attributes, Attrs}|T],A) -> + parse_search_args(T,A#eldap_search{attributes = Attrs}); +parse_search_args([{types_only, TypesOnly}|T],A) -> + parse_search_args(T,A#eldap_search{types_only = TypesOnly}); +parse_search_args([{timeout, Timeout}|T],A) when integer(Timeout) -> + parse_search_args(T,A#eldap_search{timeout = Timeout}); +parse_search_args([H|_],_) -> + throw({error,{unknown_arg, H}}); +parse_search_args([],A) -> + A. + +%%% +%%% The Scope parameter +%%% +baseObject() -> baseObject. +singleLevel() -> singleLevel. +wholeSubtree() -> wholeSubtree. + +%%% +%%% Boolean filter operations +%%% +'and'(ListOfFilters) when list(ListOfFilters) -> {'and',ListOfFilters}. +'or'(ListOfFilters) when list(ListOfFilters) -> {'or', ListOfFilters}. +'not'(Filter) when tuple(Filter) -> {'not',Filter}. + +%%% +%%% The following Filter parameters consist of an attribute +%%% and an attribute value. Example: F("uid","tobbe") +%%% +equalityMatch(Desc, Value) -> {equalityMatch, av_assert(Desc, Value)}. +greaterOrEqual(Desc, Value) -> {greaterOrEqual, av_assert(Desc, Value)}. +lessOrEqual(Desc, Value) -> {lessOrEqual, av_assert(Desc, Value)}. +approxMatch(Desc, Value) -> {approxMatch, av_assert(Desc, Value)}. + +av_assert(Desc, Value) -> + #'AttributeValueAssertion'{attributeDesc = Desc, + assertionValue = Value}. + +%%% +%%% Filter to check for the presence of an attribute +%%% +present(Attribute) when list(Attribute) -> + {present, Attribute}. + + +%%% +%%% A substring filter seem to be based on a pattern: +%%% +%%% InitValue*AnyValue*FinalValue +%%% +%%% where all three parts seem to be optional (at least when +%%% talking with an OpenLDAP server). Thus, the arguments +%%% to substrings/2 looks like this: +%%% +%%% Type ::= string( ) +%%% SubStr ::= listof( {initial,Value} | {any,Value}, {final,Value}) +%%% +%%% Example: substrings("sn",[{initial,"To"},{any,"kv"},{final,"st"}]) +%%% will match entries containing: 'sn: Tornkvist' +%%% +substrings(Type, SubStr) when list(Type), list(SubStr) -> + Ss = {'SubstringFilter_substrings',v_substr(SubStr)}, + {substrings,#'SubstringFilter'{type = Type, + substrings = Ss}}. + +%%% -------------------------------------------------------------------- +%%% Worker process. We keep track of a controlling process to +%%% be able to terminate together with it. +%%% -------------------------------------------------------------------- + +init(Hosts, Opts, Cpid) -> + Data = parse_args(Opts, Cpid, #eldap{}), + case try_connect(Hosts, Data) of + {ok,Data2} -> + send(Cpid, {ok,self()}), + put(req_timeout, Data#eldap.timeout), % kludge... + loop(Cpid, Data2); + Else -> + send(Cpid, Else), + unlink(Cpid), + exit(Else) + end. + +parse_args([{port, Port}|T], Cpid, Data) when integer(Port) -> + parse_args(T, Cpid, Data#eldap{port = Port}); +parse_args([{timeout, Timeout}|T], Cpid, Data) when integer(Timeout),Timeout>0 -> + parse_args(T, Cpid, Data#eldap{timeout = Timeout}); +parse_args([{anon_auth, true}|T], Cpid, Data) -> + parse_args(T, Cpid, Data#eldap{anon_auth = false}); +parse_args([{anon_auth, _}|T], Cpid, Data) -> + parse_args(T, Cpid, Data); +parse_args([{ssl, true}|T], Cpid, Data) -> + parse_args(T, Cpid, Data#eldap{use_tls = true}); +parse_args([{ssl, _}|T], Cpid, Data) -> + parse_args(T, Cpid, Data); +parse_args([{log, F}|T], Cpid, Data) when function(F) -> + parse_args(T, Cpid, Data#eldap{log = F}); +parse_args([{log, _}|T], Cpid, Data) -> + parse_args(T, Cpid, Data); +parse_args([H|_], Cpid, _) -> + send(Cpid, {error,{wrong_option,H}}), + exit(wrong_option); +parse_args([], _, Data) -> + Data. + +%%% Try to connect to the hosts in the listed order, +%%% and stop with the first one to which a successful +%%% connection is made. + +try_connect([Host|Hosts], Data) -> + TcpOpts = [{packet, asn1}, {active,false}], + case do_connect(Host, Data, TcpOpts) of + {ok,Fd} -> {ok,Data#eldap{host = Host, fd = Fd}}; + _ -> try_connect(Hosts, Data) + end; +try_connect([],_) -> + {error,"connect failed"}. + +do_connect(Host, Data, Opts) when Data#eldap.use_tls == false -> + gen_tcp:connect(Host, Data#eldap.port, Opts, Data#eldap.timeout); +do_connect(Host, Data, Opts) when Data#eldap.use_tls == true -> + ssl:connect(Host, Data#eldap.port, [{verify,0}|Opts]). + + +loop(Cpid, Data) -> + receive + + {From, {search, A}} -> + {Res,NewData} = do_search(Data, A), + send(From,Res), + loop(Cpid, NewData); + + {From, {modify, Obj, Mod}} -> + {Res,NewData} = do_modify(Data, Obj, Mod), + send(From,Res), + loop(Cpid, NewData); + + {From, {modify_dn, Obj, NewRDN, DelOldRDN, NewSup}} -> + {Res,NewData} = do_modify_dn(Data, Obj, NewRDN, DelOldRDN, NewSup), + send(From,Res), + loop(Cpid, NewData); + + {From, {add, Entry, Attrs}} -> + {Res,NewData} = do_add(Data, Entry, Attrs), + send(From,Res), + loop(Cpid, NewData); + + {From, {delete, Entry}} -> + {Res,NewData} = do_delete(Data, Entry), + send(From,Res), + loop(Cpid, NewData); + + {From, {simple_bind, Dn, Passwd}} -> + {Res,NewData} = do_simple_bind(Data, Dn, Passwd), + send(From,Res), + loop(Cpid, NewData); + + {From, {cnt_proc, NewCpid}} -> + unlink(Cpid), + send(From,ok), + ?PRINT("New Cpid is: ~p~n",[NewCpid]), + loop(NewCpid, Data); + + {From, close} -> + unlink(Cpid), + exit(closed); + + {Cpid, 'EXIT', Reason} -> + ?PRINT("Got EXIT from Cpid, reason=~p~n",[Reason]), + exit(Reason); + + _XX -> + ?PRINT("loop got: ~p~n",[_XX]), + loop(Cpid, Data) + + end. + +%%% -------------------------------------------------------------------- +%%% bindRequest +%%% -------------------------------------------------------------------- + +%%% Authenticate ourselves to the directory using +%%% simple authentication. + +do_simple_bind(Data, anon, anon) -> %% For testing + do_the_simple_bind(Data, "", ""); +do_simple_bind(Data, Dn, _Passwd) when Dn=="",Data#eldap.anon_auth==false -> + {{error,anonymous_auth},Data}; +do_simple_bind(Data, _Dn, Passwd) when Passwd=="",Data#eldap.anon_auth==false -> + {{error,anonymous_auth},Data}; +do_simple_bind(Data, Dn, Passwd) -> + do_the_simple_bind(Data, Dn, Passwd). + +do_the_simple_bind(Data, Dn, Passwd) -> + case catch exec_simple_bind(Data#eldap{binddn = Dn, + passwd = Passwd, + id = bump_id(Data)}) of + {ok,NewData} -> {ok,NewData}; + {error,Emsg} -> {{error,Emsg},Data}; + Else -> {{error,Else},Data} + end. + +exec_simple_bind(Data) -> + Req = #'BindRequest'{version = Data#eldap.version, + name = Data#eldap.binddn, + authentication = {simple, Data#eldap.passwd}}, + log2(Data, "bind request = ~p~n", [Req]), + Reply = request(Data#eldap.fd, Data, Data#eldap.id, {bindRequest, Req}), + log2(Data, "bind reply = ~p~n", [Reply]), + exec_simple_bind_reply(Data, Reply). + +exec_simple_bind_reply(Data, {ok,Msg}) when + Msg#'LDAPMessage'.messageID == Data#eldap.id -> + case Msg#'LDAPMessage'.protocolOp of + {bindResponse, Result} -> + case Result#'BindResponse'.resultCode of + success -> {ok,Data}; + Error -> {error, Error} + end; + Other -> {error, Other} + end; +exec_simple_bind_reply(_, Error) -> + {error, Error}. + + +%%% -------------------------------------------------------------------- +%%% searchRequest +%%% -------------------------------------------------------------------- + +do_search(Data, A) -> + case catch do_search_0(Data, A) of + {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data}; + {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data}; + {ok,Res,Ref,NewData} -> {{ok,polish(Res, Ref)},NewData}; + Else -> {ldap_closed_p(Data, Else),Data} + end. + +%%% +%%% Polish the returned search result +%%% + +polish(Res, Ref) -> + R = polish_result(Res), + %%% No special treatment of referrals at the moment. + #eldap_search_result{entries = R, + referrals = Ref}. + +polish_result([H|T]) when record(H, 'SearchResultEntry') -> + ObjectName = H#'SearchResultEntry'.objectName, + F = fun({_,A,V}) -> {A,V} end, + Attrs = lists:map(F, H#'SearchResultEntry'.attributes), + [#eldap_entry{object_name = ObjectName, + attributes = Attrs}| + polish_result(T)]; +polish_result([]) -> + []. + +do_search_0(Data, A) -> + Req = #'SearchRequest'{baseObject = A#eldap_search.base, + scope = v_scope(A#eldap_search.scope), + derefAliases = neverDerefAliases, + sizeLimit = 0, % no size limit + timeLimit = v_timeout(A#eldap_search.timeout), + typesOnly = v_bool(A#eldap_search.types_only), + filter = v_filter(A#eldap_search.filter), + attributes = v_attributes(A#eldap_search.attributes) + }, + Id = bump_id(Data), + collect_search_responses(Data#eldap{id=Id}, Req, Id). + +%%% The returned answers cames in one packet per entry +%%% mixed with possible referals + +collect_search_responses(Data, Req, ID) -> + S = Data#eldap.fd, + log2(Data, "search request = ~p~n", [Req]), + send_request(S, Data, ID, {searchRequest, Req}), + Resp = recv_response(S, Data), + log2(Data, "search reply = ~p~n", [Resp]), + collect_search_responses(Data, S, ID, Resp, [], []). + +collect_search_responses(Data, S, ID, {ok,Msg}, Acc, Ref) + when record(Msg,'LDAPMessage') -> + case Msg#'LDAPMessage'.protocolOp of + {'searchResDone',R} when R#'LDAPResult'.resultCode == success -> + log2(Data, "search reply = searchResDone ~n", []), + {ok,Acc,Ref,Data}; + {'searchResEntry',R} when record(R,'SearchResultEntry') -> + Resp = recv_response(S, Data), + log2(Data, "search reply = ~p~n", [Resp]), + collect_search_responses(Data, S, ID, Resp, [R|Acc], Ref); + {'searchResRef',R} -> + %% At the moment we don't do anyting sensible here since + %% I haven't been able to trigger the server to generate + %% a response like this. + Resp = recv_response(S, Data), + log2(Data, "search reply = ~p~n", [Resp]), + collect_search_responses(Data, S, ID, Resp, Acc, [R|Ref]); + Else -> + throw({error,Else}) + end; +collect_search_responses(_, _, _, Else, _, _) -> + throw({error,Else}). + +%%% -------------------------------------------------------------------- +%%% addRequest +%%% -------------------------------------------------------------------- + +do_add(Data, Entry, Attrs) -> + case catch do_add_0(Data, Entry, Attrs) of + {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data}; + {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data}; + {ok,NewData} -> {ok,NewData}; + Else -> {ldap_closed_p(Data, Else),Data} + end. + +do_add_0(Data, Entry, Attrs) -> + Req = #'AddRequest'{entry = Entry, + attributes = Attrs}, + S = Data#eldap.fd, + Id = bump_id(Data), + log2(Data, "add request = ~p~n", [Req]), + Resp = request(S, Data, Id, {addRequest, Req}), + log2(Data, "add reply = ~p~n", [Resp]), + check_reply(Data#eldap{id = Id}, Resp, addResponse). + + +%%% -------------------------------------------------------------------- +%%% deleteRequest +%%% -------------------------------------------------------------------- + +do_delete(Data, Entry) -> + case catch do_delete_0(Data, Entry) of + {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data}; + {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data}; + {ok,NewData} -> {ok,NewData}; + Else -> {ldap_closed_p(Data, Else),Data} + end. + +do_delete_0(Data, Entry) -> + S = Data#eldap.fd, + Id = bump_id(Data), + log2(Data, "del request = ~p~n", [Entry]), + Resp = request(S, Data, Id, {delRequest, Entry}), + log2(Data, "del reply = ~p~n", [Resp]), + check_reply(Data#eldap{id = Id}, Resp, delResponse). + + +%%% -------------------------------------------------------------------- +%%% modifyRequest +%%% -------------------------------------------------------------------- + +do_modify(Data, Obj, Mod) -> + case catch do_modify_0(Data, Obj, Mod) of + {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data}; + {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data}; + {ok,NewData} -> {ok,NewData}; + Else -> {ldap_closed_p(Data, Else),Data} + end. + +do_modify_0(Data, Obj, Mod) -> + v_modifications(Mod), + Req = #'ModifyRequest'{object = Obj, + modification = Mod}, + S = Data#eldap.fd, + Id = bump_id(Data), + log2(Data, "modify request = ~p~n", [Req]), + Resp = request(S, Data, Id, {modifyRequest, Req}), + log2(Data, "modify reply = ~p~n", [Resp]), + check_reply(Data#eldap{id = Id}, Resp, modifyResponse). + +%%% -------------------------------------------------------------------- +%%% modifyDNRequest +%%% -------------------------------------------------------------------- + +do_modify_dn(Data, Entry, NewRDN, DelOldRDN, NewSup) -> + case catch do_modify_dn_0(Data, Entry, NewRDN, DelOldRDN, NewSup) of + {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data}; + {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data}; + {ok,NewData} -> {ok,NewData}; + Else -> {ldap_closed_p(Data, Else),Data} + end. + +do_modify_dn_0(Data, Entry, NewRDN, DelOldRDN, NewSup) -> + Req = #'ModifyDNRequest'{entry = Entry, + newrdn = NewRDN, + deleteoldrdn = DelOldRDN, + newSuperior = NewSup}, + S = Data#eldap.fd, + Id = bump_id(Data), + log2(Data, "modify DN request = ~p~n", [Req]), + Resp = request(S, Data, Id, {modDNRequest, Req}), + log2(Data, "modify DN reply = ~p~n", [Resp]), + check_reply(Data#eldap{id = Id}, Resp, modDNResponse). + +%%% -------------------------------------------------------------------- +%%% Send an LDAP request and receive the answer +%%% -------------------------------------------------------------------- + +request(S, Data, ID, Request) -> + send_request(S, Data, ID, Request), + recv_response(S, Data). + +send_request(S, Data, ID, Request) -> + Message = #'LDAPMessage'{messageID = ID, + protocolOp = Request}, + {ok,Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message), + case do_send(S, Data, Bytes) of + {error,Reason} -> throw({gen_tcp_error,Reason}); + Else -> Else + end. + +do_send(S, Data, Bytes) when Data#eldap.use_tls == false -> + gen_tcp:send(S, Bytes); +do_send(S, Data, Bytes) when Data#eldap.use_tls == true -> + ssl:send(S, Bytes). + +do_recv(S, Data, Len, Timeout) when Data#eldap.use_tls == false -> + gen_tcp:recv(S, Len, Timeout); +do_recv(S, Data, Len, Timeout) when Data#eldap.use_tls == true -> + ssl:recv(S, Len, Timeout). + +recv_response(S, Data) -> + Timeout = get(req_timeout), % kludge... + case do_recv(S, Data, 0, Timeout) of + {ok, Packet} -> + check_tag(Packet), + case asn1rt:decode('ELDAPv3', 'LDAPMessage', Packet) of + {ok,Resp} -> {ok,Resp}; + Error -> throw(Error) + end; + {error,Reason} -> + throw({gen_tcp_error, Reason}); + Error -> + throw(Error) + end. + +%%% Sanity check of received packet +check_tag(Data) -> + case asn1rt_ber_bin:decode_tag(b2l(Data)) of + {_Tag, Data1, _Rb} -> + case asn1rt_ber_bin:decode_length(b2l(Data1)) of + {{_Len, _Data2}, _Rb2} -> ok; + _ -> throw({error,decoded_tag_length}) + end; + _ -> throw({error,decoded_tag}) + end. + +%%% Check for expected kind of reply +check_reply(Data, {ok,Msg}, Op) when + Msg#'LDAPMessage'.messageID == Data#eldap.id -> + case Msg#'LDAPMessage'.protocolOp of + {Op, Result} -> + case Result#'LDAPResult'.resultCode of + success -> {ok,Data}; + Error -> {error, Error} + end; + Other -> {error, Other} + end; +check_reply(_, Error, _) -> + {error, Error}. + + +%%% -------------------------------------------------------------------- +%%% Verify the input data +%%% -------------------------------------------------------------------- + +v_filter({'and',L}) -> {'and',L}; +v_filter({'or', L}) -> {'or',L}; +v_filter({'not',L}) -> {'not',L}; +v_filter({equalityMatch,AV}) -> {equalityMatch,AV}; +v_filter({greaterOrEqual,AV}) -> {greaterOrEqual,AV}; +v_filter({lessOrEqual,AV}) -> {lessOrEqual,AV}; +v_filter({approxMatch,AV}) -> {approxMatch,AV}; +v_filter({present,A}) -> {present,A}; +v_filter({substrings,S}) when record(S,'SubstringFilter') -> {substrings,S}; +v_filter(_Filter) -> throw({error,concat(["unknown filter: ",_Filter])}). + +v_modifications(Mods) -> + F = fun({_,Op,_}) -> + case lists:member(Op,[add,delete,replace]) of + true -> true; + _ -> throw({error,{mod_operation,Op}}) + end + end, + lists:foreach(F, Mods). + +v_substr([{Key,Str}|T]) when list(Str),Key==initial;Key==any;Key==final -> + [{Key,Str}|v_substr(T)]; +v_substr([H|_]) -> + throw({error,{substring_arg,H}}); +v_substr([]) -> + []. +v_scope(baseObject) -> baseObject; +v_scope(singleLevel) -> singleLevel; +v_scope(wholeSubtree) -> wholeSubtree; +v_scope(_Scope) -> throw({error,concat(["unknown scope: ",_Scope])}). + +v_bool(true) -> true; +v_bool(false) -> false; +v_bool(_Bool) -> throw({error,concat(["not Boolean: ",_Bool])}). + +v_timeout(I) when integer(I), I>=0 -> I; +v_timeout(_I) -> throw({error,concat(["timeout not positive integer: ",_I])}). + +v_attributes(Attrs) -> + F = fun(A) when list(A) -> A; + (A) -> throw({error,concat(["attribute not String: ",A])}) + end, + lists:map(F,Attrs). + + +%%% -------------------------------------------------------------------- +%%% Log routines. Call a user provided log routine F. +%%% -------------------------------------------------------------------- + +log1(Data, Str, Args) -> log(Data, Str, Args, 1). +log2(Data, Str, Args) -> log(Data, Str, Args, 2). + +log(Data, Str, Args, Level) when function(Data#eldap.log) -> + catch (Data#eldap.log)(Level, Str, Args); +log(_, _, _, _) -> + ok. + + +%%% -------------------------------------------------------------------- +%%% Misc. routines +%%% -------------------------------------------------------------------- + +send(To,Msg) -> To ! {self(),Msg}. +recv(From) -> receive {From,Msg} -> Msg end. + +ldap_closed_p(Data, Emsg) when Data#eldap.use_tls == true -> + %% Check if the SSL socket seems to be alive or not + case catch ssl:sockname(Data#eldap.fd) of + {error, _} -> + ssl:close(Data#eldap.fd), + {error, ldap_closed}; + {ok, _} -> + {error, Emsg}; + _ -> + %% sockname crashes if the socket pid is not alive + {error, ldap_closed} + end; +ldap_closed_p(Data, Emsg) -> + %% non-SSL socket + case inet:port(Data#eldap.fd) of + {error,_} -> {error, ldap_closed}; + _ -> {error,Emsg} + end. + +bump_id(Data) -> Data#eldap.id + 1. + + +%%% -------------------------------------------------------------------- +%%% parse_dn/1 - Implementation of RFC 2253: +%%% +%%% "UTF-8 String Representation of Distinguished Names" +%%% +%%% Test cases: +%%% +%%% The simplest case: +%%% +%%% 1> eldap:parse_dn("CN=Steve Kille,O=Isode Limited,C=GB"). +%%% {ok,[[{attribute_type_and_value,"CN","Steve Kille"}], +%%% [{attribute_type_and_value,"O","Isode Limited"}], +%%% [{attribute_type_and_value,"C","GB"}]]} +%%% +%%% The first RDN is multi-valued: +%%% +%%% 2> eldap:parse_dn("OU=Sales+CN=J. Smith,O=Widget Inc.,C=US"). +%%% {ok,[[{attribute_type_and_value,"OU","Sales"}, +%%% {attribute_type_and_value,"CN","J. Smith"}], +%%% [{attribute_type_and_value,"O","Widget Inc."}], +%%% [{attribute_type_and_value,"C","US"}]]} +%%% +%%% Quoting a comma: +%%% +%%% 3> eldap:parse_dn("CN=L. Eagle,O=Sue\\, Grabbit and Runn,C=GB"). +%%% {ok,[[{attribute_type_and_value,"CN","L. Eagle"}], +%%% [{attribute_type_and_value,"O","Sue\\, Grabbit and Runn"}], +%%% [{attribute_type_and_value,"C","GB"}]]} +%%% +%%% A value contains a carriage return: +%%% +%%% 4> eldap:parse_dn("CN=Before +%%% 4> After,O=Test,C=GB"). +%%% {ok,[[{attribute_type_and_value,"CN","Before\nAfter"}], +%%% [{attribute_type_and_value,"O","Test"}], +%%% [{attribute_type_and_value,"C","GB"}]]} +%%% +%%% 5> eldap:parse_dn("CN=Before\\0DAfter,O=Test,C=GB"). +%%% {ok,[[{attribute_type_and_value,"CN","Before\\0DAfter"}], +%%% [{attribute_type_and_value,"O","Test"}], +%%% [{attribute_type_and_value,"C","GB"}]]} +%%% +%%% An RDN in OID form: +%%% +%%% 6> eldap:parse_dn("1.3.6.1.4.1.1466.0=#04024869,O=Test,C=GB"). +%%% {ok,[[{attribute_type_and_value,"1.3.6.1.4.1.1466.0","#04024869"}], +%%% [{attribute_type_and_value,"O","Test"}], +%%% [{attribute_type_and_value,"C","GB"}]]} +%%% +%%% +%%% -------------------------------------------------------------------- + +parse_dn("") -> % empty DN string + {ok,[]}; +parse_dn([H|_] = Str) when H=/=$, -> % 1:st name-component ! + case catch parse_name(Str,[]) of + {'EXIT',Reason} -> {parse_error,internal_error,Reason}; + Else -> Else + end. + +parse_name("",Acc) -> + {ok,lists:reverse(Acc)}; +parse_name([$,|T],Acc) -> % N:th name-component ! + parse_name(T,Acc); +parse_name(Str,Acc) -> + {Rest,NameComponent} = parse_name_component(Str), + parse_name(Rest,[NameComponent|Acc]). + +parse_name_component(Str) -> + parse_name_component(Str,[]). + +parse_name_component(Str,Acc) -> + case parse_attribute_type_and_value(Str) of + {[$+|Rest], ATV} -> + parse_name_component(Rest,[ATV|Acc]); + {Rest,ATV} -> + {Rest,lists:reverse([ATV|Acc])} + end. + +parse_attribute_type_and_value(Str) -> + case parse_attribute_type(Str) of + {Rest,[]} -> + error(expecting_attribute_type,Str); + {Rest,Type} -> + Rest2 = parse_equal_sign(Rest), + {Rest3,Value} = parse_attribute_value(Rest2), + {Rest3,{attribute_type_and_value,Type,Value}} + end. + +-define(IS_ALPHA(X) , X>=$a,X=<$z;X>=$A,X=<$Z ). +-define(IS_DIGIT(X) , X>=$0,X=<$9 ). +-define(IS_SPECIAL(X) , X==$,;X==$=;X==$+;X==$<;X==$>;X==$#;X==$; ). +-define(IS_QUOTECHAR(X) , X=/=$\\,X=/=$" ). +-define(IS_STRINGCHAR(X) , + X=/=$,,X=/=$=,X=/=$+,X=/=$<,X=/=$>,X=/=$#,X=/=$;,?IS_QUOTECHAR(X) ). +-define(IS_HEXCHAR(X) , ?IS_DIGIT(X);X>=$a,X=<$f;X>=$A,X=<$F ). + +parse_attribute_type([H|T]) when ?IS_ALPHA(H) -> + %% NB: It must be an error in the RFC in the definition + %% of 'attributeType', should be: (ALPHA *keychar) + {Rest,KeyChars} = parse_keychars(T), + {Rest,[H|KeyChars]}; +parse_attribute_type([H|_] = Str) when ?IS_DIGIT(H) -> + parse_oid(Str); +parse_attribute_type(Str) -> + error(invalid_attribute_type,Str). + + + +%%% Is a hexstring ! +parse_attribute_value([$#,X,Y|T]) when ?IS_HEXCHAR(X),?IS_HEXCHAR(Y) -> + {Rest,HexString} = parse_hexstring(T), + {Rest,[$#,X,Y|HexString]}; +%%% Is a "quotation-sequence" ! +parse_attribute_value([$"|T]) -> + {Rest,Quotation} = parse_quotation(T), + {Rest,[$"|Quotation]}; +%%% Is a stringchar , pair or Empty ! +parse_attribute_value(Str) -> + parse_string(Str). + +parse_hexstring(Str) -> + parse_hexstring(Str,[]). + +parse_hexstring([X,Y|T],Acc) when ?IS_HEXCHAR(X),?IS_HEXCHAR(Y) -> + parse_hexstring(T,[Y,X|Acc]); +parse_hexstring(T,Acc) -> + {T,lists:reverse(Acc)}. + +parse_quotation([$"|T]) -> % an empty: "" is ok ! + {T,[$"]}; +parse_quotation(Str) -> + parse_quotation(Str,[]). + +%%% Parse to end of quotation +parse_quotation([$"|T],Acc) -> + {T,lists:reverse([$"|Acc])}; +parse_quotation([X|T],Acc) when ?IS_QUOTECHAR(X) -> + parse_quotation(T,[X|Acc]); +parse_quotation([$\\,X|T],Acc) when ?IS_SPECIAL(X) -> + parse_quotation(T,[X,$\\|Acc]); +parse_quotation([$\\,$\\|T],Acc) -> + parse_quotation(T,[$\\,$\\|Acc]); +parse_quotation([$\\,$"|T],Acc) -> + parse_quotation(T,[$",$\\|Acc]); +parse_quotation([$\\,X,Y|T],Acc) when ?IS_HEXCHAR(X),?IS_HEXCHAR(Y) -> + parse_quotation(T,[Y,X,$\\|Acc]); +parse_quotation(T,_) -> + error(expecting_double_quote_mark,T). + +parse_string(Str) -> + parse_string(Str,[]). + +parse_string("",Acc) -> + {"",lists:reverse(Acc)}; +parse_string([H|T],Acc) when ?IS_STRINGCHAR(H) -> + parse_string(T,[H|Acc]); +parse_string([$\\,X|T],Acc) when ?IS_SPECIAL(X) -> % is a pair ! + parse_string(T,[X,$\\|Acc]); +parse_string([$\\,$\\|T],Acc) -> % is a pair ! + parse_string(T,[$\\,$\\|Acc]); +parse_string([$\\,$" |T],Acc) -> % is a pair ! + parse_string(T,[$" ,$\\|Acc]); +parse_string([$\\,X,Y|T],Acc) when ?IS_HEXCHAR(X),?IS_HEXCHAR(Y) -> % is a pair! + parse_string(T,[Y,X,$\\|Acc]); +parse_string(T,Acc) -> + {T,lists:reverse(Acc)}. + +parse_equal_sign([$=|T]) -> T; +parse_equal_sign(T) -> error(expecting_equal_sign,T). + +parse_keychars(Str) -> parse_keychars(Str,[]). + +parse_keychars([H|T],Acc) when ?IS_ALPHA(H) -> parse_keychars(T,[H|Acc]); +parse_keychars([H|T],Acc) when ?IS_DIGIT(H) -> parse_keychars(T,[H|Acc]); +parse_keychars([$-|T],Acc) -> parse_keychars(T,[$-|Acc]); +parse_keychars(T,Acc) -> {T,lists:reverse(Acc)}. + +parse_oid(Str) -> parse_oid(Str,[]). + +parse_oid([H,$.|T], Acc) when ?IS_DIGIT(H) -> + parse_oid(T,[$.,H|Acc]); +parse_oid([H|T], Acc) when ?IS_DIGIT(H) -> + parse_oid(T,[H|Acc]); +parse_oid(T, Acc) -> + {T,lists:reverse(Acc)}. + +error(Emsg,Rest) -> + throw({parse_error,Emsg,Rest}). + + +%%% -------------------------------------------------------------------- +%%% Parse LDAP url according to RFC 2255 +%%% +%%% Test case: +%%% +%%% 2> eldap:parse_ldap_url("ldap://10.42.126.33:389/cn=Administrative%20CA,o=Post%20Danmark,c=DK?certificateRevokationList;binary"). +%%% {ok,{{10,42,126,33},389}, +%%% [[{attribute_type_and_value,"cn","Administrative%20CA"}], +%%% [{attribute_type_and_value,"o","Post%20Danmark"}], +%%% [{attribute_type_and_value,"c","DK"}]], +%%% {attributes,["certificateRevokationList;binary"]}} +%%% +%%% -------------------------------------------------------------------- + +parse_ldap_url("ldap://" ++ Rest1 = Str) -> + {Rest2,HostPort} = parse_hostport(Rest1), + %% Split the string into DN and Attributes+etc + {Sdn,Rest3} = split_string(rm_leading_slash(Rest2),$?), + case parse_dn(Sdn) of + {parse_error,internal_error,_Reason} -> + {parse_error,internal_error,{Str,[]}}; + {parse_error,Emsg,Tail} -> + Head = get_head(Str,Tail), + {parse_error,Emsg,{Head,Tail}}; + {ok,DN} -> + %% We stop parsing here for now and leave + %% 'scope', 'filter' and 'extensions' to + %% be implemented later if needed. + {_Rest4,Attributes} = parse_attributes(Rest3), + {ok,HostPort,DN,Attributes} + end. + +rm_leading_slash([$/|Tail]) -> Tail; +rm_leading_slash(Tail) -> Tail. + +parse_attributes([$?|Tail]) -> + case split_string(Tail,$?) of + {[],Attributes} -> + {[],{attributes,string:tokens(Attributes,",")}}; + {Attributes,Rest} -> + {Rest,{attributes,string:tokens(Attributes,",")}} + end. + +parse_hostport(Str) -> + {HostPort,Rest} = split_string(Str,$/), + case split_string(HostPort,$:) of + {Shost,[]} -> + {Rest,{parse_host(Rest,Shost),?LDAP_PORT}}; + {Shost,[$:|Sport]} -> + {Rest,{parse_host(Rest,Shost), + parse_port(Rest,Sport)}} + end. + +parse_port(Rest,Sport) -> + case list_to_integer(Sport) of + Port when integer(Port) -> Port; + _ -> error(parsing_port,Rest) + end. + +parse_host(Rest,Shost) -> + case catch validate_host(Shost) of + {parse_error,Emsg,_} -> error(Emsg,Rest); + Host -> Host + end. + +validate_host(Shost) -> + case inet_parse:address(Shost) of + {ok,Host} -> Host; + _ -> + case inet_parse:domain(Shost) of + true -> Shost; + _ -> error(parsing_host,Shost) + end + end. + + +split_string(Str,Key) -> + Pred = fun(X) when X==Key -> false; (_) -> true end, + lists:splitwith(Pred, Str). + +get_head(Str,Tail) -> + get_head(Str,Tail,[]). + +%%% Should always succeed ! +get_head([H|Tail],Tail,Rhead) -> lists:reverse([H|Rhead]); +get_head([H|Rest],Tail,Rhead) -> get_head(Rest,Tail,[H|Rhead]). + +b2l(B) when binary(B) -> B; +b2l(L) when list(L) -> list_to_binary(L). + diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/README.test b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/README.test new file mode 100644 index 0000000..9816216 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/README.test @@ -0,0 +1,96 @@ +%%% $Id$ + +%%% -------------------------------------------------------------------- +%%% Init setup +%%% -------------------------------------------------------------------- + +I set up the OpenLDAP (2.0.6) server using the following +/usr/local/etc/openldap/slapd.conf file: + + include /usr/local/etc/openldap/schema/core.schema + pidfile /var/run/slapd.pid + argsfile /var/run/slapd.args + database ldbm + suffix "dc=bluetail, dc=com" + rootdn "dc=bluetail, dc=com" + rootpw hejsan + directory /usr/local/var/openldap-ldbm + index objectClass eq + + +%%% I started it on the console with some debug output: + + /usr/local/libexec/slapd -d 255 -f /usr/local/etc/openldap/slapd.conf + +%%% Then I defined the following data in: bluetail.ldif + + dn: dc=bluetail, dc=com + objectclass: organization + objectclass: dcObject + dc: bluetail + o: Bluetail AB + +%%% and in: tobbe.ldif + + dn: cn=Torbjorn Tornkvist, dc=bluetail, dc=com + objectclass: person + cn: Torbjorn Tornkvist + sn: Tornkvist + +%%% I load the data with: + + ldapadd -D "dc=bluetail, dc=com" -w hejsan < bluetail.ldif + ldapadd -D "dc=bluetail, dc=com" -w hejsan < people.ldif + +%%%% To search from a Unix shell: + + ldapsearch -L -b "dc=bluetail, dc=com" -w hejsan "(objectclass=*)" + ldapsearch -L -b "dc=bluetail, dc=com" -w hejsan "cn=Torbjorn Tornkvist" + ldapsearch -L -b "dc=bluetail, dc=com" -w hejsan "cn=Torb*kvist" + +%%% -------------------------------------------------------------------- +%%% Example with certificateRevocationList +%%% -------------------------------------------------------------------- + +%%% Using two ldif files: + +%%% post_danmark.ldif + +dn: o=Post Danmark, c=DK +objectclass: country +objectclass: organization +c: DK +o: Post Danmark + +%%% crl.ldif + +dn: cn=Administrative CA, o=Post Danmark, c=DK +objectclass: cRLDistributionPoint +cn: Administrative CA +certificateRevocationList;binary:< file:/home/tobbe/erlang/eldap/server1.crl + +%%% Note the definition of the CRL file !! + +%%% To add the difinitions + +ldapadd -D "o=Post Danmark, c=DK" -w hejsan < post_danmark.ldif +ldapadd -D "o=Post Danmark, c=DK" -w hejsan < crl.ldif + +%%% And to retreive the CRL + +ldapsearch -L -b "o=Post Danmark, c=DK" -w hejsan "(objectclass=*)" +ldapsearch -L -b "o=Post Danmark, c=DK" -w hejsan "(cn=Administrative CA)" \ + certificateRevocationList + +### Put the retrieved binary in a file (tmp) with +### the following header and footer + +-----BEGIN X509 CRL----- + <...binary....> +-----END X509 CRL----- + +### To verify it with openssl + + openssl crl -inform PEM -in tmp -text + +ldapsearch -L -D "cn=Torbjorn Tornkvist,o=Post Danmark,c=DK" -b "o=Post Danmark, c=DK" -w qwe123 "(cn=Torbjorn Tornkvist)" cn diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/bill.ldif b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/bill.ldif new file mode 100644 index 0000000..59022ad --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/bill.ldif @@ -0,0 +1,13 @@ +dn: mail=bill@bluetail.com, dc=bluetail, dc=com +objectclass: posixAccount +mail: bill@bluetail.com +cn: Bill Valentine +sn: Valentine +uid: bill +uidNumber: 400 +gidNumber: 400 +homeDirectory: /home/accounts/bill +mailDirectory: /home/accounts/bill/INBOX +userPassword: baltazar +birMailAccept: accept +birCluster: bc1 diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/bluetail.ldif b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/bluetail.ldif new file mode 100644 index 0000000..914532e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/bluetail.ldif @@ -0,0 +1,18 @@ +dn: dc=bluetail, dc=com +objectclass: dcObject +dc: bluetail + +dn: o=Bluetail AB, dc=bluetail, dc=com +objectclass: organization +o: Bluetail AB +street: St.Eriksgatan 44 +postalCode: 112 34 + +dn: ou=people, o=Bluetail AB, dc=bluetail, dc=com +objectclass: organizationalUnit +ou: people +description: People working at Bluetail + + + + diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/crl.ldif b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/crl.ldif new file mode 100644 index 0000000..2e52873 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/crl.ldif @@ -0,0 +1,5 @@ +dn: cn=Administrative CA,o=Post Danmark,c=DK +objectclass: cRLDistributionPoint +cn: Administrative CA +certificateRevocationList;binary:< file:/home/tobbe/erlang/eldap/server1.crl + diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/eldap_test.erl b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/eldap_test.erl new file mode 100644 index 0000000..db64615 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/eldap_test.erl @@ -0,0 +1,537 @@ +-module(eldap_test). +%%% -------------------------------------------------------------------- +%%% Created: 12 Oct 2000 by Tobbe +%%% Function: Test code for the eldap module +%%% +%%% Copyright (C) 2000 Torbjörn Törnkvist +%%% Copyright (c) 2010 Torbjorn Tornkvist +%%% See MIT-LICENSE at the top dir for licensing information. +%%% +%%% -------------------------------------------------------------------- +-vc('$Id$ '). +-export([topen_bind/1,topen_bind/2,all/0,t10/0,t20/0,t21/0,t22/0, + t23/0,t24/0,t25/0,t26/0,t27/0,debug/1,t30/0,t31/0, + t40/0,t41/0,t50/0,t51/0]). +-export([crl1/0]). +-export([switch/1]). +-export([junk/0]). + +-include("ELDAPv3.hrl"). +-include("eldap.hrl"). + +junk() -> + DN = "cn=Torbjorn Tornkvist, ou=people, o=Bluetail AB, dc=bluetail, dc=com", + Msg = #'LDAPMessage'{messageID = 1, + protocolOp = {delRequest,DN}}, + asn1rt:encode('ELDAPv3', 'LDAPMessage', Msg). + +%%% -------------------------------------------------------------------- +%%% TEST STUFF +%%% ---------- +%%% When adding a new test case it can be useful to +%%% switch on debugging, i.e debug(t) in the call to +%%% topen_bind/2. +%%% -------------------------------------------------------------------- + +all() -> + Check = "=== Check the result of the previous test case !~n", + t10(), + t20(),t21(),t22(),t23(),t24(),t25(),t26(),t27(), + t30(),t26(Check),t31(),t26(Check), + t40(),t26(Check),t41(),t26(Check), + t50(),t26(Check),t51(),t26(Check), + ok. + +%%% +%%% Setup a connection and bind using simple authentication +%%% +t10() -> + F = fun() -> + sleep(), + line(), + io:format("=== TEST 10 (connection setup + simple auth)~n"), + line(), + X = topen_bind("localhost", debug(f)), + io:format("~p~n",[X]), + X + end, + go(F). + +%%% +%%% Do an equality match: sn = Tornkvist +%%% +t20() -> + F = fun() -> + sleep(), + line(), + io:format("=== TEST 20 (equality match)~n"), + line(), + {ok,S} = topen_bind("localhost", debug(f)), + Filter = eldap:equalityMatch("sn","Tornkvist"), + X=(catch eldap:search(S, [{base, "dc=bluetail, dc=com"}, + {filter, Filter}])), + + io:format("~p~n",[X]), + X + end, + go(F). + +%%% +%%% Do a substring match: sn = To*kv*st +%%% +t21() -> + F = fun() -> + sleep(), + line(), + io:format("=== TEST 21 (substring match)~n"), + line(), + {ok,S} = topen_bind("localhost", debug(f)), + Filter = eldap:substrings("sn", [{initial,"To"}, + {any,"kv"}, + {final,"st"}]), + X=(catch eldap:search(S, [{base, "dc=bluetail, dc=com"}, + {filter, Filter}])), + io:format("~p~n",[X]), + X + end, + go(F). + +%%% +%%% Do a substring match: sn = *o* +%%% and do only retrieve the cn attribute +%%% +t22() -> + F = fun() -> + sleep(), + line(), + io:format("=== TEST 22 (substring match + return 'cn' only)~n"), + line(), + {ok,S} = topen_bind("localhost", debug(f)), + Filter = eldap:substrings("sn", [{any,"o"}]), + X=(catch eldap:search(S, [{base, "dc=bluetail, dc=com"}, + {filter, Filter}, + {attributes,["cn"]}])), + io:format("~p~n",[X]), + X + end, + go(F). + + +%%% +%%% Do a present search for the attribute 'objectclass' +%%% on the base level. +%%% +t23() -> + F = fun() -> + sleep(), + line(), + io:format("=== TEST 23 (objectclass=* , base level)~n"), + line(), + {ok,S} = topen_bind("localhost", debug(f)), + X=(catch eldap:search(S, [{base, "dc=bluetail, dc=com"}, + {filter, eldap:present("objectclass")}, + {scope,eldap:baseObject()}])), + io:format("~p~n",[X]), + X + end, + go(F). + +%%% +%%% Do a present search for the attribute 'objectclass' +%%% on a single level. +%%% +t24() -> + F = fun() -> + sleep(), + line(), + io:format("=== TEST 24 (objectclass=* , single level)~n"), + line(), + {ok,S} = topen_bind("localhost", debug(f)), + X=(catch eldap:search(S, [{base, "dc=bluetail, dc=com"}, + {filter, eldap:present("objectclass")}, + {scope,eldap:singleLevel()}])), + io:format("~p~n",[X]), + X + end, + go(F). + +%%% +%%% Do a present search for the attribute 'objectclass' +%%% on the whole subtree. +%%% +t25() -> + F = fun() -> + sleep(), + line(), + io:format("=== TEST 25 (objectclass=* , whole subtree)~n"), + line(), + {ok,S} = topen_bind("localhost", debug(f)), + X=(catch eldap:search(S, [{base, "dc=bluetail, dc=com"}, + {filter, eldap:present("objectclass")}, + {scope,eldap:wholeSubtree()}])), + io:format("~p~n",[X]), + X + end, + go(F). + +%%% +%%% Do a present search for the attributes +%%% 'objectclass' and 'sn' on the whole subtree. +%%% +t26() -> t26([]). +t26(Heading) -> + F = fun() -> + sleep(), + line(), + heading(Heading, + "=== TEST 26 (objectclass=* and sn=*)~n"), + line(), + {ok,S} = topen_bind("localhost", debug(f)), + Filter = eldap:'and'([eldap:present("objectclass"), + eldap:present("sn")]), + X=(catch eldap:search(S, [{base, "dc=bluetail, dc=com"}, + {filter, Filter}, + {scope,eldap:wholeSubtree()}])), + io:format("~p~n",[X]), + X + end, + go(F). + +%%% +%%% Do a present search for the attributes +%%% 'objectclass' and (not 'sn') on the whole subtree. +%%% +t27() -> + F = fun() -> + sleep(), + line(), + io:format("=== TEST 27 (objectclass=* and (not sn))~n"), + line(), + {ok,S} = topen_bind("localhost", debug(f)), + Filter = eldap:'and'([eldap:present("objectclass"), + eldap:'not'(eldap:present("sn"))]), + X=(catch eldap:search(S, [{base, "dc=bluetail, dc=com"}, + {filter, Filter}, + {scope,eldap:wholeSubtree()}])), + io:format("~p~n",[X]), + X + end, + go(F). + +%%% +%%% Replace the 'telephoneNumber' attribute and +%%% add a new attribute 'description' +%%% +t30() -> t30([]). +t30(Heading) -> + F = fun() -> + sleep(), + {_,_,Tno} = erlang:now(), + Stno = integer_to_list(Tno), + Desc = "LDAP hacker " ++ Stno, + line(), + heading(Heading, + "=== TEST 30 (replace telephoneNumber/" + ++ Stno ++ " add description/" ++ Desc + ++ ")~n"), + line(), + {ok,S} = topen_bind("localhost", debug(f)), + Obj = "cn=Torbjorn Tornkvist, ou=people, o=Bluetail AB, dc=bluetail, dc=com", + Mod = [eldap:mod_replace("telephoneNumber", [Stno]), + eldap:mod_add("description", [Desc])], + X=(catch eldap:modify(S, Obj, Mod)), + io:format("~p~n",[X]), + X + end, + go(F). + +%%% +%%% Delete attribute 'description' +%%% +t31() -> t31([]). +t31(Heading) -> + F = fun() -> + sleep(), + {_,_,Tno} = erlang:now(), + line(), + heading(Heading, + "=== TEST 31 (delete 'description' attribute)~n"), + line(), + {ok,S} = topen_bind("localhost", debug(f)), + Obj = "cn=Torbjorn Tornkvist, ou=people, o=Bluetail AB, dc=bluetail, dc=com", + Mod = [eldap:mod_delete("description", [])], + X=(catch eldap:modify(S, Obj, Mod)), + io:format("~p~n",[X]), + X + end, + go(F). + +%%% +%%% Add an entry +%%% +t40() -> t40([]). +t40(Heading) -> + F = fun() -> + sleep(), + {_,_,Tno} = erlang:now(), + line(), + heading(Heading, + "=== TEST 40 (add entry 'Bill Valentine')~n"), + line(), + {ok,S} = topen_bind("localhost", debug(f)), + Entry = "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com", + X=(catch eldap:add(S, Entry, + [{"objectclass", ["person"]}, + {"cn", ["Bill Valentine"]}, + {"sn", ["Valentine"]}, + {"telephoneNumber", ["545 555 00"]}])), + io:format("~p~n",[X]), + X + end, + go(F). + +%%% +%%% Delete an entry +%%% +t41() -> t41([]). +t41(Heading) -> + F = fun() -> + sleep(), + {_,_,Tno} = erlang:now(), + line(), + heading(Heading, + "=== TEST 41 (delete entry 'Bill Valentine')~n"), + line(), + {ok,S} = topen_bind("localhost", debug(f)), + Entry = "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com", + X=(catch eldap:delete(S, Entry)), + io:format("~p~n",[X]), + X + end, + go(F). + +%%% +%%% Modify the DN of an entry +%%% +t50() -> t50([]). +t50(Heading) -> + F = fun() -> + sleep(), + {_,_,Tno} = erlang:now(), + line(), + heading(Heading, + "=== TEST 50 (modify DN to: 'Torbjorn M.Tornkvist')~n"), + line(), + {ok,S} = topen_bind("localhost", debug(f)), + Entry = "cn=Torbjorn Tornkvist, ou=people, o=Bluetail AB, dc=bluetail, dc=com", + X=(catch eldap:modify_dn(S, Entry, + "cn=Torbjorn M.Tornkvist", + false, + [])), + io:format("~p~n",[X]), + X + end, + go(F). + +%%% +%%% Modify the DN of an entry and remove the RDN attribute. +%%% NB: Must be run after: 't50' ! +%%% +t51() -> t51([]). +t51(Heading) -> + F = fun() -> + sleep(), + {_,_,Tno} = erlang:now(), + line(), + heading(Heading, + "=== TEST 51 (modify DN, remove the RDN attribute)~n"), + line(), + {ok,S} = topen_bind("localhost", debug(f)), + Entry = "cn=Torbjorn M.Tornkvist, ou=people, o=Bluetail AB, dc=bluetail, dc=com", + X=(catch eldap:modify_dn(S, Entry, + "cn=Torbjorn Tornkvist", + true, + [])), + io:format("~p~n",[X]), + X + end, + go(F). + +%%% -------------------------------------------------------------------- +%%% Test cases for certificate revocation lists +%%% -------------------------------------------------------------------- + +crl1() -> + F = fun() -> + sleep(), + line(), + io:format("=== CRL-TEST 1 ~n"), + line(), + {ok,S} = crl_open_bind("localhost", debug(f)), + Filter = eldap:equalityMatch("cn","Administrative CA"), + X=(catch eldap:search(S, [{base, "o=Post Danmark, c=DK"}, + {filter, Filter}, + {attributes,["certificateRevocationList"]}])), + dump_to_file("test-crl1.result",X), + ok + end, + go(F). + + +dump_to_file(Fname,{ok,Res}) -> + case Res#eldap_search_result.entries of + [Entry|_] -> + case Entry#eldap_entry.attributes of + [{Attribute,Value}|_] -> + file:write_file(Fname,list_to_binary(Value)), + io:format("Value of '~s' dumped to file: ~s~n", + [Attribute,Fname]); + Else -> + io:format("ERROR(dump_to_file): no attributes found~n",[]) + end; + Else -> + io:format("ERROR(dump_to_file): no entries found~n",[]) + end. + +switch(1) -> + %% + %% SEARCH + %% + F = fun() -> + sleep(), + line(), + io:format("=== SWITCH-TEST 1 (short-search)~n"), + line(), + {ok,S} = sw_open_bind("korp", debug(t)), + Filter = eldap:equalityMatch("cn","Administrative CA"), + X=(catch eldap:search(S, [{base, "o=Post Danmark, c=DK"}, + {filter, Filter}, + {attributes,["cn"]}])), + io:format("RESULT: ~p~n", [X]), + %%dump_to_file("test-switch-1.result",X), + eldap:close(S), + ok + end, + go(F); +switch(2) -> + %% + %% ADD AN ENTRY + %% + F = fun() -> + sleep(), + line(), + io:format("=== SWITCH-TEST 2 (add-entry)~n"), + line(), + {ok,S} = sw_open_bind("korp", debug(t)), + Entry = "cn=Bill Valentine, o=Post Danmark, c=DK", + X=(catch eldap:add(S, Entry, + [{"objectclass", ["person"]}, + {"cn", ["Bill Valentine"]}, + {"sn", ["Valentine"]} + ])), + io:format("~p~n",[X]), + eldap:close(S), + X + end, + go(F); +switch(3) -> + %% + %% SEARCH FOR THE NEWLEY ADDED ENTRY + %% + F = fun() -> + sleep(), + line(), + io:format("=== SWITCH-TEST 3 (search-added)~n"), + line(), + {ok,S} = sw_open_bind("korp", debug(t)), + Filter = eldap:equalityMatch("cn","Bill Valentine"), + X=(catch eldap:search(S, [{base, "o=Post Danmark, c=DK"}, + {filter, Filter}, + {attributes,["cn"]}])), + io:format("RESULT: ~p~n", [X]), + %%dump_to_file("test-switch-1.result",X), + eldap:close(S), + ok + end, + go(F); +switch(4) -> + %% + %% DELETE THE NEWLEY ADDED ENTRY + %% + F = fun() -> + sleep(), + line(), + io:format("=== SWITCH-TEST 4 (delete-added)~n"), + line(), + {ok,S} = sw_open_bind("korp", debug(t)), + Entry = "cn=Bill Valentine, o=Post Danmark, c=DK", + X=(catch eldap:delete(S, Entry)), + io:format("RESULT: ~p~n", [X]), + %%dump_to_file("test-switch-1.result",X), + eldap:close(S), + ok + end, + go(F). + + + +%%% --------------- +%%% Misc. functions +%%% --------------- + +sw_open_bind(Host) -> + sw_open_bind(Host, debug(t)). + +sw_open_bind(Host, Dbg) -> + sw_open_bind(Host, Dbg, "cn=Torbjorn Tornkvist,o=Post Danmark,c=DK", "qwe123"). + +sw_open_bind(Host, LogFun, RootDN, Passwd) -> + Opts = [{log,LogFun},{port,9779}], + {ok,Handle} = eldap:open([Host], Opts), + {eldap:simple_bind(Handle, RootDN, Passwd), + Handle}. + +crl_open_bind(Host) -> + crl_open_bind(Host, debug(t)). + +crl_open_bind(Host, Dbg) -> + do_open_bind(Host, Dbg, "o=Post Danmark, c=DK", "hejsan"). + +topen_bind(Host) -> + topen_bind(Host, debug(t)). + +topen_bind(Host, Dbg) -> + do_open_bind(Host, Dbg, "dc=bluetail, dc=com", "hejsan"). + +do_open_bind(Host, LogFun, RootDN, Passwd) -> + Opts = [{log,LogFun}], + {ok,Handle} = eldap:open([Host], Opts), + {eldap:simple_bind(Handle, RootDN, Passwd), + Handle}. + +debug(t) -> fun(L,S,A) -> io:format("--- " ++ S, A) end; +debug(1) -> fun(L,S,A) when L =< 1 -> io:format("--- " ++ S, A) end; +debug(2) -> fun(L,S,A) when L =< 2 -> io:format("--- " ++ S, A) end; +debug(f) -> false. + +sleep() -> msleep(400). +%sleep(Sec) -> msleep(Sec*1000). +msleep(T) -> receive after T -> true end. + +line() -> + S = "==============================================================\n", + io:format(S). + +heading([], Heading) -> io:format(Heading); +heading(Heading, _ ) -> io:format(Heading). + +%%% +%%% Process to run the test case +%%% +go(F) -> + Self = self(), + Pid = spawn(fun() -> run(F,Self) end), + receive {Pid, X} -> ok end. + +run(F, Pid) -> + Pid ! {self(),catch F()}. diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/ldap.rc b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/ldap.rc new file mode 100644 index 0000000..6cbdfea --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/ldap.rc @@ -0,0 +1,103 @@ +#!/bin/sh +# +# ldap This shell script takes care of starting and stopping +# ldap servers (slapd and slurpd). +# +# chkconfig: - 39 61 +# description: LDAP stands for Lightweight Directory Access Protocol, used \ +# for implementing the industry standard directory services. +# processname: slapd +# config: /etc/openldap/slapd.conf +# pidfile: /var/run/slapd.pid + +# Source function library. +. /etc/init.d/functions + +# Source networking configuration and check that networking is up. +if [ -r /etc/sysconfig/network ] ; then + . /etc/sysconfig/network + [ ${NETWORKING} = "no" ] && exit 0 +fi + + +slapd=/usr/sbin/slapd +slurpd=/usr/sbin/slurpd +[ -x ${slapd} ] || exit 0 +[ -x ${slurpd} ] || exit 0 + +RETVAL=0 + +function start() { + # Start daemons. + echo -n "Starting slapd:" + daemon ${slapd} + RETVAL=$? + echo + if [ $RETVAL -eq 0 ]; then + if grep -q "^replogfile" /etc/openldap/slapd.conf; then + echo -n "Starting slurpd:" + daemon ${slurpd} + RETVAL=$? + echo + fi + fi + [ $RETVAL -eq 0 ] && touch /var/lock/subsys/ldap + return $RETVAL +} + +function stop() { + # Stop daemons. + echo -n "Shutting down ldap: " + killproc ${slapd} + RETVAL=$? + if [ $RETVAL -eq 0 ]; then + if grep -q "^replogfile" /etc/openldap/slapd.conf; then + killproc ${slurpd} + RETVAL=$? + fi + fi + echo + [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/ldap /var/run/slapd.args + return $RETVAL +} + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; + status) + status ${slapd} + if grep -q "^replogfile" /etc/openldap/slapd.conf ; then + status ${slurpd} + fi + ;; + restart) + stop + start + ;; + reload) + killall -HUP ${slapd} + RETVAL=$? + if [ $RETVAL -eq 0 ]; then + if grep -q "^replogfile" /etc/openldap/slapd.conf; then + killall -HUP ${slurpd} + RETVAL=$? + fi + fi + ;; + condrestart) + if [ -f /var/lock/subsys/ldap ] ; then + stop + start + fi + ;; + *) + echo "Usage: $0 start|stop|restart|status|condrestart}" + RETVAL=1 +esac + +exit $RETVAL diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/people.ldif b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/people.ldif new file mode 100644 index 0000000..20af5a0 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/people.ldif @@ -0,0 +1,11 @@ +dn: cn=Torbjorn Tornkvist, ou=people, o=Bluetail AB, dc=bluetail, dc=com +objectclass: person +cn: Torbjorn Tornkvist +sn: Tornkvist +telephoneNumber: 545 550 23 + +dn: cn=Magnus Froberg, ou=people, o=Bluetail AB, dc=bluetail, dc=com +objectclass: person +cn: Magnus Froberg +sn: Froberg +telephoneNumber: 545 550 26 diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/post_danmark.ldif b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/post_danmark.ldif new file mode 100644 index 0000000..24fbb3f --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/post_danmark.ldif @@ -0,0 +1,5 @@ +dn: o=Post Danmark,c=DK +objectclass: country +objectclass: organization +c: DK +o: Post Danmark diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/server1.crl b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/eldap-git/test/server1.crl new file mode 100644 index 0000000000000000000000000000000000000000..6be714ad40275d7003f4c4cbbb8a08c642f062c7 GIT binary patch literal 47075 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 W@y$ + gen_tcp:connect(Host, Data#eldap.port, Opts, Data#eldap.timeout); + do_connect(Host, Data, Opts) when Data#eldap.use_tls == true -> +- Vsn = erlang:system_info(version), +- if Vsn >= "5.3" -> +- %% In R9C, but not in R9B +- {_,_,X} = erlang:now(), +- ssl:seed("bkrlnateqqo" ++ integer_to_list(X)); +- true -> true +- end, + ssl:connect(Host, Data#eldap.port, [{verify,0}|Opts]). + + diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/hash.mk b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/hash.mk new file mode 100644 index 0000000..262b7cc --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/hash.mk @@ -0,0 +1 @@ +UPSTREAM_SHORT_HASH:=e309de4 diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/license_info b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/license_info new file mode 100644 index 0000000..0a0e13c --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/license_info @@ -0,0 +1,3 @@ +Eldap is "Copyright (c) 2010, Torbjorn Tornkvist" and is covered by +the MIT license. It was downloaded from https://github.com/etnt/eldap + diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/package.mk b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/package.mk new file mode 100644 index 0000000..02c8b4e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/package.mk @@ -0,0 +1,30 @@ +APP_NAME:=eldap + +UPSTREAM_GIT:=https://github.com/rabbitmq/eldap.git +UPSTREAM_REVISION:=e309de4db4b78d67d623 +WRAPPER_PATCHES:=eldap-appify.patch remove-eldap-fsm.patch eldap-no-ssl-seed.patch remove-ietf-doc.patch + +ORIGINAL_APP_FILE:=$(CLONE_DIR)/ebin/$(APP_NAME).app +DO_NOT_GENERATE_APP_FILE=true + +GENERATED_DIR:=$(CLONE_DIR)/generated +PACKAGE_ERLC_OPTS+=-I $(GENERATED_DIR) +INCLUDE_HRLS+=$(GENERATED_DIR)/ELDAPv3.hrl +EBIN_BEAMS+=$(GENERATED_DIR)/ELDAPv3.beam + +define package_rules + +$(CLONE_DIR)/src/ELDAPv3.asn: $(CLONE_DIR)/.done + +$(GENERATED_DIR)/ELDAPv3.hrl $(GENERATED_DIR)/ELDAPv3.beam: $(CLONE_DIR)/src/ELDAPv3.asn + @mkdir -p $(GENERATED_DIR) + $(ERLC) $(PACKAGE_ERLC_OPTS) -o $(GENERATED_DIR) $$< + +$(PACKAGE_DIR)+clean:: + rm -rf $(GENERATED_DIR) $(EBIN_DIR) + +# This rule is run *before* the one in do_package.mk +$(PLUGINS_SRC_DIST_DIR)/$(PACKAGE_DIR)/.srcdist_done:: + cp $(CLONE_DIR)/LICENSE $(PACKAGE_DIR)/LICENSE-MIT-eldap + +endef diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/remove-eldap-fsm.patch b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/remove-eldap-fsm.patch new file mode 100644 index 0000000..f6b05f6 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/remove-eldap-fsm.patch @@ -0,0 +1,952 @@ +diff --git a/src/eldap_fsm.erl b/src/eldap_fsm.erl +deleted file mode 100644 +index 381ce69..0000000 +--- a/src/eldap_fsm.erl ++++ /dev/null +@@ -1,946 +0,0 @@ +--module(eldap_fsm). +-%%% -------------------------------------------------------------------- +-%%% Created: 12 Oct 2000 by Tobbe +-%%% Function: Erlang client LDAP implementation according RFC 2251. +-%%% The interface is based on RFC 1823, and +-%%% draft-ietf-asid-ldap-c-api-00.txt +-%%% +-%%% Copyright (C) 2000 Torbjörn Törnkvist +-%%% Copyright (c) 2010 Torbjorn Tornkvist +-%%% See MIT-LICENSE at the top dir for licensing information. +-%%% +-%%% Modified by Sean Hinde 7th Dec 2000 +-%%% Turned into gen_fsm, made non-blocking, added timers etc to support this. +-%%% Now has the concept of a name (string() or atom()) per instance which allows +-%%% multiple users to call by name if so desired. +-%%% +-%%% Can be configured with start_link parameters or use a config file to get +-%%% host to connect to, dn, password, log function etc. +-%%% -------------------------------------------------------------------- +- +- +-%%%---------------------------------------------------------------------- +-%%% LDAP Client state machine. +-%%% Possible states are: +-%%% connecting - actually disconnected, but retrying periodically +-%%% wait_bind_response - connected and sent bind request +-%%% active - bound to LDAP Server and ready to handle commands +-%%%---------------------------------------------------------------------- +- +-%%-compile(export_all). +-%%-export([Function/Arity, ...]). +- +--behaviour(gen_fsm). +- +-%% External exports +--export([start_link/1, start_link/5, start_link/6]). +- +--export([baseObject/0,singleLevel/0,wholeSubtree/0,close/1, +- equalityMatch/2,greaterOrEqual/2,lessOrEqual/2, +- approxMatch/2,search/2,substrings/2,present/1, +- 'and'/1,'or'/1,'not'/1,modify/3, mod_add/2, mod_delete/2, +- mod_replace/2, add/3, delete/2, modify_dn/5]). +--export([debug_level/2, get_status/1]). +- +-%% gen_fsm callbacks +--export([init/1, connecting/2, +- connecting/3, wait_bind_response/3, active/3, handle_event/3, +- handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). +- +- +--import(lists,[concat/1]). +- +--include("ELDAPv3.hrl"). +--include("eldap.hrl"). +- +--define(LDAP_VERSION, 3). +--define(RETRY_TIMEOUT, 5000). +--define(BIND_TIMEOUT, 10000). +--define(CMD_TIMEOUT, 5000). +--define(MAX_TRANSACTION_ID, 65535). +--define(MIN_TRANSACTION_ID, 0). +- +--record(eldap, {version = ?LDAP_VERSION, +- hosts, % Possible hosts running LDAP servers +- host = null, % Connected Host LDAP server +- port = 389 , % The LDAP server port +- fd = null, % Socket filedescriptor. +- rootdn = "", % Name of the entry to bind as +- passwd, % Password for (above) entry +- id = 0, % LDAP Request ID +- log, % User provided log function +- bind_timer, % Ref to bind timeout +- dict, % dict holding operation params and results +- debug_level % Integer debug/logging level +- }). +- +-%%%---------------------------------------------------------------------- +-%%% API +-%%%---------------------------------------------------------------------- +-start_link(Name) -> +- Reg_name = list_to_atom("eldap_" ++ Name), +- gen_fsm:start_link({local, Reg_name}, ?MODULE, [], []). +- +-start_link(Name, Hosts, Port, Rootdn, Passwd) -> +- Log = fun(N, Fmt, Args) -> io:format("---- " ++ Fmt, [Args]) end, +- Reg_name = list_to_atom("eldap_" ++ Name), +- gen_fsm:start_link({local, Reg_name}, ?MODULE, {Hosts, Port, Rootdn, Passwd, Log}, []). +- +-start_link(Name, Hosts, Port, Rootdn, Passwd, Log) -> +- Reg_name = list_to_atom("eldap_" ++ Name), +- gen_fsm:start_link({local, Reg_name}, ?MODULE, {Hosts, Port, Rootdn, Passwd, Log}, []). +- +-%%% -------------------------------------------------------------------- +-%%% Set Debug Level. 0 - none, 1 - errors, 2 - ldap events +-%%% -------------------------------------------------------------------- +-debug_level(Handle, N) when integer(N) -> +- Handle1 = get_handle(Handle), +- gen_fsm:sync_send_all_state_event(Handle1, {debug_level,N}). +- +-%%% -------------------------------------------------------------------- +-%%% Get status of connection. +-%%% -------------------------------------------------------------------- +-get_status(Handle) -> +- Handle1 = get_handle(Handle), +- gen_fsm:sync_send_all_state_event(Handle1, get_status). +- +-%%% -------------------------------------------------------------------- +-%%% Shutdown connection (and process) asynchronous. +-%%% -------------------------------------------------------------------- +-close(Handle) -> +- Handle1 = get_handle(Handle), +- gen_fsm:send_all_state_event(Handle1, close). +- +-%%% -------------------------------------------------------------------- +-%%% Add an entry. The entry field MUST NOT exist for the AddRequest +-%%% to succeed. The parent of the entry MUST exist. +-%%% Example: +-%%% +-%%% add(Handle, +-%%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com", +-%%% [{"objectclass", ["person"]}, +-%%% {"cn", ["Bill Valentine"]}, +-%%% {"sn", ["Valentine"]}, +-%%% {"telephoneNumber", ["545 555 00"]}] +-%%% ) +-%%% -------------------------------------------------------------------- +-add(Handle, Entry, Attributes) when list(Entry),list(Attributes) -> +- Handle1 = get_handle(Handle), +- gen_fsm:sync_send_event(Handle1, {add, Entry, add_attrs(Attributes)}). +- +-%%% Do sanity check ! +-add_attrs(Attrs) -> +- F = fun({Type,Vals}) when list(Type),list(Vals) -> +- %% Confused ? Me too... :-/ +- {'AddRequest_attributes',Type, Vals} +- end, +- case catch lists:map(F, Attrs) of +- {'EXIT', _} -> throw({error, attribute_values}); +- Else -> Else +- end. +- +- +-%%% -------------------------------------------------------------------- +-%%% Delete an entry. The entry consists of the DN of +-%%% the entry to be deleted. +-%%% Example: +-%%% +-%%% delete(Handle, +-%%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com" +-%%% ) +-%%% -------------------------------------------------------------------- +-delete(Handle, Entry) when list(Entry) -> +- Handle1 = get_handle(Handle), +- gen_fsm:sync_send_event(Handle1, {delete, Entry}). +- +-%%% -------------------------------------------------------------------- +-%%% Modify an entry. Given an entry a number of modification +-%%% operations can be performed as one atomic operation. +-%%% Example: +-%%% +-%%% modify(Handle, +-%%% "cn=Torbjorn Tornkvist, ou=people, o=Bluetail AB, dc=bluetail, dc=com", +-%%% [replace("telephoneNumber", ["555 555 00"]), +-%%% add("description", ["LDAP hacker"])] +-%%% ) +-%%% -------------------------------------------------------------------- +-modify(Handle, Object, Mods) when list(Object), list(Mods) -> +- Handle1 = get_handle(Handle), +- gen_fsm:sync_send_event(Handle1, {modify, Object, Mods}). +- +-%%% +-%%% Modification operations. +-%%% Example: +-%%% replace("telephoneNumber", ["555 555 00"]) +-%%% +-mod_add(Type, Values) when list(Type), list(Values) -> m(add, Type, Values). +-mod_delete(Type, Values) when list(Type), list(Values) -> m(delete, Type, Values). +-mod_replace(Type, Values) when list(Type), list(Values) -> m(replace, Type, Values). +- +-m(Operation, Type, Values) -> +- #'ModifyRequest_modification_SEQOF'{ +- operation = Operation, +- modification = #'AttributeTypeAndValues'{ +- type = Type, +- vals = Values}}. +- +-%%% -------------------------------------------------------------------- +-%%% Modify an entry. Given an entry a number of modification +-%%% operations can be performed as one atomic operation. +-%%% Example: +-%%% +-%%% modify_dn(Handle, +-%%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com", +-%%% "cn=Ben Emerson", +-%%% true, +-%%% "" +-%%% ) +-%%% -------------------------------------------------------------------- +-modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) +- when list(Entry),list(NewRDN),atom(DelOldRDN),list(NewSup) -> +- Handle1 = get_handle(Handle), +- gen_fsm:sync_send_event(Handle1, {modify_dn, Entry, NewRDN, bool_p(DelOldRDN), optional(NewSup)}). +- +-%%% Sanity checks ! +- +-bool_p(Bool) when Bool==true;Bool==false -> Bool. +- +-optional([]) -> asn1_NOVALUE; +-optional(Value) -> Value. +- +-%%% -------------------------------------------------------------------- +-%%% Synchronous search of the Directory returning a +-%%% requested set of attributes. +-%%% +-%%% Example: +-%%% +-%%% Filter = eldap:substrings("sn", [{any,"o"}]), +-%%% eldap:search(S, [{base, "dc=bluetail, dc=com"}, +-%%% {filter, Filter}, +-%%% {attributes,["cn"]}])), +-%%% +-%%% Returned result: {ok, #eldap_search_result{}} +-%%% +-%%% Example: +-%%% +-%%% {ok,{eldap_search_result, +-%%% [{eldap_entry, +-%%% "cn=Magnus Froberg, dc=bluetail, dc=com", +-%%% [{"cn",["Magnus Froberg"]}]}, +-%%% {eldap_entry, +-%%% "cn=Torbjorn Tornkvist, dc=bluetail, dc=com", +-%%% [{"cn",["Torbjorn Tornkvist"]}]}], +-%%% []}} +-%%% +-%%% -------------------------------------------------------------------- +-search(Handle, A) when record(A, eldap_search) -> +- call_search(Handle, A); +-search(Handle, L) when list(Handle), list(L) -> +- case catch parse_search_args(L) of +- {error, Emsg} -> {error, Emsg}; +- {'EXIT', Emsg} -> {error, Emsg}; +- A when record(A, eldap_search) -> call_search(Handle, A) +- end. +- +-call_search(Handle, A) -> +- Handle1 = get_handle(Handle), +- gen_fsm:sync_send_event(Handle1, {search, A}). +- +-parse_search_args(Args) -> +- parse_search_args(Args, #eldap_search{scope = wholeSubtree}). +- +-parse_search_args([{base, Base}|T],A) -> +- parse_search_args(T,A#eldap_search{base = Base}); +-parse_search_args([{filter, Filter}|T],A) -> +- parse_search_args(T,A#eldap_search{filter = Filter}); +-parse_search_args([{scope, Scope}|T],A) -> +- parse_search_args(T,A#eldap_search{scope = Scope}); +-parse_search_args([{attributes, Attrs}|T],A) -> +- parse_search_args(T,A#eldap_search{attributes = Attrs}); +-parse_search_args([{types_only, TypesOnly}|T],A) -> +- parse_search_args(T,A#eldap_search{types_only = TypesOnly}); +-parse_search_args([{timeout, Timeout}|T],A) when integer(Timeout) -> +- parse_search_args(T,A#eldap_search{timeout = Timeout}); +-parse_search_args([H|T],A) -> +- throw({error,{unknown_arg, H}}); +-parse_search_args([],A) -> +- A. +- +-%%% +-%%% The Scope parameter +-%%% +-baseObject() -> baseObject. +-singleLevel() -> singleLevel. +-wholeSubtree() -> wholeSubtree. +- +-%%% +-%%% Boolean filter operations +-%%% +-'and'(ListOfFilters) when list(ListOfFilters) -> {'and',ListOfFilters}. +-'or'(ListOfFilters) when list(ListOfFilters) -> {'or', ListOfFilters}. +-'not'(Filter) when tuple(Filter) -> {'not',Filter}. +- +-%%% +-%%% The following Filter parameters consist of an attribute +-%%% and an attribute value. Example: F("uid","tobbe") +-%%% +-equalityMatch(Desc, Value) -> {equalityMatch, av_assert(Desc, Value)}. +-greaterOrEqual(Desc, Value) -> {greaterOrEqual, av_assert(Desc, Value)}. +-lessOrEqual(Desc, Value) -> {lessOrEqual, av_assert(Desc, Value)}. +-approxMatch(Desc, Value) -> {approxMatch, av_assert(Desc, Value)}. +- +-av_assert(Desc, Value) -> +- #'AttributeValueAssertion'{attributeDesc = Desc, +- assertionValue = Value}. +- +-%%% +-%%% Filter to check for the presence of an attribute +-%%% +-present(Attribute) when list(Attribute) -> +- {present, Attribute}. +- +- +-%%% +-%%% A substring filter seem to be based on a pattern: +-%%% +-%%% InitValue*AnyValue*FinalValue +-%%% +-%%% where all three parts seem to be optional (at least when +-%%% talking with an OpenLDAP server). Thus, the arguments +-%%% to substrings/2 looks like this: +-%%% +-%%% Type ::= string( ) +-%%% SubStr ::= listof( {initial,Value} | {any,Value}, {final,Value}) +-%%% +-%%% Example: substrings("sn",[{initial,"To"},{any,"kv"},{final,"st"}]) +-%%% will match entries containing: 'sn: Tornkvist' +-%%% +-substrings(Type, SubStr) when list(Type), list(SubStr) -> +- Ss = {'SubstringFilter_substrings',v_substr(SubStr)}, +- {substrings,#'SubstringFilter'{type = Type, +- substrings = Ss}}. +- +- +-get_handle(Pid) when pid(Pid) -> Pid; +-get_handle(Atom) when atom(Atom) -> Atom; +-get_handle(Name) when list(Name) -> list_to_atom("eldap_" ++ Name). +-%%%---------------------------------------------------------------------- +-%%% Callback functions from gen_fsm +-%%%---------------------------------------------------------------------- +- +-%%---------------------------------------------------------------------- +-%% Func: init/1 +-%% Returns: {ok, StateName, StateData} | +-%% {ok, StateName, StateData, Timeout} | +-%% ignore | +-%% {stop, StopReason} +-%% I use the trick of setting a timeout of 0 to pass control into the +-%% process. +-%%---------------------------------------------------------------------- +-init([]) -> +- case get_config() of +- {ok, Hosts, Rootdn, Passwd, Log} -> +- init({Hosts, Rootdn, Passwd, Log}); +- {error, Reason} -> +- {stop, Reason} +- end; +-init({Hosts, Port, Rootdn, Passwd, Log}) -> +- {ok, connecting, #eldap{hosts = Hosts, +- port = Port, +- rootdn = Rootdn, +- passwd = Passwd, +- id = 0, +- log = Log, +- dict = dict:new(), +- debug_level = 0}, 0}. +- +-%%---------------------------------------------------------------------- +-%% Func: StateName/2 +-%% Called when gen_fsm:send_event/2,3 is invoked (async) +-%% Returns: {next_state, NextStateName, NextStateData} | +-%% {next_state, NextStateName, NextStateData, Timeout} | +-%% {stop, Reason, NewStateData} +-%%---------------------------------------------------------------------- +-connecting(timeout, S) -> +- {ok, NextState, NewS} = connect_bind(S), +- {next_state, NextState, NewS}. +- +-%%---------------------------------------------------------------------- +-%% Func: StateName/3 +-%% Called when gen_fsm:sync_send_event/2,3 is invoked. +-%% Returns: {next_state, NextStateName, NextStateData} | +-%% {next_state, NextStateName, NextStateData, Timeout} | +-%% {reply, Reply, NextStateName, NextStateData} | +-%% {reply, Reply, NextStateName, NextStateData, Timeout} | +-%% {stop, Reason, NewStateData} | +-%% {stop, Reason, Reply, NewStateData} +-%%---------------------------------------------------------------------- +-connecting(Event, From, S) -> +- Reply = {error, connecting}, +- {reply, Reply, connecting, S}. +- +-wait_bind_response(Event, From, S) -> +- Reply = {error, wait_bind_response}, +- {reply, Reply, wait_bind_response, S}. +- +-active(Event, From, S) -> +- case catch send_command(Event, From, S) of +- {ok, NewS} -> +- {next_state, active, NewS}; +- {error, Reason} -> +- {reply, {error, Reason}, active, S}; +- {'EXIT', Reason} -> +- {reply, {error, Reason}, active, S} +- end. +- +-%%---------------------------------------------------------------------- +-%% Func: handle_event/3 +-%% Called when gen_fsm:send_all_state_event/2 is invoked. +-%% Returns: {next_state, NextStateName, NextStateData} | +-%% {next_state, NextStateName, NextStateData, Timeout} | +-%% {stop, Reason, NewStateData} +-%%---------------------------------------------------------------------- +-handle_event(close, StateName, S) -> +- gen_tcp:close(S#eldap.fd), +- {stop, closed, S}; +- +-handle_event(Event, StateName, S) -> +- {next_state, StateName, S}. +- +-%%---------------------------------------------------------------------- +-%% Func: handle_sync_event/4 +-%% Called when gen_fsm:sync_send_all_state_event/2,3 is invoked +-%% Returns: {next_state, NextStateName, NextStateData} | +-%% {next_state, NextStateName, NextStateData, Timeout} | +-%% {reply, Reply, NextStateName, NextStateData} | +-%% {reply, Reply, NextStateName, NextStateData, Timeout} | +-%% {stop, Reason, NewStateData} | +-%% {stop, Reason, Reply, NewStateData} +-%%---------------------------------------------------------------------- +-handle_sync_event({debug_level, N}, From, StateName, S) -> +- {reply, ok, StateName, S#eldap{debug_level = N}}; +- +-handle_sync_event(Event, From, StateName, S) -> +- {reply, {StateName, S}, StateName, S}; +- +-handle_sync_event(Event, From, StateName, S) -> +- Reply = ok, +- {reply, Reply, StateName, S}. +- +-%%---------------------------------------------------------------------- +-%% Func: handle_info/3 +-%% Returns: {next_state, NextStateName, NextStateData} | +-%% {next_state, NextStateName, NextStateData, Timeout} | +-%% {stop, Reason, NewStateData} +-%%---------------------------------------------------------------------- +- +-%% +-%% Packets arriving in various states +-%% +-handle_info({tcp, Socket, Data}, connecting, S) -> +- log1("eldap. tcp packet received when disconnected!~n~p~n", [Data], S), +- {next_state, connecting, S}; +- +-handle_info({tcp, Socket, Data}, wait_bind_response, S) -> +- cancel_timer(S#eldap.bind_timer), +- case catch recvd_wait_bind_response(Data, S) of +- bound -> {next_state, active, S}; +- {fail_bind, Reason} -> close_and_retry(S), +- {next_state, connecting, S#eldap{fd = null}}; +- {'EXIT', Reason} -> close_and_retry(S), +- {next_state, connecting, S#eldap{fd = null}}; +- {error, Reason} -> close_and_retry(S), +- {next_state, connecting, S#eldap{fd = null}} +- end; +- +-handle_info({tcp, Socket, Data}, active, S) -> +- case catch recvd_packet(Data, S) of +- {reply, Reply, To, NewS} -> gen_fsm:reply(To, Reply), +- {next_state, active, NewS}; +- {ok, NewS} -> {next_state, active, NewS}; +- {'EXIT', Reason} -> {next_state, active, S}; +- {error, Reason} -> {next_state, active, S} +- end; +- +-handle_info({tcp_closed, Socket}, All_fsm_states, S) -> +- F = fun(Id, [{Timer, From, Name}|Res]) -> +- gen_fsm:reply(From, {error, tcp_closed}), +- cancel_timer(Timer) +- end, +- dict:map(F, S#eldap.dict), +- retry_connect(), +- {next_state, connecting, S#eldap{fd = null, +- dict = dict:new()}}; +- +-handle_info({tcp_error, Socket, Reason}, Fsm_state, S) -> +- log1("eldap received tcp_error: ~p~nIn State: ~p~n", [Reason, Fsm_state], S), +- {next_state, Fsm_state, S}; +-%% +-%% Timers +-%% +-handle_info({timeout, Timer, {cmd_timeout, Id}}, active, S) -> +- case cmd_timeout(Timer, Id, S) of +- {reply, To, Reason, NewS} -> gen_fsm:reply(To, Reason), +- {next_state, active, NewS}; +- {error, Reason} -> {next_state, active, S} +- end; +- +-handle_info({timeout, retry_connect}, connecting, S) -> +- {ok, NextState, NewS} = connect_bind(S), +- {next_state, NextState, NewS}; +- +-handle_info({timeout, Timer, bind_timeout}, wait_bind_response, S) -> +- close_and_retry(S), +- {next_state, connecting, S#eldap{fd = null}}; +- +-%% +-%% Make sure we don't fill the message queue with rubbish +-%% +-handle_info(Info, StateName, S) -> +- log1("eldap. Unexpected Info: ~p~nIn state: ~p~n when StateData is: ~p~n", +- [Info, StateName, S], S), +- {next_state, StateName, S}. +- +-%%---------------------------------------------------------------------- +-%% Func: terminate/3 +-%% Purpose: Shutdown the fsm +-%% Returns: any +-%%---------------------------------------------------------------------- +-terminate(Reason, StateName, StatData) -> +- ok. +- +-%%---------------------------------------------------------------------- +-%% Func: code_change/4 +-%% Purpose: Convert process state when code is changed +-%% Returns: {ok, NewState, NewStateData} +-%%---------------------------------------------------------------------- +-code_change(OldVsn, StateName, S, Extra) -> +- {ok, StateName, S}. +- +-%%%---------------------------------------------------------------------- +-%%% Internal functions +-%%%---------------------------------------------------------------------- +-send_command(Command, From, S) -> +- Id = bump_id(S), +- {Name, Request} = gen_req(Command), +- Message = #'LDAPMessage'{messageID = Id, +- protocolOp = {Name, Request}}, +- log2("~p~n",[{Name, Request}], S), +- {ok, Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message), +- ok = gen_tcp:send(S#eldap.fd, Bytes), +- Timer = erlang:start_timer(?CMD_TIMEOUT, self(), {cmd_timeout, Id}), +- New_dict = dict:store(Id, [{Timer, From, Name}], S#eldap.dict), +- {ok, S#eldap{id = Id, +- dict = New_dict}}. +- +-gen_req({search, A}) -> +- {searchRequest, +- #'SearchRequest'{baseObject = A#eldap_search.base, +- scope = v_scope(A#eldap_search.scope), +- derefAliases = neverDerefAliases, +- sizeLimit = 0, % no size limit +- timeLimit = v_timeout(A#eldap_search.timeout), +- typesOnly = v_bool(A#eldap_search.types_only), +- filter = v_filter(A#eldap_search.filter), +- attributes = v_attributes(A#eldap_search.attributes) +- }}; +-gen_req({add, Entry, Attrs}) -> +- {addRequest, +- #'AddRequest'{entry = Entry, +- attributes = Attrs}}; +-gen_req({delete, Entry}) -> +- {delRequest, Entry}; +-gen_req({modify, Obj, Mod}) -> +- v_modifications(Mod), +- {modifyRequest, +- #'ModifyRequest'{object = Obj, +- modification = Mod}}; +-gen_req({modify_dn, Entry, NewRDN, DelOldRDN, NewSup}) -> +- {modDNRequest, +- #'ModifyDNRequest'{entry = Entry, +- newrdn = NewRDN, +- deleteoldrdn = DelOldRDN, +- newSuperior = NewSup}}. +- +-%%----------------------------------------------------------------------- +-%% recvd_packet +-%% Deals with incoming packets in the active state +-%% Will return one of: +-%% {ok, NewS} - Don't reply to client yet as this is part of a search +-%% result and we haven't got all the answers yet. +-%% {reply, Result, From, NewS} - Reply with result to client From +-%% {error, Reason} +-%% {'EXIT', Reason} - Broke +-%%----------------------------------------------------------------------- +-recvd_packet(Pkt, S) -> +- check_tag(Pkt), +- case asn1rt:decode('ELDAPv3', 'LDAPMessage', Pkt) of +- {ok,Msg} -> +- Op = Msg#'LDAPMessage'.protocolOp, +- log2("~p~n",[Op], S), +- Dict = S#eldap.dict, +- Id = Msg#'LDAPMessage'.messageID, +- {Timer, From, Name, Result_so_far} = get_op_rec(Id, Dict), +- case {Name, Op} of +- {searchRequest, {searchResEntry, R}} when +- record(R,'SearchResultEntry') -> +- New_dict = dict:append(Id, R, Dict), +- {ok, S#eldap{dict = New_dict}}; +- {searchRequest, {searchResDone, Result}} -> +- case Result#'LDAPResult'.resultCode of +- success -> +- {Res, Ref} = polish(Result_so_far), +- New_dict = dict:erase(Id, Dict), +- cancel_timer(Timer), +- {reply, #eldap_search_result{entries = Res, +- referrals = Ref}, From, +- S#eldap{dict = New_dict}}; +- Reason -> +- New_dict = dict:erase(Id, Dict), +- cancel_timer(Timer), +- {reply, {error, Reason}, From, S#eldap{dict = New_dict}} +- end; +- {searchRequest, {searchResRef, R}} -> +- New_dict = dict:append(Id, R, Dict), +- {ok, S#eldap{dict = New_dict}}; +- {addRequest, {addResponse, Result}} -> +- New_dict = dict:erase(Id, Dict), +- cancel_timer(Timer), +- Reply = check_reply(Result, From), +- {reply, Reply, From, S#eldap{dict = New_dict}}; +- {delRequest, {delResponse, Result}} -> +- New_dict = dict:erase(Id, Dict), +- cancel_timer(Timer), +- Reply = check_reply(Result, From), +- {reply, Reply, From, S#eldap{dict = New_dict}}; +- {modifyRequest, {modifyResponse, Result}} -> +- New_dict = dict:erase(Id, Dict), +- cancel_timer(Timer), +- Reply = check_reply(Result, From), +- {reply, Reply, From, S#eldap{dict = New_dict}}; +- {modDNRequest, {modDNResponse, Result}} -> +- New_dict = dict:erase(Id, Dict), +- cancel_timer(Timer), +- Reply = check_reply(Result, From), +- {reply, Reply, From, S#eldap{dict = New_dict}}; +- {OtherName, OtherResult} -> +- New_dict = dict:erase(Id, Dict), +- cancel_timer(Timer), +- {reply, {error, {invalid_result, OtherName, OtherResult}}, +- From, S#eldap{dict = New_dict}} +- end; +- Error -> Error +- end. +- +-check_reply(#'LDAPResult'{resultCode = success}, From) -> +- ok; +-check_reply(#'LDAPResult'{resultCode = Reason}, From) -> +- {error, Reason}; +-check_reply(Other, From) -> +- {error, Other}. +- +-get_op_rec(Id, Dict) -> +- case dict:find(Id, Dict) of +- {ok, [{Timer, From, Name}|Res]} -> +- {Timer, From, Name, Res}; +- error -> +- throw({error, unkown_id}) +- end. +- +-%%----------------------------------------------------------------------- +-%% recvd_wait_bind_response packet +-%% Deals with incoming packets in the wait_bind_response state +-%% Will return one of: +-%% bound - Success - move to active state +-%% {fail_bind, Reason} - Failed +-%% {error, Reason} +-%% {'EXIT', Reason} - Broken packet +-%%----------------------------------------------------------------------- +-recvd_wait_bind_response(Pkt, S) -> +- check_tag(Pkt), +- case asn1rt:decode('ELDAPv3', 'LDAPMessage', Pkt) of +- {ok,Msg} -> +- log2("~p", [Msg], S), +- check_id(S#eldap.id, Msg#'LDAPMessage'.messageID), +- case Msg#'LDAPMessage'.protocolOp of +- {bindResponse, Result} -> +- case Result#'LDAPResult'.resultCode of +- success -> bound; +- Error -> {fail_bind, Error} +- end +- end; +- Else -> +- {fail_bind, Else} +- end. +- +-check_id(Id, Id) -> ok; +-check_id(_, _) -> throw({error, wrong_bind_id}). +- +-%%----------------------------------------------------------------------- +-%% General Helpers +-%%----------------------------------------------------------------------- +- +-cancel_timer(Timer) -> +- erlang:cancel_timer(Timer), +- receive +- {timeout, Timer, _} -> +- ok +- after 0 -> +- ok +- end. +- +- +-%%% Sanity check of received packet +-check_tag(Data) -> +- case asn1rt_ber:decode_tag(Data) of +- {Tag, Data1, Rb} -> +- case asn1rt_ber:decode_length(Data1) of +- {{Len,Data2}, Rb2} -> ok; +- _ -> throw({error,decoded_tag_length}) +- end; +- _ -> throw({error,decoded_tag}) +- end. +- +-close_and_retry(S) -> +- gen_tcp:close(S#eldap.fd), +- retry_connect(). +- +-retry_connect() -> +- erlang:send_after(?RETRY_TIMEOUT, self(), +- {timeout, retry_connect}). +- +- +-%%----------------------------------------------------------------------- +-%% Sort out timed out commands +-%%----------------------------------------------------------------------- +-cmd_timeout(Timer, Id, S) -> +- Dict = S#eldap.dict, +- case dict:find(Id, Dict) of +- {ok, [{Id, Timer, From, Name}|Res]} -> +- case Name of +- searchRequest -> +- {Res1, Ref1} = polish(Res), +- New_dict = dict:erase(Id, Dict), +- {reply, From, {timeout, +- #eldap_search_result{entries = Res1, +- referrals = Ref1}}, +- S#eldap{dict = New_dict}}; +- Others -> +- New_dict = dict:erase(Id, Dict), +- {reply, From, {error, timeout}, S#eldap{dict = New_dict}} +- end; +- error -> +- {error, timed_out_cmd_not_in_dict} +- end. +- +-%%----------------------------------------------------------------------- +-%% Common stuff for results +-%%----------------------------------------------------------------------- +-%%% +-%%% Polish the returned search result +-%%% +- +-polish(Entries) -> +- polish(Entries, [], []). +- +-polish([H|T], Res, Ref) when record(H, 'SearchResultEntry') -> +- ObjectName = H#'SearchResultEntry'.objectName, +- F = fun({_,A,V}) -> {A,V} end, +- Attrs = lists:map(F, H#'SearchResultEntry'.attributes), +- polish(T, [#eldap_entry{object_name = ObjectName, +- attributes = Attrs}|Res], Ref); +-polish([H|T], Res, Ref) -> % No special treatment of referrals at the moment. +- polish(T, Res, [H|Ref]); +-polish([], Res, Ref) -> +- {Res, Ref}. +- +-%%----------------------------------------------------------------------- +-%% Connect to next server in list and attempt to bind to it. +-%%----------------------------------------------------------------------- +-connect_bind(S) -> +- Host = next_host(S#eldap.host, S#eldap.hosts), +- TcpOpts = [{packet, asn1}, {active, true}], +- case gen_tcp:connect(Host, S#eldap.port, TcpOpts) of +- {ok, Socket} -> +- case bind_request(Socket, S) of +- {ok, NewS} -> +- Timer = erlang:start_timer(?BIND_TIMEOUT, self(), +- {timeout, bind_timeout}), +- {ok, wait_bind_response, NewS#eldap{fd = Socket, +- host = Host, +- bind_timer = Timer}}; +- {error, Reason} -> +- gen_tcp:close(Socket), +- erlang:send_after(?RETRY_TIMEOUT, self(), +- {timeout, retry_connect}), +- {ok, connecting, S#eldap{host = Host}} +- end; +- {error, Reason} -> +- erlang:send_after(?RETRY_TIMEOUT, self(), +- {timeout, retry_connect}), +- {ok, connecting, S#eldap{host = Host}} +- end. +- +-bind_request(Socket, S) -> +- Id = bump_id(S), +- Req = #'BindRequest'{version = S#eldap.version, +- name = S#eldap.rootdn, +- authentication = {simple, S#eldap.passwd}}, +- Message = #'LDAPMessage'{messageID = Id, +- protocolOp = {bindRequest, Req}}, +- log2("Message:~p~n",[Message], S), +- {ok, Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message), +- ok = gen_tcp:send(Socket, Bytes), +- {ok, S#eldap{id = Id}}. +- +-%% Given last tried Server, find next one to try +-next_host(null, [H|_]) -> H; % First time, take first +-next_host(Host, Hosts) -> % Find next in turn +- next_host(Host, Hosts, Hosts). +- +-next_host(Host, [Host], Hosts) -> hd(Hosts); % Wrap back to first +-next_host(Host, [Host|Tail], Hosts) -> hd(Tail); % Take next +-next_host(Host, [], Hosts) -> hd(Hosts); % Never connected before? (shouldn't happen) +-next_host(Host, [H|T], Hosts) -> next_host(Host, T, Hosts). +- +- +-%%% -------------------------------------------------------------------- +-%%% Verify the input data +-%%% -------------------------------------------------------------------- +- +-v_filter({'and',L}) -> {'and',L}; +-v_filter({'or', L}) -> {'or',L}; +-v_filter({'not',L}) -> {'not',L}; +-v_filter({equalityMatch,AV}) -> {equalityMatch,AV}; +-v_filter({greaterOrEqual,AV}) -> {greaterOrEqual,AV}; +-v_filter({lessOrEqual,AV}) -> {lessOrEqual,AV}; +-v_filter({approxMatch,AV}) -> {approxMatch,AV}; +-v_filter({present,A}) -> {present,A}; +-v_filter({substrings,S}) when record(S,'SubstringFilter') -> {substrings,S}; +-v_filter(_Filter) -> throw({error,concat(["unknown filter: ",_Filter])}). +- +-v_modifications(Mods) -> +- F = fun({_,Op,_}) -> +- case lists:member(Op,[add,delete,replace]) of +- true -> true; +- _ -> throw({error,{mod_operation,Op}}) +- end +- end, +- lists:foreach(F, Mods). +- +-v_substr([{Key,Str}|T]) when list(Str),Key==initial;Key==any;Key==final -> +- [{Key,Str}|v_substr(T)]; +-v_substr([H|T]) -> +- throw({error,{substring_arg,H}}); +-v_substr([]) -> +- []. +-v_scope(baseObject) -> baseObject; +-v_scope(singleLevel) -> singleLevel; +-v_scope(wholeSubtree) -> wholeSubtree; +-v_scope(_Scope) -> throw({error,concat(["unknown scope: ",_Scope])}). +- +-v_bool(true) -> true; +-v_bool(false) -> false; +-v_bool(_Bool) -> throw({error,concat(["not Boolean: ",_Bool])}). +- +-v_timeout(I) when integer(I), I>=0 -> I; +-v_timeout(_I) -> throw({error,concat(["timeout not positive integer: ",_I])}). +- +-v_attributes(Attrs) -> +- F = fun(A) when list(A) -> A; +- (A) -> throw({error,concat(["attribute not String: ",A])}) +- end, +- lists:map(F,Attrs). +- +- +-%%% -------------------------------------------------------------------- +-%%% Get and Validate the initial configuration +-%%% -------------------------------------------------------------------- +-get_config() -> +- Priv_dir = code:priv_dir(eldap), +- File = filename:join(Priv_dir, "eldap.conf"), +- case file:consult(File) of +- {ok, Entries} -> +- case catch parse(Entries) of +- {ok, Hosts, Port, Rootdn, Passwd, Log} -> +- {ok, Hosts, Port, Rootdn, Passwd, Log}; +- {error, Reason} -> +- {error, Reason}; +- {'EXIT', Reason} -> +- {error, Reason} +- end; +- {error, Reason} -> +- {error, Reason} +- end. +- +-parse(Entries) -> +- {ok, +- get_hosts(host, Entries), +- get_integer(port, Entries), +- get_list(rootdn, Entries), +- get_list(passwd, Entries), +- get_log(log, Entries)}. +- +-get_integer(Key, List) -> +- case lists:keysearch(Key, 1, List) of +- {value, {Key, Value}} when integer(Value) -> +- Value; +- {value, {Key, Value}} -> +- throw({error, "Bad Value in Config for " ++ atom_to_list(Key)}); +- false -> +- throw({error, "No Entry in Config for " ++ atom_to_list(Key)}) +- end. +- +-get_list(Key, List) -> +- case lists:keysearch(Key, 1, List) of +- {value, {Key, Value}} when list(Value) -> +- Value; +- {value, {Key, Value}} -> +- throw({error, "Bad Value in Config for " ++ atom_to_list(Key)}); +- false -> +- throw({error, "No Entry in Config for " ++ atom_to_list(Key)}) +- end. +- +-get_log(Key, List) -> +- case lists:keysearch(Key, 1, List) of +- {value, {Key, Value}} when function(Value) -> +- Value; +- {value, {Key, Else}} -> +- false; +- false -> +- fun(Level, Format, Args) -> io:format("--- " ++ Format, Args) end +- end. +- +-get_hosts(Key, List) -> +- lists:map(fun({Key1, {A,B,C,D}}) when integer(A), +- integer(B), +- integer(C), +- integer(D), +- Key == Key1-> +- {A,B,C,D}; +- ({Key1, Value}) when list(Value), +- Key == Key1-> +- Value; +- ({Else, Value}) -> +- throw({error, "Bad Hostname in config"}) +- end, List). +- +-%%% -------------------------------------------------------------------- +-%%% Other Stuff +-%%% -------------------------------------------------------------------- +-bump_id(#eldap{id = Id}) when Id > ?MAX_TRANSACTION_ID -> +- ?MIN_TRANSACTION_ID; +-bump_id(#eldap{id = Id}) -> +- Id + 1. +- +-%%% -------------------------------------------------------------------- +-%%% Log routines. Call a user provided log routine Fun. +-%%% -------------------------------------------------------------------- +- +-log1(Str, Args, #eldap{log = Fun, debug_level = N}) -> log(Fun, Str, Args, 1, N). +-log2(Str, Args, #eldap{log = Fun, debug_level = N}) -> log(Fun, Str, Args, 2, N). +- +-log(Fun, Str, Args, This_level, Status) when function(Fun), This_level =< Status -> +- catch Fun(This_level, Str, Args); +-log(_, _, _, _, _) -> +- ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/remove-ietf-doc.patch b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/remove-ietf-doc.patch new file mode 100644 index 0000000..e1f55d9 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/eldap-wrapper/remove-ietf-doc.patch @@ -0,0 +1,3036 @@ +diff --git a/doc/draft-ietf-asid-ldap-c-api-00.txt b/doc/draft-ietf-asid-ldap-c-api-00.txt +deleted file mode 100755 +index 5f2e856..0000000 +--- a/doc/draft-ietf-asid-ldap-c-api-00.txt ++++ /dev/null +@@ -1,3030 +0,0 @@ +- +- +- +- +- +- +-Network Working Group T. Howes +-INTERNET-DRAFT Netscape Communications Corp. +-Intended Category: Standards Track M. Smith +-Obsoletes: RFC 1823 Netscape Communications Corp. +-Expires: January 1998 A. Herron +- Microsoft Corp. +- C. Weider +- Microsoft Corp. +- M. Wahl +- Critical Angle, Inc. +- +- 29 July 1997 +- +- +- The C LDAP Application Program Interface +- +- +- +- +-1. Status of this Memo +- +-This draft document will be submitted to the RFC Editor as a Standards +-Track document. Distribution of this memo is unlimited. Please send com- +-ments to the authors. +- +-This document is an Internet-Draft. Internet-Drafts are working docu- +-ments of the Internet Engineering Task Force (IETF), its areas, and its +-working groups. Note that other groups may also distribute working +-documents as Internet-Drafts. +- +-Internet-Drafts are draft documents valid for a maximum of six months +-and may be updated, replaced, or obsoleted by other documents at any +-time. It is inappropriate to use Internet-Drafts as reference material +-or to cite them other than as ``work in progress.'' +- +-To learn the current status of any Internet-Draft, please check the +-``1id-abstracts.txt'' listing contained in the Internet-Drafts Shadow +-Directories on ds.internic.net (US East Coast), nic.nordu.net (Europe), +-ftp.isi.edu (US West Coast), or munnari.oz.au (Pacific Rim). +- +-2. Introduction +- +-This document defines a C language application program interface to the +-lightweight directory access protocol (LDAP). This document replaces the +-previous definition of this API, defined in RFC 1823, updating it to +-include support for features found in version 3 of the LDAP protocol. +-New extended operation functions were added to support LDAPv3 features +-such as controls. In addition, other LDAP API changes were made to +- +- +- +-Expires: January 1998 [Page 1] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-support information hiding and thread safety. +- +-The C LDAP API is designed to be powerful, yet simple to use. It defines +-compatible synchronous and asynchronous interfaces to LDAP to suit a +-wide variety of applications. This document gives a brief overview of +-the LDAP model, then an overview of how the API is used by an applica- +-tion program to obtain LDAP information. The API calls are described in +-detail, followed by an appendix that provides some example code demon- +-strating the use of the API. This document provides information to the +-Internet community. It does not specify any standard. +- +-3. Overview of the LDAP Model +- +-LDAP is the lightweight directory access protocol, described in [2] and +-[6]. It can provide a lightweight frontend to the X.500 directory [1], +-or a stand-alone service. In either mode, LDAP is based on a client- +-server model in which a client makes a TCP connection to an LDAP server, +-over which it sends requests and receives responses. +- +-The LDAP information model is based on the entry, which contains infor- +-mation about some object (e.g., a person). Entries are composed of +-attributes, which have a type and one or more values. Each attribute has +-a syntax that determines what kinds of values are allowed in the attri- +-bute (e.g., ASCII characters, a jpeg photograph, etc.) and how those +-values behave during directory operations (e.g., is case significant +-during comparisons). +- +-Entries may be organized in a tree structure, usually based on politi- +-cal, geographical, and organizational boundaries. Each entry is uniquely +-named relative to its sibling entries by its relative distinguished name +-(RDN) consisting of one or more distinguished attribute values from the +-entry. At most one value from each attribute may be used in the RDN. +-For example, the entry for the person Babs Jensen might be named with +-the "Barbara Jensen" value from the commonName attribute. +- +-A globally unique name for an entry, called a distinguished name or DN, +-is constructed by concatenating the sequence of RDNs from the entry up +-to the root of the tree. For example, if Babs worked for the University +-of Michigan, the DN of her U-M entry might be "cn=Barbara Jensen, +-o=University of Michigan, c=US". The DN format used by LDAP is defined +-in [4]. +- +-Operations are provided to authenticate, search for and retrieve infor- +-mation, modify information, and add and delete entries from the tree. +-The next sections give an overview of how the API is used and detailed +-descriptions of the LDAP API calls that implement all of these func- +-tions. +- +- +- +- +-Expires: January 1998 [Page 2] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-4. Overview of LDAP API Use +- +-An application generally uses the C LDAP API in four simple steps. +- +-- Initialize an LDAP session with a default LDAP server. The +- ldap_init() function returns a handle to the session, allowing mul- +- tiple connections to be open at once. +- +-- Authenticate to the LDAP server. The ldap_bind() function and +- friends support a variety of authentication methods. +- +-- Perform some LDAP operations and obtain some results. ldap_search() +- and friends return results which can be parsed by +- ldap_result2error(), ldap_first_entry(), ldap_next_entry(), etc. +- +-- Close the session. The ldap_unbind() function closes the connec- +- tion. +- +-Operations can be performed either synchronously or asynchronously. The +-names of the synchronous functions end in _s. For example, a synchronous +-search can be completed by calling ldap_search_s(). An asynchronous +-search can be initiated by calling ldap_search(). All synchronous rou- +-tines return an indication of the outcome of the operation (e.g, the +-constant LDAP_SUCCESS or some other error code). The asynchronous rou- +-tines return the message id of the operation initiated. This id can be +-used in subsequent calls to ldap_result() to obtain the result(s) of the +-operation. An asynchronous operation can be abandoned by calling +-ldap_abandon(). +- +-Results and errors are returned in an opaque structure called LDAPMes- +-sage. Routines are provided to parse this structure, step through +-entries and attributes returned, etc. Routines are also provided to +-interpret errors. Later sections of this document describe these rou- +-tines in more detail. +- +-LDAP version 3 servers may return referrals to other servers. By +-default, implementations of this API will attempt to follow referrals +-automatically for the application. This behavior can be disabled glo- +-bally (using the ldap_set_option() call) or on a per-request basis +-through the use of a client control. +- +-As in the LDAPv3 protocol itself, all DNs and string values that are +-passed into or produced by the C LDAP API are represented as UTF-8[10] +-characters. +- +-For compatibility with existing applications, implementations of this +-API will by default use version 2 of the LDAP protocol. Applications +-that intend to take advantage of LDAP version 3 features will need to +- +- +- +-Expires: January 1998 [Page 3] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-use the ldap_set_option() call with a LDAP_OPT_PROTOCOL_VERSION to +-switch to version 3. +- +- +-5. Common Data Structures +- +-Some data structures that are common to several LDAP API functions are +-defined here: +- +- typedef struct ldap LDAP; +- +- typedef struct ldapmsg LDAPMessage; +- +- struct berval { +- unsigned long bv_len; +- char *bv_val; +- }; +- +- struct timeval { +- long tv_sec; +- long tv_usec; +- }; +- +-The LDAP structure is an opaque data type that represents an LDAP ses- +-sion Typically this corresponds to a connection to a single server, but +-it may encompass several server connections in the face of LDAPv3 refer- +-rals. +- +-The LDAPMessage structure is an opaque data type that is used to return +-results and error information. +- +-The berval structure is used to represent arbitrary binary data and its +-fields have the following meanings: +- +-bv_len Length of data in bytes. +- +-bv_val A pointer to the data itself. +- +- +-The timeval structure is used to represent an interval of time and its +-fields have the following meanings: +- +-tv_sec Seconds component of time interval. +- +-tv_usec Microseconds component of time interval. +- +- +- +- +- +- +-Expires: January 1998 [Page 4] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-6. LDAP Error Codes +- +-Many of the LDAP API routines return LDAP error codes, some of which +-indicate local errors and some of which may be returned by servers. +-Supported error codes are (hexadecimal values are given in parentheses +-after the constant): +- +- LDAP_SUCCESS (0x00) +- LDAP_OPERATIONS_ERROR( 0x01) +- LDAP_PROTOCOL_ERROR (0x02) +- LDAP_TIMELIMIT_EXCEEDED (0x03) +- LDAP_SIZELIMIT_EXCEEDED (0x04) +- LDAP_COMPARE_FALSE (0x05) +- LDAP_COMPARE_TRUE (0x06) +- LDAP_STRONG_AUTH_NOT_SUPPORTED (0x07) +- LDAP_STRONG_AUTH_REQUIRED (0x08) +- LDAP_REFERRAL (0x0a) -- new in LDAPv3 +- LDAP_ADMINLIMIT_EXCEEDED (0x0b) -- new in LDAPv3 +- LDAP_UNAVAILABLE_CRITICAL_EXTENSION (0x0c) -- new in LDAPv3 +- LDAP_CONFIDENTIALITY_REQUIRED (0x0d) -- new in LDAPv3 +- LDAP_NO_SUCH_ATTRIBUTE (0x10) +- LDAP_UNDEFINED_TYPE (0x11) +- LDAP_INAPPROPRIATE_MATCHING (0x12) +- LDAP_CONSTRAINT_VIOLATION (0x13) +- LDAP_TYPE_OR_VALUE_EXISTS (0x14) +- LDAP_INVALID_SYNTAX (0x15) +- LDAP_NO_SUCH_OBJECT (0x20) +- LDAP_ALIAS_PROBLEM (0x21) +- LDAP_INVALID_DN_SYNTAX (0x22) +- LDAP_IS_LEAF (0x23) -- not used in LDAPv3 +- LDAP_ALIAS_DEREF_PROBLEM (0x24) +- LDAP_INAPPROPRIATE_AUTH (0x30) +- LDAP_INVALID_CREDENTIALS (0x31) +- LDAP_INSUFFICIENT_ACCESS (0x32) +- LDAP_BUSY (0x33) +- LDAP_UNAVAILABLE (0x34) +- LDAP_UNWILLING_TO_PERFORM (0x35) +- LDAP_LOOP_DETECT (0x36) +- LDAP_NAMING_VIOLATION (0x40) +- LDAP_OBJECT_CLASS_VIOLATION (0x41) +- LDAP_NOT_ALLOWED_ON_NONLEAF (0x42) +- LDAP_NOT_ALLOWED_ON_RDN (0x43) +- LDAP_ALREADY_EXISTS (0x44) +- LDAP_NO_OBJECT_CLASS_MODS (0x45) +- LDAP_RESULTS_TOO_LARGE (0x46) +- LDAP_AFFECTS_MULTIPLE_DSAS (0x47) -- new in LDAPv3 +- LDAP_OTHER (0x50) +- LDAP_SERVER_DOWN (0x51) +- +- +- +-Expires: January 1998 [Page 5] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- LDAP_LOCAL_ERROR (0x52) +- LDAP_ENCODING_ERROR (0x53) +- LDAP_DECODING_ERROR (0x54) +- LDAP_TIMEOUT (0x55) +- LDAP_AUTH_UNKNOWN (0x56) +- LDAP_FILTER_ERROR (0x57) +- LDAP_USER_CANCELLED (0x58) +- LDAP_PARAM_ERROR (0x59) +- LDAP_NO_MEMORY (0x5a) +- LDAP_CONNECT_ERROR (0x5b) +- LDAP_NOT_SUPPORTED (0x5c) +- LDAP_CONTROL_NOT_FOUND (0x5d) +- LDAP_NO_RESULTS_RETURNED (0x5e) +- LDAP_MORE_RESULTS_TO_RETURN (0x5f) +- LDAP_CLIENT_LOOP (0x60) +- LDAP_REFERRAL_LIMIT_EXCEEDED (0x61) +- +- +-7. Performing LDAP Operations +- +-This section describes each LDAP operation API call in detail. All func- +-tions take a "session handle," a pointer to an LDAP structure containing +-per-connection information. Many routines return results in an LDAPMes- +-sage structure. These structures and others are described as needed +-below. +- +- +-7.1. Initializing an LDAP Session +- +-ldap_init() initializes a session with an LDAP server. The server is not +-actually contacted until an operation is performed that requires it, +-allowing various options to be set after initialization. +- +- LDAP *ldap_init( +- char *hostname, +- int portno +- ); +- +-Use of the following routine is deprecated. +- +- LDAP *ldap_open( +- char *hostname, +- int portno +- ); +- +-Parameters are: +- +-hostname Contains a space-separated list of hostnames or dotted strings +- +- +- +-Expires: January 1998 [Page 6] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- representing the IP address of hosts running an LDAP server to +- connect to. Each hostname in the list can include an optional +- port number which is separated from the host itself with a +- colon (:) character. The hosts are tried in the order listed, +- stopping with the first one to which a successful connection is +- made. Note that only ldap_open() attempts to make the connec- +- tion before returning to the caller. ldap_init() does not con- +- nect to the LDAP server. +- +-portno Contains the TCP port number to connect to. The default LDAP +- port of 389 can be obtained by supplying the constant +- LDAP_PORT. If a host includes a port number then this parame- +- ter is ignored. +- +-ldap_init() and ldap_open() both return a "session handle," a pointer to +-an opaque structure that should be passed to subsequent calls pertaining +-to the session. These routines return NULL if the session cannot be ini- +-tialized in which case the operating system error reporting mechanism +-can be checked to see why the call failed. +- +-Note that if you connect to an LDAPv2 server, one of the ldap_bind() +-calls described below must be completed before other operations can be +-performed on the session. LDAPv3 does not require that a bind operation +-be completed before other operations can be performed. +- +-The calling program can set various attributes of the session by calling +-the routines described in the next section. +- +- +-7.2. LDAP Session Handle Options +- +-The LDAP session handle returned by ldap_init() is a pointer to an +-opaque data type representing an LDAP session. Formerly, this data type +-was a structure exposed to the caller, and various fields in the struc- +-ture could be set to control aspects of the session, such as size and +-time limits on searches. +- +-In the interest of insulating callers from inevitable changes to this +-structure, these aspects of the session are now accessed through a pair +-of accessor functions, described below. +- +-ldap_get_option() is used to access the current value of various +-session-wide parameters. ldap_set_option() is used to set the value of +-these parameters. +- +- int ldap_get_option( +- LDAP *ld, +- int option, +- +- +- +-Expires: January 1998 [Page 7] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- void *outvalue +- ); +- +- int ldap_set_option( +- LDAP *ld, +- int option, +- void *invalue +- ); +- +-Parameters are: +- +-ld The session handle. +- +-option The name of the option being accessed or set. This parameter +- should be one of the following constants, which have the indi- +- cated meanings. After the constant the actual value of the con- +- stant is listed in hexadecimal in parentheses followed by the +- type of the corresponding outvalue or invalue parameter. +- +- LDAP_OPT_DESC (0x01) int * +- The underlying socket descriptor corresponding to the default +- LDAP connection. +- +- LDAP_OPT_DEREF (0x02) int * +- Controls how aliases are handled during search. It can have +- one of the following values: LDAP_DEREF_NEVER (0x00), +- LDAP_DEREF_SEARCHING (0x01), LDAP_DEREF_FINDING (0x02), or +- LDAP_DEREF_ALWAYS (0x03). The LDAP_DEREF_SEARCHING value +- means aliases should be dereferenced during the search but not +- when locating the base object of the search. The +- LDAP_DEREF_FINDING value means aliases should be dereferenced +- when locating the base object but not during the search. +- +- LDAP_OPT_SIZELIMIT (0x03) int * +- A limit on the number of entries to return from a search. A +- value of zero means no limit. +- +- LDAP_OPT_TIMELIMIT (0x04) int * +- A limit on the number of seconds to spend on a search. A value +- of zero means no limit +- +- LDAP_OPT_REBIND_FN (0x06) function pointer +- See the discussion of ldap_bind() and friends below. +- +- LDAP_OPT_REBIND_ARG (0x07) void * +- See the discussion of ldap_bind() and friends below. +- +- LDAP_OPT_REFERRALS (0x08) void * +- +- +- +-Expires: January 1998 [Page 8] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- This option controls whether the LDAP library automatically +- follows referrals returned by LDAP servers or not. It can be +- set to one of the constants LDAP_OPT_ON or LDAP_OPT_OFF. +- +- LDAP_OPT_RESTART (0x09) void * +- This option controls whether LDAP I/O operations should +- automatically be restarted if they abort prematurely. It +- should be set to one of the constants LDAP_OPT_ON or +- LDAP_OPT_OFF. This option is useful if an LDAP I/O operation +- may be interrupted prematurely, for example by a timer going +- off, or other interrrupt. +- +- LDAP_OPT_PROTOCOL_VERSION (0x11) int * +- This option indicates the version of the default LDAP server. +- It can be one of the constants LDAP_VERSION2 or LDAP_VERSION3. +- If no version is set the default is LDAP_VERSION2. +- +- LDAP_OPT_SERVER_CONTROLS (0x12) LDAPControl ** +- A default list of LDAP server controls to be sent with each +- request. See the Using Controls section below. +- +- LDAP_OPT_CLIENT_CONTROLS (0x13) LDAPControl ** +- A default list of client controls that affect the LDAP ses- +- sion. See the Using Controls section below. +- +- LDAP_OPT_HOST_NAME (0x30) char ** +- The host name of the default LDAP server. +- +- LDAP_OPT_ERROR_NUMBER (0x31) int * +- The code of the most recent LDAP error that occurred for this +- session. +- +- LDAP_OPT_ERROR_STRING (0x32) char ** +- The message returned with the most recent LDAP error that +- occurred for this session. +- +- +-outvalue The address of a place to put the value of the option. The +- actual type of this parameter depends on the setting of the +- option parameter. +- +-invalue A pointer to the value the option is to be given. The actual +- type of this parameter depends on the setting of the option +- parameter. The constants LDAP_OPT_ON and LDAP_OPT_OFF can be +- given for options that have on or off settings. +- +- +- +- +- +- +-Expires: January 1998 [Page 9] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-7.3. Working with controls +- +-LDAPv3 operations can be extended through the use of controls. Controls +-may be sent to a server or returned to the client with any LDAP message. +-These controls are referred to as server controls. +- +-The LDAP API also supports a client-side extension mechanism through the +-use of client controls. These controls affect the behavior of the LDAP +-API only and are never sent to a server. A common data structure is +-used to represent both types of controls: +- +- typedef struct ldapcontrol { +- char *ldctl_oid; +- struct berval ldctl_value; +- char ldctl_iscritical; +- } LDAPControl, *PLDAPControl; +- +-The fields in the ldapcontrol structure have the following meanings: +- +-ldctl_oid The control type, represented as a string. +- +-ldctl_value The data associated with the control (if any). +- +-ldctl_iscritical Indicates whether the control is critical of not. If +- this field is non-zero, the operation will only be car- +- ried out if the control is recognized by the server +- and/or client. +- +-Some LDAP API calls allocate an ldapcontrol structure or a NULL- +-terminated array of ldapcontrol structures. The following routines can +-be used to dispose of a single control or an array of controls: +- +- void ldap_control_free( LDAPControl *ctrl ); +- void ldap_controls_free( LDAPControl **ctrls ); +- +-A set of controls that affect the entire session can be set using the +-ldap_set_option() function (see above). A list of controls can also be +-passed directly to some LDAP API calls such as ldap_search_ext(), in +-which case any controls set for the session through the use of +-ldap_set_option() are ignored. Control lists are represented as a NULL- +-terminated array of pointers to ldapcontrol structures. +- +-Server controls are defined by LDAPv3 protocol extension documents; for +-example, a control has been proposed to support server-side sorting of +-search results [7]. +- +-No client controls are defined by this document but they may be defined +-in future revisions or in any document that extends this API. +- +- +- +-Expires: January 1998 [Page 10] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-7.4. Authenticating to the directory +- +-The following functions are used to authenticate an LDAP client to an +-LDAP directory server. +- +-The ldap_sasl_bind() and ldap_sasl_bind_s() functions can be used to do +-general and extensible authentication over LDAP through the use of the +-Simple Authentication Security Layer [8]. The routines both take the dn +-to bind as, the method to use, as a dotted-string representation of an +-OID identifying the method, and a struct berval holding the credentials. +-The special constant value LDAP_SASL_SIMPLE ("") can be passed to +-request simple authentication, or the simplified routines +-ldap_simple_bind() or ldap_simple_bind_s() can be used. +- +- int ldap_sasl_bind( +- LDAP *ld, +- char *dn, +- char *mechanism, +- struct berval *cred, +- LDAPControl **serverctrls, +- LDAPControl **clientctrls, +- int *msgidp +- ); +- +- int ldap_sasl_bind_s( +- LDAP *ld, +- char *dn, +- char *mechanism, +- struct berval *cred, +- LDAPControl **serverctrls, +- LDAPControl **clientctrls, +- struct berval **servercredp +- ); +- +- int ldap_simple_bind( +- LDAP *ld, +- char *dn, +- char *passwd +- ); +- +- int ldap_simple_bind_s( +- LDAP *ld, +- char *dn, +- char *passwd +- ); +- +- The use of the following routines is deprecated: +- +- +- +- +-Expires: January 1998 [Page 11] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- int ldap_bind( LDAP *ld, char *dn, char *cred, int method ); +- +- int ldap_bind_s( LDAP *ld, char *dn, char *cred, int method ); +- +- int ldap_kerberos_bind( LDAP *ld, char *dn ); +- +- int ldap_kerberos_bind_s( LDAP *ld, char *dn ); +- +-Parameters are: +- +-ld The session handle. +- +-dn The name of the entry to bind as. +- +-mechanism Either LDAP_AUTH_SIMPLE_OID to get simple authentication, +- or a dotted text string representing an OID identifying the +- SASL method. +- +-cred The credentials with which to authenticate. Arbitrary +- credentials can be passed using this parameter. The format +- and content of the credentials depends on the setting of +- the mechanism parameter. +- +-passwd For ldap_simple_bind(), the password to compare to the +- entry's userPassword attribute. +- +-serverctrls List of LDAP server controls. +- +-clientctrls List of client controls. +- +-msgidp This result parameter will be set to the message id of the +- request if the ldap_sasl_bind() call succeeds. +- +-servercredp This result parameter will be set to the credentials +- returned by the server. This should be freed by calling +- ldap_If no credentials are returned it will be set to NULL. +- +-Additional parameters for the deprecated routines are not described. +-Interested readers are referred to RFC 1823. +- +-The ldap_sasl_bind() function initiates an asynchronous bind operation +-and returns the constant LDAP_SUCCESS if the request was successfully +-sent, or another LDAP error code if not. See the section below on error +-handling for more information about possible errors and how to interpret +-them. If successful, ldap_sasl_bind() places the message id of the +-request in *msgidp. A subsequent call to ldap_result(), described below, +-can be used to obtain the result of the bind. +- +- +- +- +-Expires: January 1998 [Page 12] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-The ldap_simple_bind() function initiates a simple asynchronous bind +-operation and returns the message id of the operation initiated. A sub- +-sequent call to ldap_result(), described below, can be used to obtain +-the result of the bind. In case of error, ldap_simple_bind() will return +--1, setting the session error parameters in the LDAP structure appropri- +-ately. +- +-The synchronous ldap_sasl_bind_s() and ldap_simple_bind_s() functions +-both return the result of the operation, either the constant +-LDAP_SUCCESS if the operation was successful, or another LDAP error code +-if it was not. See the section below on error handling for more informa- +-tion about possible errors and how to interpret them. +- +-Note that if an LDAPv2 server is contacted, no other operations over the +-connection should be attempted before a bind call has successfully com- +-pleted. +- +-Subsequent bind calls can be used to re-authenticate over the same con- +-nection, and multistep SASL sequences can be accomplished through a +-sequence of calls to ldap_sasl_bind() or ldap_sasl_bind_s(). +- +- +-7.5. Closing the session +- +-The following functions are used to unbind from the directory, close the +-connection, and dispose of the session handle. +- +- int ldap_unbind( LDAP *ld ); +- +- int ldap_unbind_s( LDAP *ld ); +- +-Parameters are: +- +-ld The session handle. +- +-ldap_unbind() and ldap_unbind_s() both work synchronously, unbinding +-from the directory, closing the connection, and freeing up the ld struc- +-ture before returning. There is no server response to an unbind opera- +-tion. ldap_unbind() returns LDAP_SUCCESS (or another LDAP error code if +-the request cannot be sent to the LDAP server). After a call to +-ldap_unbind() or ldap_unbind_s(), the session handle ld is invalid. +- +- +-7.6. Searching +- +-The following functions are used to search the LDAP directory, returning +-a requested set of attributes for each entry matched. There are five +-variations. +- +- +- +-Expires: January 1998 [Page 13] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- int ldap_search_ext( +- LDAP *ld, +- char *base, +- int scope, +- char *filter, +- char **attrs, +- int attrsonly, +- LDAPControl **serverctrls, +- LDAPControl **clientctrls, +- struct timeval *timeoutp, +- int sizelimit, +- int *msgidp +- ); +- +- int ldap_search_ext_s( +- LDAP *ld, +- char *base, +- int scope, +- char *filter, +- char **attrs, +- int attrsonly, +- LDAPControl **serverctrls, +- LDAPControl **clientctrls, +- struct timeval *timeoutp, +- int sizelimit, +- LDAPMessage **res +- ); +- +- int ldap_search( +- LDAP *ld, +- char *base, +- int scope, +- char *filter, +- char **attrs, +- int attrsonly +- ); +- +- int ldap_search_s( +- LDAP *ld, +- char *base, +- int scope, +- char *filter, +- char **attrs, +- int attrsonly, +- LDAPMessage **res +- ); +- +- int ldap_search_st( +- +- +- +-Expires: January 1998 [Page 14] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- LDAP *ld, +- char *base, +- int scope, +- char *filter, +- char **attrs, +- int attrsonly, +- struct timeval *timeout, +- LDAPMessage **res +- ); +- +-Parameters are: +- +-ld The session handle. +- +-base The dn of the entry at which to start the search. +- +-scope One of LDAP_SCOPE_BASE (0x00), LDAP_SCOPE_ONELEVEL (0x01), +- or LDAP_SCOPE_SUBTREE (0x02), indicating the scope of the +- search. +- +-filter A character string as described in [3], representing the +- search filter. +- +-attrs A NULL-terminated array of strings indicating which attri- +- butes to return for each matching entry. Passing NULL for +- this parameter causes all available attributes to be +- retrieved. +- +-attrsonly A boolean value that should be zero if both attribute types +- and values are to be returned, non-zero if only types are +- wanted. +- +-timeout For the ldap_search_st() function, this specifies the local +- search timeout value. For the ldap_search_ext() and +- ldap_search_ext_s() functions, this specifies both the +- local search timeout value and the operation time limit +- that is sent to the server within the search request. +- +-res For the synchronous calls, this is a result parameter which +- will contain the results of the search upon completion of +- the call. +- +-serverctrls List of LDAP server controls. +- +-clientctrls List of client controls. +- +-msgidp This result parameter will be set to the message id of the +- request if the ldap_search_ext() call succeeds. +- +- +- +-Expires: January 1998 [Page 15] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-There are three options in the session handle ld which potentially +-affect how the search is performed. They are: +- +-LDAP_OPT_SIZELIMIT +- A limit on the number of entries to return from the search. +- A value of zero means no limit. Note that the value from +- the session handle is ignored when using the +- ldap_search_ext() or ldap_search_ext_s() functions. +- +-LDAP_OPT_TIMELIMIT +- A limit on the number of seconds to spend on the search. A +- value of zero means no limit. Note that the value from the +- session handle is ignored when using the ldap_search_ext() +- or ldap_search_ext_s() functions. +- +-LDAP_OPT_DEREF +- One of LDAP_DEREF_NEVER (0x00), LDAP_DEREF_SEARCHING +- (0x01), LDAP_DEREF_FINDING (0x02), or LDAP_DEREF_ALWAYS +- (0x03), specifying how aliases should be handled during the +- search. The LDAP_DEREF_SEARCHING value means aliases should +- be dereferenced during the search but not when locating the +- base object of the search. The LDAP_DEREF_FINDING value +- means aliases should be dereferenced when locating the base +- object but not during the search. +- +-The ldap_search_ext() function initiates an asynchronous search opera- +-tion and returns the constant LDAP_SUCCESS if the request was success- +-fully sent, or another LDAP error code if not. See the section below on +-error handling for more information about possible errors and how to +-interpret them. If successful, ldap_search_ext() places the message id +-of the request in *msgidp. A subsequent call to ldap_result(), described +-below, can be used to obtain the results from the search. These results +-can be parsed using the result parsing routines described in detail +-later. +- +-Similar to ldap_search_ext(), the ldap_search() function initiates an +-asynchronous search operation and returns the message id of the opera- +-tion initiated. As for ldap_search_ext(), a subsequent call to +-ldap_result(), described below, can be used to obtain the result of the +-bind. In case of error, ldap_search() will return -1, setting the ses- +-sion error parameters in the LDAP structure appropriately. +- +-The synchronous ldap_search_ext_s(), ldap_search_s(), and +-ldap_search_st() functions all return the result of the operation, +-either the constant LDAP_SUCCESS if the operation was successful, or +-another LDAP error code if it was not. See the section below on error +-handling for more information about possible errors and how to interpret +-them. Entries returned from the search (if any) are contained in the +- +- +- +-Expires: January 1998 [Page 16] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-res parameter. This parameter is opaque to the caller. Entries, attri- +-butes, values, etc., should be extracted by calling the parsing routines +-described below. The results contained in res should be freed when no +-longer in use by calling ldap_msgfree(), described later. +- +-The ldap_search_ext() and ldap_search_ext_s() functions support LDAPv3 +-server controls, client controls, and allow varying size and time limits +-to be easily specified for each search operation. The ldap_search_st() +-function is identical to ldap_search_s() except that it takes an addi- +-tional parameter specifying a local timeout for the search. +- +-7.7. Reading an Entry +- +-LDAP does not support a read operation directly. Instead, this operation +-is emulated by a search with base set to the DN of the entry to read, +-scope set to LDAP_SCOPE_BASE, and filter set to "(objectclass=*)". attrs +-contains the list of attributes to return. +- +- +-7.8. Listing the Children of an Entry +- +-LDAP does not support a list operation directly. Instead, this operation +-is emulated by a search with base set to the DN of the entry to list, +-scope set to LDAP_SCOPE_ONELEVEL, and filter set to "(objectclass=*)". +-attrs contains the list of attributes to return for each child entry. +- +-7.9. Comparing a Value Against an Entry +- +-The following routines are used to compare a given attribute value +-assertion against an LDAP entry. There are four variations: +- +- int ldap_compare_ext( +- LDAP *ld, +- char *dn, +- char *attr, +- struct berval *bvalue +- LDAPControl **serverctrls, +- LDAPControl **clientctrls, +- int *msgidp +- ); +- +- int ldap_compare_ext_s( +- LDAP *ld, +- char *dn, +- char *attr, +- struct berval *bvalue, +- LDAPControl **serverctrls, +- LDAPControl **clientctrls +- +- +- +-Expires: January 1998 [Page 17] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- ); +- +- int ldap_compare( +- LDAP *ld, +- char *dn, +- char *attr, +- char *value +- ); +- +- int ldap_compare_s( +- LDAP *ld, +- char *dn, +- char *attr, +- char *value +- ); +- +-Parameters are: +- +-ld The session handle. +- +-dn The name of the entry to compare against. +- +-attr The attribute to compare against. +- +-bvalue The attribute value to compare against those found in the +- given entry. This parameter is used in the extended rou- +- tines and is a pointer to a struct berval so it is possible +- to compare binary values. +- +-value A string attribute value to compare against, used by the +- ldap_compare() and ldap_compare_s() functions. Use +- ldap_compare_ext() or ldap_compare_ext_s() if you need to +- compare binary values. +- +-serverctrls List of LDAP server controls. +- +-clientctrls List of client controls. +- +-msgidp This result parameter will be set to the message id of the +- request if the ldap_compare_ext() call succeeds. +- +-The ldap_compare_ext() function initiates an asynchronous compare opera- +-tion and returns the constant LDAP_SUCCESS if the request was success- +-fully sent, or another LDAP error code if not. See the section below on +-error handling for more information about possible errors and how to +-interpret them. If successful, ldap_compare_ext() places the message id +-of the request in *msgidp. A subsequent call to ldap_result(), described +-below, can be used to obtain the result of the compare. +- +- +- +-Expires: January 1998 [Page 18] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-Similar to ldap_compare_ext(), the ldap_compare() function initiates an +-asynchronous compare operation and returns the message id of the opera- +-tion initiated. As for ldap_compare_ext(), a subsequent call to +-ldap_result(), described below, can be used to obtain the result of the +-bind. In case of error, ldap_compare() will return -1, setting the ses- +-sion error parameters in the LDAP structure appropriately. +- +-The synchronous ldap_compare_ext_s() and ldap_compare_s() functions both +-return the result of the operation, either the constant LDAP_SUCCESS if +-the operation was successful, or another LDAP error code if it was not. +-See the section below on error handling for more information about pos- +-sible errors and how to interpret them. +- +-The ldap_compare_ext() and ldap_compare_ext_s() functions support LDAPv3 +-server controls and client controls. +- +- +-7.10. Modifying an entry +- +-The following routines are used to modify an existing LDAP entry. There +-are four variations: +- +- typedef struct ldapmod { +- int mod_op; +- char *mod_type; +- union { +- char **modv_strvals; +- struct berval **modv_bvals; +- } mod_vals; +- } LDAPMod; +- #define mod_values mod_vals.modv_strvals +- #define mod_bvalues mod_vals.modv_bvals +- +- int ldap_modify_ext( +- LDAP *ld, +- char *dn, +- LDAPMod **mods, +- LDAPControl **serverctrls, +- LDAPControl **clientctrls, +- int *msgidp +- ); +- +- int ldap_modify_ext_s( +- LDAP *ld, +- char *dn, +- LDAPMod **mods, +- LDAPControl **serverctrls, +- LDAPControl **clientctrls +- +- +- +-Expires: January 1998 [Page 19] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- ); +- +- int ldap_modify( +- LDAP *ld, +- char *dn, +- LDAPMod **mods +- ); +- +- int ldap_modify_s( +- LDAP *ld, +- char *dn, +- LDAPMod **mods +- ); +- +-Parameters are: +- +-ld The session handle. +- +-dn The name of the entry to modify. +- +-mods A NULL-terminated array of modifications to make to the +- entry. +- +-serverctrls List of LDAP server controls. +- +-clientctrls List of client controls. +- +-msgidp This result parameter will be set to the message id of the +- request if the ldap_modify_ext() call succeeds. +- +-The fields in the LDAPMod structure have the following meanings: +- +-mod_op The modification operation to perform. It should be one of +- LDAP_MOD_ADD (0x00), LDAP_MOD_DELETE (0x01), or +- LDAP_MOD_REPLACE (0x02). This field also indicates the +- type of values included in the mod_vals union. It is logi- +- cally ORed with LDAP_MOD_BVALUES (0x80) to select the +- mod_bvalues form. Otherwise, the mod_values form is used. +- +-mod_type The type of the attribute to modify. +- +-mod_vals The values (if any) to add, delete, or replace. Only one of +- the mod_values or mod_bvalues variants should be used, +- selected by ORing the mod_op field with the constant +- LDAP_MOD_BVALUES. mod_values is a NULL-terminated array of +- zero-terminated strings and mod_bvalues is a NULL- +- terminated array of berval structures that can be used to +- pass binary values such as images. +- +- +- +-Expires: January 1998 [Page 20] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-For LDAP_MOD_ADD modifications, the given values are added to the +-entry, creating the attribute if necessary. +- +-For LDAP_MOD_DELETE modifications, the given values are deleted from the +-entry, removing the attribute if no values remain. If the entire attri- +-bute is to be deleted, the mod_vals field should be set to NULL. +- +-For LDAP_MOD_REPLACE modifications, the attribute will have the listed +-values after the modification, having been created if necessary, or +-removed if the mod_vals field is NULL. All modifications are performed +-in the order in which they are listed. +- +-The ldap_modify_ext() function initiates an asynchronous modify opera- +-tion and returns the constant LDAP_SUCCESS if the request was success- +-fully sent, or another LDAP error code if not. See the section below on +-error handling for more information about possible errors and how to +-interpret them. If successful, ldap_modify_ext() places the message id +-of the request in *msgidp. A subsequent call to ldap_result(), described +-below, can be used to obtain the result of the modify. +- +-Similar to ldap_modify_ext(), the ldap_modify() function initiates an +-asynchronous modify operation and returns the message id of the opera- +-tion initiated. As for ldap_modify_ext(), a subsequent call to +-ldap_result(), described below, can be used to obtain the result of the +-modify. In case of error, ldap_modify() will return -1, setting the ses- +-sion error parameters in the LDAP structure appropriately. +- +-The synchronous ldap_modify_ext_s() and ldap_modify_s() functions both +-return the result of the operation, either the constant LDAP_SUCCESS if +-the operation was successful, or another LDAP error code if it was not. +-See the section below on error handling for more information about pos- +-sible errors and how to interpret them. +- +-The ldap_modify_ext() and ldap_modify_ext_s() functions support LDAPv3 +-server controls and client controls. +- +- +-7.11. Modifying the Name of an Entry +- +-In LDAPv2, the ldap_modrdn() and ldap_modrdn_s() routines were used to +-change the name of an LDAP entry. They could only be used to change the +-least significant component of a name (the RDN or relative distinguished +-name). LDAPv3 provides the Modify DN protocol operation that allows more +-general name change access. The ldap_rename() and ldap_rename_s() rou- +-tines are used to change the name of an entry, and the use of the +-ldap_modrdn() and ldap_modrdn_s() routines is deprecated. +- +- int ldap_rename( +- +- +- +-Expires: January 1998 [Page 21] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- LDAP *ld, +- char *dn, +- char *newrdn, +- char *newparent, +- int deleteoldrdn, +- LDAPControl **serverctrls, +- LDAPControl **clientctrls, +- int *msgidp +- +- ); +- int ldap_rename_s( +- LDAP *ld, +- char *dn, +- char *newrdn, +- char *newparent, +- int deleteoldrdn, +- LDAPControl **serverctrls, +- LDAPControl **clientctrls +- ); +- +- Use of the following routines is deprecated. +- +- int ldap_modrdn( +- LDAP *ld, +- char *dn, +- char *newrdn, +- int deleteoldrdn +- ); +- int ldap_modrdn_s( +- LDAP *ld, +- char *dn, +- char *newrdn, +- int deleteoldrdn +- ); +- +-Parameters are: +- +-ld The session handle. +- +-dn The name of the entry whose DN is to be changed. +- +-newrdn The new RDN to give the entry. +- +-newparent The new parent, or superior entry. If this parameter is +- NULL, only the RDN of the entry is changed. The root DN +- may be specified by passing a zero length string, "". The +- newparent parameter should always be NULL when using ver- +- sion 2 of the LDAP protocol; otherwise the server's +- +- +- +-Expires: January 1998 [Page 22] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- behavior is undefined. +- +-deleteoldrdn This parameter only has meaning on the rename routines if +- newrdn is different than the old RDN. It is a boolean +- value, if non-zero indicating that the old RDN value(s) +- should be removed, if zero indicating that the old RDN +- value(s) should be retained as non-distinguished values of +- the entry. +- +-serverctrls List of LDAP server controls. +- +-clientctrls List of client controls. +- +-msgidp This result parameter will be set to the message id of the +- request if the ldap_rename() call succeeds. +- +-The ldap_rename() function initiates an asynchronous modify DN operation +-and returns the constant LDAP_SUCCESS if the request was successfully +-sent, or another LDAP error code if not. See the section below on error +-handling for more information about possible errors and how to interpret +-them. If successful, ldap_rename() places the DN message id of the +-request in *msgidp. A subsequent call to ldap_result(), described below, +-can be used to obtain the result of the rename. +- +-The synchronous ldap_rename_s() returns the result of the operation, +-either the constant LDAP_SUCCESS if the operation was successful, or +-another LDAP error code if it was not. See the section below on error +-handling for more information about possible errors and how to interpret +-them. +- +-The ldap_rename() and ldap_rename_s() functions both support LDAPv3 +-server controls and client controls. +- +- +-7.12. Adding an entry +- +-The following functions are used to add entries to the LDAP directory. +-There are four variations: +- +- int ldap_add_ext( +- LDAP *ld, +- char *dn, +- LDAPMod **attrs, +- LDAPControl **serverctrls, +- LDAPControl **clientctrls, +- int *msgidp +- ); +- +- +- +- +-Expires: January 1998 [Page 23] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- int ldap_add_ext_s( +- LDAP *ld, +- char *dn, +- LDAPMod **attrs, +- LDAPControl **serverctrls, +- LDAPControl **clientctrls +- ); +- +- int ldap_add( +- LDAP *ld, +- char *dn, +- LDAPMod **attrs +- ); +- +- int ldap_add_s( +- LDAP *ld, +- char *dn, +- LDAPMod **attrs +- ); +- +-Parameters are: +- +-ld The session handle. +- +-dn The name of the entry to add. +- +-attrs The entry's attributes, specified using the LDAPMod struc- +- ture defined for ldap_modify(). The mod_type and mod_vals +- fields should be filled in. The mod_op field is ignored +- unless ORed with the constant LDAP_MOD_BVALUES, used to +- select the mod_bvalues case of the mod_vals union. +- +-serverctrls List of LDAP server controls. +- +-clientctrls List of client controls. +- +-msgidp This result parameter will be set to the message id of the +- request if the ldap_add_ext() call succeeds. +- +-Note that the parent of the entry being added must already exist or the +-parent must be empty (i.e., equal to the root DN) for an add to succeed. +- +-The ldap_add_ext() function initiates an asynchronous add operation and +-returns the constant LDAP_SUCCESS if the request was successfully sent, +-or another LDAP error code if not. See the section below on error han- +-dling for more information about possible errors and how to interpret +-them. If successful, ldap_add_ext() places the message id of the +-request in *msgidp. A subsequent call to ldap_result(), described below, +- +- +- +-Expires: January 1998 [Page 24] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-can be used to obtain the result of the add. +- +-Similar to ldap_add_ext(), the ldap_add() function initiates an asyn- +-chronous add operation and returns the message id of the operation ini- +-tiated. As for ldap_add_ext(), a subsequent call to ldap_result(), +-described below, can be used to obtain the result of the add. In case of +-error, ldap_add() will return -1, setting the session error parameters +-in the LDAP structure appropriately. +- +-The synchronous ldap_add_ext_s() and ldap_add_s() functions both return +-the result of the operation, either the constant LDAP_SUCCESS if the +-operation was successful, or another LDAP error code if it was not. See +-the section below on error handling for more information about possible +-errors and how to interpret them. +- +-The ldap_add_ext() and ldap_add_ext_s() functions support LDAPv3 server +-controls and client controls. +- +- +- +-7.13. Deleting an entry +- +-The following functions are used to delete a leaf entry from the LDAP +-directory. There are four variations: +- +- int ldap_delete_ext( +- LDAP *ld, +- char *dn, +- LDAPControl **serverctrls, +- LDAPControl **clientctrls, +- int *msgidp +- ); +- +- int ldap_delete_ext_s( +- LDAP *ld, +- char *dn, +- LDAPControl **serverctrls, +- LDAPControl **clientctrls +- ); +- +- int ldap_delete( +- LDAP *ld, +- char *dn +- ); +- +- int ldap_delete_s( +- LDAP *ld, +- char *dn +- +- +- +-Expires: January 1998 [Page 25] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- ); +- +-Parameters are: +- +-ld The session handle. +- +-dn The name of the entry to delete. +- +-serverctrls List of LDAP server controls. +- +-clientctrls List of client controls. +- +-msgidp This result parameter will be set to the message id of the +- request if the ldap_delete_ext() call succeeds. +- +-Note that the entry to delete must be a leaf entry (i.e., it must have +-no children). Deletion of entire subtrees in a single operation is not +-supported by LDAP. +- +-The ldap_delete_ext() function initiates an asynchronous delete opera- +-tion and returns the constant LDAP_SUCCESS if the request was success- +-fully sent, or another LDAP error code if not. See the section below on +-error handling for more information about possible errors and how to +-interpret them. If successful, ldap_delete_ext() places the message id +-of the request in *msgidp. A subsequent call to ldap_result(), described +-below, can be used to obtain the result of the delete. +- +-Similar to ldap_delete_ext(), the ldap_delete() function initiates an +-asynchronous delete operation and returns the message id of the opera- +-tion initiated. As for ldap_delete_ext(), a subsequent call to +-ldap_result(), described below, can be used to obtain the result of the +-delete. In case of error, ldap_delete() will return -1, setting the ses- +-sion error parameters in the LDAP structure appropriately. +- +-The synchronous ldap_delete_ext_s() and ldap_delete_s() functions both +-return the result of the operation, either the constant LDAP_SUCCESS if +-the operation was successful, or another LDAP error code if it was not. +-See the section below on error handling for more information about pos- +-sible errors and how to interpret them. +- +-The ldap_delete_ext() and ldap_delete_ext_s() functions support LDAPv3 +-server controls and client controls. +- +- +-7.14. Extended Operations +- +-The ldap_extended_operation() and ldap_extended_operation_s() routines +-allow extended LDAP operations to be passed to the server, providing a +- +- +- +-Expires: January 1998 [Page 26] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-general protocol extensibility mechanism. +- +- int ldap_extended_operation( +- LDAP *ld, +- char *exoid, +- struct berval *exdata, +- LDAPControl **serverctrls, +- LDAPControl **clientctrls, +- int *msgidp +- ); +- +- int ldap_extended_operation_s( +- LDAP *ld, +- char *exoid, +- struct berval *exdata, +- LDAPControl **serverctrls, +- LDAPControl **clientctrls, +- char **retoidp, +- struct berval **retdatap +- ); +- +-Parameters are: +- +-ld The session handle. +- +-requestoid The dotted-OID text string naming the request. +- +-requestdata The arbitrary data required by the operation (if NULL, no +- data is sent to the server). +- +-serverctrls List of LDAP server controls. +- +-clientctrls List of client controls. +- +-msgidp This result parameter will be set to the message id of the +- request if the ldap_extended_operation() call succeeds. +- +-retoidp Pointer to a character string that will be set to an allo- +- cated, dotted-OID text string returned by the server. This +- string should be disposed of using the ldap_memfree() func- +- tion. If no OID was returned, *retoidp is set to NULL. +- +-retdatap Pointer to a berval structure pointer that will be set an +- allocated copy of the data returned by the server. This +- struct berval should be disposed of using ber_bvfree(). If +- no data is returned, *retdatap is set to NULL. +- +-The ldap_extended_operation() function initiates an asynchronous +- +- +- +-Expires: January 1998 [Page 27] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-extended operation and returns the constant LDAP_SUCCESS if the request +-was successfully sent, or another LDAP error code if not. See the sec- +-tion below on error handling for more information about possible errors +-and how to interpret them. If successful, ldap_extended_operation() +-places the message id of the request in *msgidp. A subsequent call to +-ldap_result(), described below, can be used to obtain the result of the +-extended operation which can be passed to ldap_parse_extended_result() +-to obtain the OID and data contained in the response. +- +-The synchronous ldap_extended_operation_s() function returns the result +-of the operation, either the constant LDAP_SUCCESS if the operation was +-successful, or another LDAP error code if it was not. See the section +-below on error handling for more information about possible errors and +-how to interpret them. The retoid and retdata parameters are filled in +-with the OID and data from the response. If no OID or data was +-returned, these parameters are set to NULL. +- +-The ldap_extended_operation() and ldap_extended_operation_s() functions +-both support LDAPv3 server controls and client controls. +- +- +-8. Abandoning An Operation +- +-The following calls are used to abandon an operation in progress: +- +- int ldap_abandon_ext( +- LDAP *ld, +- int msgid, +- LDAPControl **serverctrls, +- LDAPControl **clientctrls +- ); +- +- int ldap_abandon( +- LDAP *ld, +- int msgid +- ); +- +- +-ld The session handle. +- +-msgid The message id of the request to be abandoned. +- +-serverctrls List of LDAP server controls. +- +-clientctrls List of client controls. +- +-ldap_abandon_ext() abandons the operation with message id msgid and +-returns the constant LDAP_SUCCESS if the abandon was successful or +- +- +- +-Expires: January 1998 [Page 28] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-another LDAP error code if not. See the section below on error handling +-for more information about possible errors and how to interpret them. +- +-ldap_abandon() is identical to ldap_abandon_ext() except that it returns +-zero if the abandon was successful, -1 otherwise and does not support +-LDAPv3 server controls or client controls. +- +-After a successful call to ldap_abandon() or ldap_abandon_ext(), results +-with the given message id are never returned from a subsequent call to +-ldap_result(). There is no server response to LDAP abandon operations. +- +- +-9. Obtaining Results and Peeking Inside LDAP Messages +- +-ldap_result() is used to obtain the result of a previous asynchronously +-initiated operation. Note that depending on how it is called, +-ldap_result() may actually return a list or "chain" of messages. +- +-ldap_msgfree() frees the results obtained from a previous call to +-ldap_result(), or a synchronous search routine. +- +-ldap_msgtype() returns the type of an LDAP message. ldap_msgid() +-returns the message ID of an LDAP message. +- +- int ldap_result( +- LDAP *ld, +- int msgid, +- int all, +- struct timeval *timeout, +- LDAPMessage **res +- ); +- +- int ldap_msgfree( LDAPMessage *res ); +- +- int ldap_msgtype( LDAPMessage *res ); +- +- int ldap_msgid( LDAPMessage *res ); +- +-Parameters are: +- +-ld The session handle. +- +-msgid The message id of the operation whose results are to be +- returned, or the constant LDAP_RES_ANY (-1) if any result is +- desired. +- +-all Specifies how many messages will be retrieved in a single call +- to ldap_result(). This parameter only has meaning for search +- +- +- +-Expires: January 1998 [Page 29] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- results. Pass the constant LDAP_MSG_ONE (0x00) to retrieve one +- message at a time. Pass LDAP_MSG_ALL (0x01) to request that +- all results of a search be received before returning all +- results in a single chain. Pass LDAP_MSG_RECEIVED (0x02) to +- indicate that all results retrieved so far should be returned +- in the result chain. +- +-timeout A timeout specifying how long to wait for results to be +- returned. A NULL value causes ldap_result() to block until +- results are available. A timeout value of zero seconds speci- +- fies a polling behavior. +- +-res For ldap_result(), a result parameter that will contain the +- result(s) of the operation. For ldap_msgfree(), the result +- chain to be freed, obtained from a previous call to +- ldap_result(), ldap_search_s(), or ldap_search_st(). +- +-Upon successful completion, ldap_result() returns the type of the first +-result returned in the res parameter. This will be one of the following +-constants. +- +- LDAP_RES_BIND (0x61) +- LDAP_RES_SEARCH_ENTRY (0x64) +- LDAP_RES_SEARCH_REFERENCE (0x73) -- new in LDAPv3 +- LDAP_RES_SEARCH_RESULT (0x65) +- LDAP_RES_MODIFY (0x67) +- LDAP_RES_ADD (0x69) +- LDAP_RES_DELETE (0x6B) +- LDAP_RES_MODDN (0x6D) +- LDAP_RES_COMPARE (0x6F) +- LDAP_RES_EXTENDED (0x78) -- new in LDAPv3 +- +-ldap_result() returns 0 if the timeout expired and -1 if an error +-occurs, in which case the error parameters of the LDAP session handle +-will be set accordingly. +- +-ldap_msgfree() frees the result structure pointed to by res and returns +-the type of the message it freed. +- +-ldap_msgtype() returns the type of the LDAP message it is passed as a +-parameter. The type will be one of the types listed above, or -1 on +-error. +- +-ldap_msgid() returns the message ID associated with the LDAP message +-passed as a parameter. +- +- +- +- +- +- +-Expires: January 1998 [Page 30] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-10. Handling Errors and Parsing Results +- +-The following calls are used to extract information from results and +-handle errors returned by other LDAP API routines. +- +- int ldap_parse_result( +- LDAP *ld, +- LDAPMessage *res, +- int *errcodep, +- char **matcheddnp, +- char **errmsgp, +- char ***referralsp, +- LDAPControl ***serverctrlsp, +- int freeit +- ); +- +- int ldap_parse_sasl_bind_result( +- LDAP *ld, +- LDAPMessage *res, +- struct berval **servercredp, +- int freeit +- ); +- +- int ldap_parse_extended_result( +- LDAP *ld, +- LDAPMessage *res, +- char **resultoidp, +- struct berval **resultdata, +- int freeit +- ); +- +- char *ldap_err2string( int err ); +- +- The use of the following routines is deprecated. +- +- int ldap_result2error( +- LDAP *ld, +- LDAPMessage *res, +- int freeit +- ); +- +- void ldap_perror( LDAP *ld, char *msg ); +- +-Parameters are: +- +-ld The session handle. +- +-res The result of an LDAP operation as returned by +- +- +- +-Expires: January 1998 [Page 31] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- ldap_result() or one of the synchronous API operation +- calls. +- +-errcodep This result parameter will be filled in with the LDAP error +- code field from the LDAPResult message. This is the indi- +- cation from the server of the outcome of the operation. +- NULL may be passed to ignore this field. +- +-matcheddnp In the case of a return of LDAP_NO_SUCH_OBJECT, this result +- parameter will be filled in with a DN indicating how much +- of the name in the request was recognized. NULL may be +- passed to ignore this field. The matched DN string should +- be freed by calling ldap_memfree() which is described later +- in this document. +- +-errmsgp This result parameter will be filled in with the contents +- of the error message field from the LDAPResult message. +- The error message string should be freed by calling +- ldap_memfree() which is described later in this document. +- NULL may be passed to ignore this field. +- +-referralsp This result parameter will be filled in with the contents +- of the referrals field from the LDAPResult message, indi- +- cating zero or more alternate LDAP servers where the +- request should be retried. The referrals array should be +- freed by calling ldap_value_free() which is described later +- in this document. NULL may be passed to ignore this field. +- +-serverctrlsp This result parameter will be filled in with an allocated +- array of controls copied out of the LDAPResult message. +- The control array should be freed by calling +- ldap_controls_free() which was described earlier. +- +-freeit A boolean that determines whether the res parameter is +- disposed of or not. Pass any non-zero value to have these +- routines free res after extracting the requested informa- +- tion. This is provided as a convenience; you can also use +- ldap_msgfree() to free the result later. +- +-servercredp For SASL bind results, this result parameter will be filled +- in with the credentials passed back by the server for +- mutual authentication, if given. An allocated berval struc- +- ture is returned that should be disposed of by calling +- ldap_ber_free(). NULL may be passed to ignore this field. +- +-resultoidp For extended results, this result parameter will be filled +- in with the dotted-OID text representation of the name of +- the extended operation response. This string should be +- +- +- +-Expires: January 1998 [Page 32] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- disposed of by calling ldap_memfree(). NULL may be passed +- to ignore this field. +- +-resultdatap For extended results, this result parameter will be filled +- in with a pointer to a struct berval containing the data in +- the extended operation response. It should be disposed of +- by calling ldap_ber_free(). NULL may be passed to ignore +- this field. +- +-err For ldap_err2string(), an LDAP error code, as returned by +- ldap_result2error() or another LDAP API call. +- +-Additional parameters for the deprecated routines are not described. +-Interested readers are referred to RFC 1823. +- +-All of the ldap_parse_*_result() routines skip over messages of type +-LDAP_RES_SEARCH_ENTRY and LDAP_RES_SEARCH_REFERENCE when looking for a +-result message to parse. They return the constant LDAP_SUCCESS if the +-result was successfully parsed and another LDAP error code if not. Note +-that the LDAP error code that indicates the outcome of the operation +-performed by the server is placed in the errcodep ldap_parse_result() +-parameter. +- +-ldap_err2string() is used to convert a numeric LDAP error code, as +-returned by one of the ldap_parse_*_result() routines, or one of the +-synchronous API operation calls, into an informative NULL-terminated +-character string message describing the error. It returns a pointer to +-static data. +- +- +-11. Stepping Through a List of Results +- +-The ldap_first_message() and ldap_next_message() routines are used to +-step through the list of messages in a result chain returned by +-ldap_result(). For search operations, the result chain may actually +-include referral messages, entry messages, and result messages. +-ldap_count_messages() is used to count the number of messages returned. +-The ldap_msgtype() function, described above, can be used to distinguish +-between the different message types. +- +- LDAPMessage *ldap_first_message( LDAP *ld, LDAPMessage *res ); +- +- LDAPMessage *ldap_next_message( LDAP *ld, LDAPMessage *msg ); +- +- int ldap_count_messages( LDAP *ld, LDAPMessage *res ); +- +-Parameters are: +- +- +- +- +-Expires: January 1998 [Page 33] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-ld The session handle. +- +-res The result chain, as obtained by a call to one of the synchronous +- search routines or ldap_result(). +- +-msg The message returned by a previous call to ldap_first_message() +- or ldap_next_message(). +- +-ldap_first_message() and ldap_next_message() will return NULL when no +-more messages exist in the result set to be returned. NULL is also +-returned if an error occurs while stepping through the entries, in which +-case the error parameters in the session handle ld will be set to indi- +-cate the error. +- +-ldap_count_messages() returns the number of messages contained in a +-chain of results. It can also be used to count the number of messages +-that remain in a chain if called with a message, entry, or reference +-returned by ldap_first_message(), ldap_next_message(), +-ldap_first_entry(), ldap_next_entry(), ldap_first_reference(), +-ldap_next_reference(). +- +- +-12. Parsing Search Results +- +-The following calls are used to parse the entries and references +-returned by ldap_search() and friends. These results are returned in an +-opaque structure that should only be accessed by calling the routines +-described below. Routines are provided to step through the entries and +-references returned, step through the attributes of an entry, retrieve +-the name of an entry, and retrieve the values associated with a given +-attribute in an entry. +- +- +-12.1. Stepping Through a List of Entries +- +-The ldap_first_entry() and ldap_next_entry() routines are used to step +-through and retrieve the list of entries from a search result chain. +-The ldap_first_reference() and ldap_next_reference() routines are used +-to step through and retrieve the list of continuation references from a +-search result chain. ldap_count_entries() is used to count the number +-of entries returned. ldap_count_references() is used to count the number +-of references returned. +- +- LDAPMessage *ldap_first_entry( LDAP *ld, LDAPMessage *res ); +- +- LDAPMessage *ldap_next_entry( LDAP *ld, LDAPMessage *entry ); +- +- LDAPMessage *ldap_first_reference( LDAP *ld, LDAPMessage *res ); +- +- +- +-Expires: January 1998 [Page 34] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- LDAPMessage *ldap_next_reference( LDAP *ld, LDAPMessage *ref ); +- +- int ldap_count_entries( LDAP *ld, LDAPMessage *res ); +- +- int ldap_count_references( LDAP *ld, LDAPMessage *res ); +- +-Parameters are: +- +-ld The session handle. +- +-res The search result, as obtained by a call to one of the synchro- +- nous search routines or ldap_result(). +- +-entry The entry returned by a previous call to ldap_first_entry() or +- ldap_next_entry(). +- +-ldap_first_entry() and ldap_next_entry() will return NULL when no more +-entries or references exist in the result set to be returned. NULL is +-also returned if an error occurs while stepping through the entries, in +-which case the error parameters in the session handle ld will be set to +-indicate the error. +- +-ldap_count_entries() returns the number of entries contained in a chain +-of entries. It can also be used to count the number of entries that +-remain in a chain if called with a message, entry or reference returned +-by ldap_first_message(), ldap_next_message(), ldap_first_entry(), +-ldap_next_entry(), ldap_first_reference(), ldap_next_reference(). +- +-ldap_count_references() returns the number of references contained in a +-chain of search results. It can also be used to count the number of +-references that remain in a chain. +- +- +-12.2. Stepping Through the Attributes of an Entry +- +-The ldap_first_attribute() and ldap_next_attribute() calls are used to +-step through the list of attribute types returned with an entry. +- +- char *ldap_first_attribute( +- LDAP *ld, +- LDAPMessage *entry, +- BerElement **ptr +- ); +- +- char *ldap_next_attribute( +- LDAP *ld, +- LDAPMessage *entry, +- BerElement *ptr +- +- +- +-Expires: January 1998 [Page 35] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- ); +- +- void ldap_memfree( char *mem ); +- +-Parameters are: +- +-ld The session handle. +- +-entry The entry whose attributes are to be stepped through, as returned +- by ldap_first_entry() or ldap_next_entry(). +- +-ptr In ldap_first_attribute(), the address of a pointer used inter- +- nally to keep track of the current position in the entry. In +- ldap_next_attribute(), the pointer returned by a previous call to +- ldap_first_attribute(). +- +-mem A pointer to memory allocated by the LDAP library, such as the +- attribute names returned by ldap_first_attribute() and +- ldap_next_attribute, or the DN returned by ldap_get_dn(). +- +-ldap_first_attribute() and ldap_next_attribute() will return NULL when +-the end of the attributes is reached, or if there is an error, in which +-case the error parameters in the session handle ld will be set to indi- +-cate the error. +- +-Both routines return a pointer to an allocated buffer containing the +-current attribute name. This should be freed when no longer in use by +-calling ldap_memfree(). +- +-ldap_first_attribute() will allocate and return in ptr a pointer to a +-BerElement used to keep track of the current position. This pointer +-should be passed in subsequent calls to ldap_next_attribute() to step +-through the entry's attributes. After a set of calls to +-ldap_first_attribute() and ldap_next_attibute(), if ptr is non-NULL, it +-should be freed by calling ldap_ber_free( ptr, 0 ). Note that it is very +-important to pass the second parameter as 0 (zero) in this call. +- +-The attribute names returned are suitable for passing in a call to +-ldap_get_values() and friends to retrieve the associated values. +- +- +-12.3. Retrieving the Values of an Attribute +- +-ldap_get_values() and ldap_get_values_len() are used to retrieve the +-values of a given attribute from an entry. ldap_count_values() and +-ldap_count_values_len() are used to count the returned values. +-ldap_value_free() and ldap_value_free_len() are used to free the values. +- +- +- +- +-Expires: January 1998 [Page 36] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- char **ldap_get_values( +- LDAP *ld, +- LDAPMessage *entry, +- char *attr +- ); +- +- struct berval **ldap_get_values_len( +- LDAP *ld, +- LDAPMessage *entry, +- char *attr +- ); +- +- int ldap_count_values( char **vals ); +- +- int ldap_count_values_len( struct berval **vals ); +- +- int ldap_value_free( char **vals ); +- +- int ldap_value_free_len( struct berval **vals ); +- +-Parameters are: +- +-ld The session handle. +- +-entry The entry from which to retrieve values, as returned by +- ldap_first_entry() or ldap_next_entry(). +- +-attr The attribute whose values are to be retrieved, as returned by +- ldap_first_attribute() or ldap_next_attribute(), or a caller- +- supplied string (e.g., "mail"). +- +-vals The values returned by a previous call to ldap_get_values() or +- ldap_get_values_len(). +- +-Two forms of the various calls are provided. The first form is only +-suitable for use with non-binary character string data. The second _len +-form is used with any kind of data. +- +-Note that the values returned are dynamically allocated and should be +-freed by calling either ldap_value_free() or ldap_value_free_len() when +-no longer in use. +- +- +-12.4. Retrieving the name of an entry +- +-ldap_get_dn() is used to retrieve the name of an entry. +-ldap_explode_dn() and ldap_explode_rdn() are used to break up a name +-into its component parts. ldap_dn2ufn() is used to convert the name into +- +- +- +-Expires: January 1998 [Page 37] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-a more "user friendly" format. +- +- char *ldap_get_dn( LDAP *ld, LDAPMessage *entry ); +- +- char **ldap_explode_dn( char *dn, int notypes ); +- +- char **ldap_explode_rdn( char *rdn, int notypes ); +- +- char *ldap_dn2ufn( char *dn ); +- +-Parameters are: +- +-ld The session handle. +- +-entry The entry whose name is to be retrieved, as returned by +- ldap_first_entry() or ldap_next_entry(). +- +-dn The dn to explode, such as returned by ldap_get_dn(). +- +-rdn The rdn to explode, such as returned in the components of the +- array returned by ldap_explode_dn(). +- +-notypes A boolean parameter, if non-zero indicating that the dn or rdn +- components should have their type information stripped off +- (i.e., "cn=Babs" would become "Babs"). +- +-ldap_get_dn() will return NULL if there is some error parsing the dn, +-setting error parameters in the session handle ld to indicate the error. +-It returns a pointer to malloc'ed space that the caller should free by +-calling ldap_memfree() when it is no longer in use. Note the format of +-the DNs returned is given by [4]. +- +-ldap_explode_dn() returns a NULL-terminated char * array containing the +-RDN components of the DN supplied, with or without types as indicated by +-the notypes parameter. The array returned should be freed when it is no +-longer in use by calling ldap_value_free(). +- +-ldap_explode_rdn() returns a NULL-terminated char * array containing the +-components of the RDN supplied, with or without types as indicated by +-the notypes parameter. The array returned should be freed when it is no +-longer in use by calling ldap_value_free(). +- +-ldap_dn2ufn() converts the DN into the user friendly format described in +-[5]. The UFN returned is malloc'ed space that should be freed by a call +-to ldap_memfree() when no longer in use. +- +- +- +- +- +- +-Expires: January 1998 [Page 38] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-13. Encoded ASN.1 Value Manipulation +- +-This section describes routines which may be used to encode and decode +-BER-encoded ASN.1 values, which are often used inside of control and +-extension values. +- +-With the exceptions of two new functions ber_flatten() and ber_init(), +-these functions are compatible with the University of Michigan LDAP 3.3 +-implementation of BER. +- +- +-13.1. General +- +- struct berval { +- unsigned long bv_len; +- char *bv_val; +- }; +- +-A struct berval contains a sequence of bytes and an indication of its +-length. The bv_val is not null terminated. bv_len must always be a +-nonnegative number. Applications may allocate their own berval struc- +-tures. +- +- typedef struct berelement { +- /* opaque */ +- } BerElement; +- +-The BerElement structure contains not only a copy of the encoded value, +-but also state information used in encoding or decoding. Applications +-cannot allocate their own BerElement structures. The internal state is +-neither thread-specific nor locked, so two threads should not manipulate +-the same BerElement value simultaneously. +- +-A single BerElement value cannot be used for both encoding and decoding. +- +- void ber_bvfree ( struct berval *bv); +- +-ber_bvfree() frees a berval returned from this API. Both the bv->bv_val +-string and the berval itself are freed. Applications should not use +-ber_bvfree() with bervals which the application has allocated. +- +- void ber_bvecfree ( struct berval **bv ); +- +-ber_bvecfree() frees an array of bervals returned from this API. Each +-of the bervals in the array are freed using ber_bvfree(), then the array +-itself is freed. +- +- struct berval *ber_bvdup (struct berval *bv ); +- +- +- +-Expires: January 1998 [Page 39] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-ber_bvdup() returns a copy of a berval. The bv_val field in the +-returned berval points to a different area of memory as the bv_val field +-in the argument berval. The null pointer is returned on error (e.g. out +-of memory). +- +- void ber_free ( BerElement *ber, int fbuf ); +- +-ber_free() frees a BerElement which is returned from the API calls +-ber_alloc_t() or ber_init(). Each BerElement must be freed by the +-caller. The second argument fbuf should always be set to 1. +- +- +-13.2. Encoding +- +- BerElement *ber_alloc_t(int options); +- +-ber_alloc_t() constructs and returns BerElement. The null pointer is +-returned on error. The options field contains a bitwise-or of options +-which are to be used when generating the encoding of this BerElement. +-One option is defined and must always be supplied: +- +- #define LBER_USE_DER 0x01 +- +-When this option is present, lengths will always be encoded in the +-minimum number of octets. Note that this option does not cause values +-of sets and sequences to be rearranged in tag and byte order, so these +-functions are not suitable for generating DER output as defined in X.509 +-and X.680. +- +-Unrecognized option bits are ignored. +- +-The BerElement returned by ber_alloc_t() is initially empty. Calls to +-ber_printf() will append bytes to the end of the ber_alloc_t(). +- +- int ber_printf(BerElement *ber, char *fmt, ... ) +- +-The ber_printf() routine is used to encode a BER element in much the +-same way that sprintf() works. One important difference, though, is +-that state information is kept in the ber argument so that multiple +-calls can be made to ber_printf() to append to the end of the BER ele- +-ment. ber must be a pointer to a BerElement returned by ber_alloc_t(). +-ber_printf() interprets and formats its arguments according to the for- +-mat string fmt. ber_printf() returns -1 if there is an error during +-encoding. As with sprintf(), each character in fmt refers to an argu- +-ment to ber_printf(). +- +-The format string can contain the following format characters: +- +- +- +- +-Expires: January 1998 [Page 40] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-'t' Tag. The next argument is an int specifying the tag to override +- the next element to be written to the ber. This works across +- calls. The int value must contain the tag class, constructed +- bit, and tag value. The tag value must fit in a single octet +- (tag value is less than 32). For example, a tag of "[3]" for a +- constructed type is 0xA3. +- +-'b' Boolean. The next argument is an int, containing either 0 for +- FALSE or 0xff for TRUE. A boolean element is output. If this +- format character is not preceded by the 't' format modifier, the +- tag 0x01 is used for the element. +- +-'i' Integer. The next argument is an int, containing the integer in +- the host's byte order. An integer element is output. If this +- format character is not preceded by the 't' format modifier, the +- tag 0x02 is used for the element. +- +-'X' Bitstring. The next two arguments are a char * pointer to the +- start of the bitstring, followed by an int containing the number +- of bits in the bitstring. A bitstring element is output, in +- primitive form. If this format character is not preceded by the +- 't' format modifier, the tag 0x03 is used for the element. +- +-'n' Null. No argument is required. An ASN.1 NULL element is out- +- put. If this format character is not preceded by the 't' format +- modifier, the tag 0x05 is used for the element. +- +-'o' Octet string. The next two arguments are a char *, followed by +- an int with the length of the string. The string may contain +- null bytes and need not by null-terminated. An octet string +- element is output, in primitive form. If this format character +- is not preceded by the 't' format modifier, the tag 0x04 is used +- for the element. +- +-'s' Octet string. The next argument is a char * pointing to a +- null-terminated string. An octet string element in primitive +- form is output, which does not include the trailing ' ' byte. If +- this format character is not preceded by the 't' format modif- +- ier, the tag 0x04 is used for the element. +- +-'v' Several octet strings. The next argument is a char **, an array +- of char * pointers to null-terminated strings. The last element +- in the array must be a null pointer. The octet strings do not +- include the trailing SEQUENCE OF octet strings. The 't' format +- modifier cannot be used with this format character. +- +-'V' Several octet strings. A null-terminated array of berval *'s is +- supplied. Note that a construct like '{V}' is required to get an +- +- +- +-Expires: January 1998 [Page 41] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- actual SEQUENCE OF octet strings. The 't' format modifier cannot +- be used with this format character. +- +-'{' Begin sequence. No argument is required. If this format char- +- acter is not preceded by the 't' format modifier, the tag 0x30 +- is used. +- +-'}' End sequence. No argument is required. The 't' format modifier +- cannot be used with this format character. +- +-'[' Begin set. No argument is required. If this format character +- is not preceded by the 't' format modifier, the tag 0x31 is +- used. +- +-']' End set. No argument is required. The 't' format modifier can- +- not be used with this format character. +- +-Each use of a '{' format character must be matched by a '}' character, +-either later in the format string, or in the format string of a subse- +-quent call to ber_printf() for that BerElement. The same applies to the +-'[' and +- +-Sequences and sets nest, and implementations of this API must maintain +-internal state to be able to properly calculate the lengths. +- +- int ber_flatten (BerElement *ber, struct berval **bvPtr); +- +-The ber_flatten routine allocates a struct berval whose contents are a +-BER encoding taken from the ber argument. The bvPtr pointer points to +-the returned berval, which must be freed using ber_bvfree(). This rou- +-tine returns 0 on success and -1 on error. +- +-The ber_flatten API call is not present in U-M LDAP 3.3. +- +-The use of ber_flatten on a BerElement in which all '{' and '}' format +-modifiers have not been properly matched can result in a berval whose +-contents are not a valid BER encoding. +- +- +-13.3. Encoding Example +- +-The following is an example of encoding the following ASN.1 data type: +- +- Example1Request ::= SEQUENCE { +- s OCTET STRING, -- must be printable +- val1 INTEGER, +- val2 [0] INTEGER DEFAULT 0 +- } +- +- +- +-Expires: January 1998 [Page 42] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- int encode_example1(char *s,int val1,int val2,struct berval **bvPtr) +- { +- BerElement *ber; +- int rc; +- +- ber = ber_alloc_t(LBER_USE_DER); +- +- if (ber == NULL) return -1; +- +- if (ber_printf(ber,"{si",s,val1) == -1) { +- ber_free(ber,1); +- return -1; +- } +- +- if (val2 != 0) { +- if (ber_printf(ber,"ti",0x80,val2) == -1) { +- ber_free(ber,1); +- return -1; +- } +- } +- +- if (ber_printf(ber,"}") == -1) { +- ber_free(ber,1); +- return -1; +- } +- +- rc = ber_flatten(ber,bvPtr); +- ber_free(ber,1); +- return -1; +- } +- +- +-13.4. Decoding +- +-The following two symbols are available to applications. +- +- #define LBER_ERROR 0xffffffffL +- #define LBER_DEFAULT 0xffffffffL +- +- BerElement *ber_init (struct berval *bv); +- +-The ber_init functions construct BerElement and returns a new BerElement +-containing a copy of the data in the bv argument. ber_init returns the +-null pointer on error. +- +- unsigned long ber_scanf (BerElement *ber, char *fmt, ... ); +- +-The ber_scanf() routine is used to decode a BER element in much the same +- +- +- +-Expires: January 1998 [Page 43] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-way that sscanf() works. One important difference, though, is that some +-state information is kept with the ber argument so that multiple calls +-can be made to ber_scanf() to sequentially read from the BER element. +-The ber argument must be a pointer to a BerElement returned by +-ber_init(). ber_scanf interprets the bytes according to the format +-string fmt, and stores the results in its additional arguments. +-ber_scanf() returns LBER_ERROR on error, and a nonnegative number on +-success. +- +-The format string contains conversion specifications which are used to +-direct the interpretation of the BER element. The format string can +-contain the following characters: +- +-'a' Octet string. A char ** argument should be supplied. Memory is +- allocated, filled with the contents of the octet string, null- +- terminated, and the pointer to the string is stored in the argu- +- ment. The returned value must be freed using ldap_memfree. The +- tag of the element must indicate the primitive form (constructed +- strings are not supported) but is otherwise ignored and dis- +- carded during the decoding. This format cannot be used with +- octet strings which could contain null bytes. +- +-'O' Octet string. A struct berval ** argument should be supplied, +- which upon return points to a allocated struct berval containing +- the octet string and its length. ber_bvfree() must be called to +- free the allocated memory. The tag of the element must indicate +- the primitive form (constructed strings are not supported) but +- is otherwise ignored during the decoding. +- +-'b' Boolean. A pointer to an int should be supplied. The int value +- stored will be 0 for FALSE or nonzero for TRUE. The tag of the +- element must indicate the primitive form but is otherwise +- ignored during the decoding. +- +-'i' Integer. A pointer to an int should be supplied. The int value +- stored will be in host byte order. The tag of the element must +- indicate the primitive form but is otherwise ignored during the +- decoding. ber_scanf() will return an error if the integer can- +- not be stored in an int. +- +-'B' Bitstring. A char ** argument should be supplied which will +- point to the allocated bits, followed by an unsigned long * +- argument, which will point to the length (in bits) of the bit- +- string returned. ldap_memfree must be called to free the bit- +- string. The tag of the element must indicate the primitive form +- (constructed bitstrings are not supported) but is otherwise +- ignored during the decoding. +- +- +- +- +-Expires: January 1998 [Page 44] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-'n' Null. No argument is required. The element is simply skipped +- if it is recognized as a zero-length element. The tag is +- ignored. +- +-'v' Several octet strings. A char *** argument should be supplied, +- which upon return points to a allocated null-terminated array of +- char *'s containing the octet strings. NULL is stored if the +- sequence is empty. ldap_memfree must be called to free each +- element of the array and the array itself. The tag of the +- sequence and of the octet strings are ignored. +- +-'V' Several octet strings (which could contain null bytes). A +- struct berval *** should be supplied, which upon return points +- to a allocated null-terminated array of struct berval *'s con- +- taining the octet strings and their lengths. NULL is stored if +- the sequence is empty. ber_bvecfree() can be called to free the +- allocated memory. The tag of the sequence and of the octet +- strings are ignored. +- +-'x' Skip element. The next element is skipped. No argument is +- required. +- +-'{' Begin sequence. No argument is required. The initial sequence +- tag and length are skipped. +- +-'}' End sequence. No argument is required. +- +-'[' Begin set. No argument is required. The initial set tag and +- length are skipped. +- +-']' End set. No argument is required. +- +- unsigned long ber_peek_tag (BerElement *ber, unsigned long *lenPtr); +- +-ber_peek_tag() returns the tag of the next element to be parsed in the +-BerElement argument. The length of this element is stored in the +-*lenPtr argument. LBER_DEFAULT is returned if there is no further data +-to be read. The ber argument is not modified. +- +- unsigned long ber_skip_tag (BerElement *ber, unsigned long *lenPtr); +- +-ber_skip_tag() is similar to ber_peek_tag(), except that the state +-pointer in the BerElement argument is advanced past the first tag and +-length, and is pointed to the value part of the next element. This rou- +-tine should only be used with constructed types and situations when a +-BER encoding is used as the value of an OCTET STRING. The length of the +-value is stored in *lenPtr. +- +- +- +- +-Expires: January 1998 [Page 45] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- unsigned long ber_first_element(BerElement *ber, +- unsigned long *lenPtr, char **opaquePtr); +- +- unsigned long ber_next_element (BerElement *ber, +- unsigned long *lenPtr, char *opaque); +- +-ber_first_element() and ber_next_element() are used to traverse a SET, +-SET OF, SEQUENCE or SEQUENCE OF data value. ber_first_element() calls +-ber_skip_tag(), stores internal information in *lenPtr and *opaquePtr, +-and calls ber_peek_tag() for the first element inside the constructed +-value. LBER_DEFAULT is returned if the constructed value is empty. +-ber_next_element() positions the state at the start of the next element +-in the constructed type. LBER_DEFAULT is returned if there are no +-further values. +- +-The len and opaque values should not be used by applications other than +-as arguments to ber_next_element(), as shown in the example below. +- +- +-13.5. Decoding Example +- +-The following is an example of decoding an ASN.1 data type: +- +- Example2Request ::= SEQUENCE { +- dn OCTET STRING, -- must be printable +- scope ENUMERATED { b (0), s (1), w (2) }, +- ali ENUMERATED { n (0), s (1), f (2), a (3) }, +- size INTEGER, +- time INTEGER, +- tonly BOOLEAN, +- attrs SEQUENCE OF OCTET STRING, -- must be printable +- [0] SEQUENCE OF SEQUENCE { +- type OCTET STRING -- must be printable, +- crit BOOLEAN DEFAULT FALSE, +- value OCTET STRING +- } OPTIONAL } +- +- #define LDAP_TAG_CONTROL_LIST 0xA0L /* context specific cons 0 */ +- +- int decode_example2(struct berval *bv) +- { +- BerElement *ber; +- unsigned long len; +- int scope, ali, size, time, tonly; +- char *dn = NULL, **attrs = NULL; +- int res,i,rc = 0; +- +- ber = ber_init(bv); +- +- +- +-Expires: January 1998 [Page 46] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- if (ber == NULL) { +- printf("ERROR ber_init failed0); +- return -1; +- } +- +- res = ber_scanf(ber,"{aiiiiib{v}",&dn,&scope,&ali, +- &size,&time,&tonly,&attrs); +- +- if (res == -1) { +- printf("ERROR ber_scanf failed0); +- ber_free(ber,1); +- return -1; +- } +- +- /* *** use dn */ +- ldap_memfree(dn); +- +- for (i = 0; attrs != NULL && attrs[i] != NULL; i++) { +- /* *** use attrs[i] */ +- ldap_memfree(attrs[i]); +- } +- ldap_memfree(attrs); +- +- if (ber_peek_tag(ber,&len) == LDAP_TAG_CONTROL_LIST) { +- char *opaque; +- unsigned long tag; +- +- for (tag = ber_first_element(ber,&len,&opaque); +- tag != LBER_DEFAULT; +- tag = ber_next_element (ber,&len,opaque)) { +- +- unsigned long ttag, tlen; +- char *type; +- int crit; +- struct berval *value; +- +- if (ber_scanf(ber,"{a",&type) == LBER_ERROR) { +- printf("ERROR cannot parse type0); +- break; +- } +- /* *** use type */ +- ldap_memfree(type); +- +- ttag = ber_peek_tag(ber,&tlen); +- if (ttag == 0x01) { /* boolean */ +- if (ber_scanf(ber,"b", +- &crit) == LBER_ERROR) { +- printf("ERROR cannot parse crit0); +- +- +- +-Expires: January 1998 [Page 47] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- rc = -1; +- break; +- } +- } else if (ttag == 0x04) { /* octet string */ +- crit = 0; +- } else { +- printf("ERROR extra field in controls0); +- break; +- } +- +- if (ber_scanf(ber,"O}",&value) == LBER_ERROR) { +- printf("ERROR cannot parse value0); +- rc = -1; +- break; +- } +- /* *** use value */ +- ldap_bvfree(value); +- } +- } +- +- ber_scanf(ber,"}"); +- +- ber_free(ber,1); +- +- return rc; +- } +- +- +- +-14. Security Considerations +- +-LDAPv2 supports security through protocol-level authentication using +-clear-text passwords. LDAPv3 adds support for SASL [8] (Simple Authen- +-tication Security Layer) methods. LDAPv3 also supports operation over a +-secure transport layer using Transport Layer Security TLS [8]. Readers +-are referred to the protocol documents for discussion of related secu- +-rity considerations. +- +-Implementations of this API should be cautious when handling authentica- +-tion credentials. In particular, keeping long-lived copies of creden- +-tials without the application's knowledge is discouraged. +- +- +-15. Acknowledgements +- +-Many members of the IETF ASID working group as well as members of the +-Internet at large have provided useful comments and suggestions that +-have been incorporated into this revision. +- +- +- +-Expires: January 1998 [Page 48] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-This original material upon which this revision is based was based upon +-work supported by the National Science Foundation under Grant No. NCR- +-9416667. +- +- +-16. Bibliography +- +-[1] The Directory: Selected Attribute Syntaxes. CCITT, Recommendation +- X.520. +- +-[2] M. Wahl, A. Coulbeck, T. Howes, S. Kille, W. Yeong, C. Robbins, +- "Lightweight Directory Access Protocol Attribute Syntax Defini- +- tions", INTERNET-DRAFT , +- 11 July 1997. +- +-[3] T. Howes, "A String Representation of LDAP Search Filters," +- INTERNET-DRAFT , May 1997. +- +-[4] S. Kille, M. Wahl, "A UTF-8 String Representation of Distinguished +- Names", INTERNET-DRAFT , 29 April +- 1997. +- +-[5] S. Kille, "Using the OSI Directory to Achieve User Friendly Nam- +- ing," RFC 1781, March 1995. +- +-[6] M. Wahl, T. Howes, S. Kille, "Lightweight Directory Access Protocol +- (v3)", INTERNET-DRAFT , 11 +- July 1997. +- +-[7] A. Herron, T. Howes, M. Wahl, "LDAP Control Extension for Server +- Side Sorting of Search Result," INTERNET-DRAFT , 16 April 1997. +- +-[8] J. Meyers, "Simple Authentication and Security Layer", INTERNET- +- DRAFT , April 1997. +- +-[9] "Lightweight Directory Access Protocol (v3) Extension for Transport +- Layer Security", INTERNET-DRAFT , June 1997. +- +-[10] "UTF-8, a transformation format of Unicode and ISO 10646", RFC +- 2044, October 1996. +- +-[11] "IP Version 6 Addressing Architecture,", RFC 1884, December 1995. +- +- +- +- +- +- +- +-Expires: January 1998 [Page 49] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-17. Author's Addresses +- +- Tim Howes +- Netscape Communications Corp. +- 501 E. Middlefield Rd., Mailstop MV068 +- Mountain View, CA 94043 +- USA +- +1 415 937-3419 +- howes@netscape.com +- +- +- Mark Smith +- Netscape Communications Corp. +- 501 E. Middlefield Rd., Mailstop MV068 +- Mountain View, CA 94043 +- USA +- +1 415 937-3477 +- mcs@netscape.com +- +- Andy Herron +- Microsoft Corp. +- 1 Microsoft Way +- Redmond, WA 98052 +- USA +- +1 425 882-8080 +- andyhe@microsoft.com +- +- Chris Weider +- Microsoft Corp. +- 1 Microsoft Way +- Redmond, WA 98052 +- USA +- +1 425 882-8080 +- cweider@microsoft.com +- +- Mark Wahl +- Critical Angle Inc. +- 4815 W Braker Lane #502-385 +- Austin, TX 78759 +- USA +- M.Wahl@critical-angle.com +- +- +-18. Appendix A - Sample LDAP API Code +- +- #include +- +- main() +- +- +- +-Expires: January 1998 [Page 50] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- { +- LDAP *ld; +- LDAPMessage *res, *e; +- int i; +- char *a, *dn; +- BerElement *ptr; +- char **vals; +- +- /* open an LDAP session */ +- if ( (ld = ldap_init( "dotted.host.name", LDAP_PORT )) == NULL ) +- exit( 1 ); +- +- /* authenticate as nobody */ +- if ( ldap_simple_bind_s( ld, NULL, NULL ) != LDAP_SUCCESS ) { +- ldap_perror( ld, "ldap_simple_bind_s" ); +- exit( 1 ); +- } +- +- /* search for entries with cn of "Babs Jensen", return all attrs */ +- if ( ldap_search_s( ld, "o=University of Michigan, c=US", +- LDAP_SCOPE_SUBTREE, "(cn=Babs Jensen)", NULL, 0, &res ) +- != LDAP_SUCCESS ) { +- ldap_perror( ld, "ldap_search_s" ); +- exit( 1 ); +- } +- +- /* step through each entry returned */ +- for ( e = ldap_first_entry( ld, res ); e != NULL; +- e = ldap_next_entry( ld, e ) ) { +- /* print its name */ +- dn = ldap_get_dn( ld, e ); +- printf( "dn: %s\n", dn ); +- ldap_memfree( dn ); +- +- /* print each attribute */ +- for ( a = ldap_first_attribute( ld, e, &ptr ); a != NULL; +- a = ldap_next_attribute( ld, e, ptr ) ) { +- printf( "attribute: %s\n", a ); +- +- /* print each value */ +- vals = ldap_get_values( ld, e, a ); +- for ( i = 0; vals[i] != NULL; i++ ) { +- printf( "value: %s\n", vals[i] ); +- } +- ldap_value_free( vals ); +- } +- if ( ptr != NULL ) { +- ldap_ber_free( ptr, 0 ); +- +- +- +-Expires: January 1998 [Page 51] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +- } +- } +- /* free the search results */ +- ldap_msgfree( res ); +- +- /* close and free connection resources */ +- ldap_unbind( ld ); +- } +- +- +- +-19. Appendix B - Outstanding Issues +- +- +-19.1. Support for multithreaded applications +- +-In order to support multithreaded applications in a platform-independent +-way, some additions to the LDAP API are needed. Different implementors +-have taken different paths to solve this problem in the past. A common +-set of thread-related API calls must be defined so that application +-developers are not unduly burdened. These will be added to a future +-revision of this specification. +- +- +-19.2. Using Transport Layer Security (TLS) +- +-The API calls used to support TLS must be specified. They will be added +-to a future revision of this specification. +- +- +-19.3. Client control for chasing referrals +- +-A client control has been defined that can be used to specify on a per- +-operation basis whether references and external referrals are automati- +-cally chased by the client library. This will be added to a future +-revision of this specification. +- +- +-19.4. Potential confusion between hostname:port and IPv6 addresses +- +-String representations of IPv6 network addresses [11] can contain colon +-characters. The ldap_init() call is specified to take strings of the +-form "hostname:port" or "ipaddress:port". If IPv6 addresses are used, +-the latter could be ambiguous. A future revision of this specification +-will resolve this issue. +- +- +- +- +- +- +-Expires: January 1998 [Page 52] +- +-C LDAP API The C LDAP Application Program Interface 29 July 1997 +- +- +-19.5. Need to track SASL API standardization efforts +- +-If a standard Simple Authentication and Security Layer API is defined, +-it may be necessary to modify the LDAP API to accommodate it. +- +- +-19.6. Support for character sets other than UTF-8? +- +-Some application developers would prefer to pass string data using a +-character set other than UTF-8. This could be accommodated by adding a +-new option to ldap_set_option() that supports choosing a character set. +-If this feature is added, the number of different character sets sup- +-ported should definitely be minimized. +- +- +-19.7. Use of UTF-8 with LDAPv2 servers +- +-Strings are always passed as UTF-8 in this API but LDAP version 2 +-servers do not support the full range of UTF-8 characters. The expected +-behavior of this API when using LDAP version 2 with unsupported charac- +-ters should be specified. +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +-Expires: January 1998 [Page 53] +- +- +- +-1. Status of this Memo............................................1 +-2. Introduction...................................................1 +-3. Overview of the LDAP Model.....................................2 +-4. Overview of LDAP API Use.......................................3 +-5. Common Data Structures.........................................4 +-6. LDAP Error Codes...............................................5 +-7. Performing LDAP Operations.....................................6 +-7.1. Initializing an LDAP Session................................6 +-7.2. LDAP Session Handle Options.................................7 +-7.3. Working with controls.......................................10 +-7.4. Authenticating to the directory.............................11 +-7.5. Closing the session.........................................13 +-7.6. Searching...................................................13 +-7.7. Reading an Entry............................................17 +-7.8. Listing the Children of an Entry............................17 +-7.9. Comparing a Value Against an Entry..........................17 +-7.10. Modifying an entry..........................................19 +-7.11. Modifying the Name of an Entry..............................21 +-7.12. Adding an entry.............................................23 +-7.13. Deleting an entry...........................................25 +-7.14. Extended Operations.........................................26 +-8. Abandoning An Operation........................................28 +-9. Obtaining Results and Peeking Inside LDAP Messages.............29 +-10. Handling Errors and Parsing Results............................31 +-11. Stepping Through a List of Results.............................33 +-12. Parsing Search Results.........................................34 +-12.1. Stepping Through a List of Entries..........................34 +-12.2. Stepping Through the Attributes of an Entry.................35 +-12.3. Retrieving the Values of an Attribute.......................36 +-12.4. Retrieving the name of an entry.............................37 +-13. Encoded ASN.1 Value Manipulation...............................39 +-13.1. General.....................................................39 +-13.2. Encoding....................................................40 +-13.3. Encoding Example............................................42 +-13.4. Decoding....................................................43 +-13.5. Decoding Example............................................46 +-14. Security Considerations........................................48 +-15. Acknowledgements...............................................48 +-16. Bibliography...................................................49 +-17. Author's Addresses.............................................50 +-18. Appendix A - Sample LDAP API Code..............................50 +-19. Appendix B - Outstanding Issues................................52 +-19.1. Support for multithreaded applications......................52 +-19.2. Using Transport Layer Security (TLS)........................52 +-19.3. Client control for chasing referrals........................52 +-19.4. Potential confusion between hostname:port and IPv6 addresses52 +-19.5. Need to track SASL API standardization efforts..............53 +-19.6. Support for character sets other than UTF-8?................53 +-19.7. Use of UTF-8 with LDAPv2 servers............................53 +- +- +- +- +- +- +- +- diff --git a/rabbitmq-server-3.3.5/plugins-src/generate_app b/rabbitmq-server-3.3.5/plugins-src/generate_app new file mode 100644 index 0000000..fb0eb1e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/generate_app @@ -0,0 +1,16 @@ +#!/usr/bin/env escript +%% -*- erlang -*- + +main([InFile, OutFile | SrcDirs]) -> + Modules = [list_to_atom(filename:basename(F, ".erl")) || + SrcDir <- SrcDirs, + F <- filelib:wildcard("*.erl", SrcDir)], + {ok, [{application, Application, Properties}]} = file:consult(InFile), + NewProperties = + case proplists:get_value(modules, Properties) of + [] -> lists:keyreplace(modules, 1, Properties, {modules, Modules}); + _ -> Properties + end, + file:write_file( + OutFile, + io_lib:format("~p.~n", [{application, Application, NewProperties}])). diff --git a/rabbitmq-server-3.3.5/plugins-src/generate_deps b/rabbitmq-server-3.3.5/plugins-src/generate_deps new file mode 100644 index 0000000..9f8485b --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/generate_deps @@ -0,0 +1,61 @@ +#!/usr/bin/env escript +%% -*- erlang -*- +-mode(compile). + +%% We expect the list of Erlang source and header files to arrive on +%% stdin, with the entries colon-separated. +main([TargetFile, EbinDir]) -> + ErlsAndHrls = [ string:strip(S,left) || + S <- string:tokens(io:get_line(""), ":\n")], + ErlFiles = [F || F <- ErlsAndHrls, lists:suffix(".erl", F)], + Modules = sets:from_list( + [list_to_atom(filename:basename(FileName, ".erl")) || + FileName <- ErlFiles]), + HrlFiles = [F || F <- ErlsAndHrls, lists:suffix(".hrl", F)], + IncludeDirs = lists:usort([filename:dirname(Path) || Path <- HrlFiles]), + Headers = sets:from_list(HrlFiles), + Deps = lists:foldl( + fun (Path, Deps1) -> + dict:store(Path, detect_deps(IncludeDirs, EbinDir, + Modules, Headers, Path), + Deps1) + end, dict:new(), ErlFiles), + {ok, Hdl} = file:open(TargetFile, [write, delayed_write]), + dict:fold( + fun (_Path, [], ok) -> + ok; + (Path, Dep, ok) -> + Module = filename:basename(Path, ".erl"), + ok = file:write(Hdl, [EbinDir, "/", Module, ".beam: ", + Path]), + ok = sets:fold(fun (E, ok) -> file:write(Hdl, [" ", E]) end, + ok, Dep), + file:write(Hdl, ["\n"]) + end, ok, Deps), + ok = file:write(Hdl, [TargetFile, ": ", escript:script_name(), "\n"]), + ok = file:sync(Hdl), + ok = file:close(Hdl). + +detect_deps(IncludeDirs, EbinDir, Modules, Headers, Path) -> + {ok, Forms} = epp:parse_file(Path, IncludeDirs, [{use_specs, true}]), + lists:foldl( + fun ({attribute, _Line, Attribute, Behaviour}, Deps) + when Attribute =:= behaviour orelse Attribute =:= behavior -> + maybe_add_to_deps(EbinDir, Modules, Behaviour, Deps); + ({attribute, _Line, compile, {parse_transform, Transform}}, Deps) -> + maybe_add_to_deps(EbinDir, Modules, Transform, Deps); + ({attribute, _Line, file, {FileName, _LineNumber1}}, Deps) -> + case sets:is_element(FileName, Headers) of + true -> sets:add_element(FileName, Deps); + false -> Deps + end; + (_Form, Deps) -> + Deps + end, sets:new(), Forms). + +maybe_add_to_deps(EbinDir, Modules, Module, Deps) -> + case sets:is_element(Module, Modules) of + true -> sets:add_element( + [EbinDir, "/", atom_to_list(Module), ".beam"], Deps); + false -> Deps + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-APACHE2-ExplorerCanvas b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-APACHE2-ExplorerCanvas new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-APACHE2-ExplorerCanvas @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-APL2-Stomp-Websocket b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-APL2-Stomp-Websocket new file mode 100644 index 0000000..ef51da2 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-APL2-Stomp-Websocket @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-Apache-Basho b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-Apache-Basho new file mode 100644 index 0000000..e454a52 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-Apache-Basho @@ -0,0 +1,178 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-BSD-base64js b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-BSD-base64js new file mode 100644 index 0000000..7073704 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-BSD-base64js @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2010 Nick Galbreath + * http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript + * + * 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/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-BSD-glMatrix b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-BSD-glMatrix new file mode 100644 index 0000000..660571e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-BSD-glMatrix @@ -0,0 +1,26 @@ +Copyright (c) 2011, Brandon Jones +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-EJS10 b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-EJS10 new file mode 100644 index 0000000..f3bdcd8 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-EJS10 @@ -0,0 +1,23 @@ +EJS - Embedded JavaScript + +Copyright (c) 2007 Edward Benson + +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/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-Flot b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-Flot new file mode 100644 index 0000000..67f4625 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-Flot @@ -0,0 +1,22 @@ +Copyright (c) 2007-2013 IOLA and Ole Laursen + +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/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-Mochi b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-Mochi new file mode 100644 index 0000000..c85b65a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-Mochi @@ -0,0 +1,9 @@ +This is the MIT license. + +Copyright (c) 2007 Mochi Media, Inc. + +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/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-Sammy060 b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-Sammy060 new file mode 100644 index 0000000..3debf5a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-Sammy060 @@ -0,0 +1,25 @@ +Copyright (c) 2008 Aaron Quint, Quirkey NYC, LLC + +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/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-eldap b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-eldap new file mode 100644 index 0000000..1f62009 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-eldap @@ -0,0 +1,21 @@ + +Copyright (c) 2010, Torbjorn Tornkvist + +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/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-jQuery164 b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-jQuery164 new file mode 100644 index 0000000..5a87162 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MIT-jQuery164 @@ -0,0 +1,21 @@ +Copyright (c) 2011 John Resig, http://jquery.com/ + +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/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MPL-RabbitMQ b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MPL-RabbitMQ new file mode 100644 index 0000000..c87c1a3 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/licensing/LICENSE-MPL-RabbitMQ @@ -0,0 +1,455 @@ + MOZILLA PUBLIC LICENSE + Version 1.1 + + --------------- + +1. Definitions. + + 1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. + + 1.1. "Contributor" means each entity that creates or contributes to + the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications + made by that particular Contributor. + + 1.3. "Covered Code" means the Original Code or Modifications or the + combination of the Original Code and Modifications, in each case + including portions thereof. + + 1.4. "Electronic Distribution Mechanism" means a mechanism generally + accepted in the software development community for the electronic + transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source + Code. + + 1.6. "Initial Developer" means the individual or entity identified + as the Initial Developer in the Source Code notice required by Exhibit + A. + + 1.7. "Larger Work" means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + + 1.8. "License" means this document. + + 1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + A. Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. + + B. Any new file that contains any part of the Original Code or + previous Modifications. + + 1.10. "Original Code" means Source Code of computer software code + which is described in the Source Code notice required by Exhibit A as + Original Code, and which, at the time of its release under this + License is not already Covered Code governed by this License. + + 1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, + and apparatus claims, in any patent Licensable by grantor. + + 1.11. "Source Code" means the preferred form of the Covered Code for + making modifications to it, including all modules it contains, plus + any associated interface definition files, scripts used to control + compilation and installation of an Executable, or source code + differential comparisons against either the Original Code or another + well known, available Covered Code of the Contributor's choice. The + Source Code can be in a compressed or archival form, provided the + appropriate decompression or de-archiving software is widely available + for no charge. + + 1.12. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License or a future version of this License issued under Section 6.1. + For legal entities, "You" includes any entity which controls, is + controlled by, or is under common control with You. For purposes of + this definition, "control" means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty percent + (50%) of the outstanding shares or beneficial ownership of such + entity. + +2. Source Code License. + + 2.1. The Initial Developer Grant. + The Initial Developer hereby grants You a world-wide, royalty-free, + non-exclusive license, subject to third party intellectual property + claims: + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original + Code (or portions thereof) with or without Modifications, and/or + as part of a Larger Work; and + + (b) under Patents Claims infringed by the making, using or + selling of Original Code, to make, have made, use, practice, + sell, and offer for sale, and/or otherwise dispose of the + Original Code (or portions thereof). + + (c) the licenses granted in this Section 2.1(a) and (b) are + effective on the date Initial Developer first distributes + Original Code under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) + separate from the Original Code; or 3) for infringements caused + by: i) the modification of the Original Code or ii) the + combination of the Original Code with other software or devices. + + 2.2. Contributor Grant. + Subject to third party intellectual property claims, each Contributor + hereby grants You a world-wide, royalty-free, non-exclusive license + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor, to use, reproduce, modify, + display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an + unmodified basis, with other Modifications, as Covered Code + and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either alone + and/or in combination with its Contributor Version (or portions + of such combination), to make, use, sell, offer for sale, have + made, and/or otherwise dispose of: 1) Modifications made by that + Contributor (or portions thereof); and 2) the combination of + Modifications made by that Contributor with its Contributor + Version (or portions of such combination). + + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of + the Covered Code. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: 1) for any code that Contributor has deleted from the + Contributor Version; 2) separate from the Contributor Version; + 3) for infringements caused by: i) third party modifications of + Contributor Version or ii) the combination of Modifications made + by that Contributor with other software (except as part of the + Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by + that Contributor. + +3. Distribution Obligations. + + 3.1. Application of License. + The Modifications which You create or to which You contribute are + governed by the terms of this License, including without limitation + Section 2.2. The Source Code version of Covered Code may be + distributed only under the terms of this License or a future version + of this License released under Section 6.1, and You must include a + copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code + version that alters or restricts the applicable version of this + License or the recipients' rights hereunder. However, You may include + an additional document offering the additional rights described in + Section 3.5. + + 3.2. Availability of Source Code. + Any Modification which You create or to which You contribute must be + made available in Source Code form under the terms of this License + either on the same media as an Executable version or via an accepted + Electronic Distribution Mechanism to anyone to whom you made an + Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) + months after the date it initially became available, or at least six + (6) months after a subsequent version of that particular Modification + has been made available to such recipients. You are responsible for + ensuring that the Source Code version remains available even if the + Electronic Distribution Mechanism is maintained by a third party. + + 3.3. Description of Modifications. + You must cause all Covered Code to which You contribute to contain a + file documenting the changes You made to create that Covered Code and + the date of any change. You must include a prominent statement that + the Modification is derived, directly or indirectly, from Original + Code provided by the Initial Developer and including the name of the + Initial Developer in (a) the Source Code, and (b) in any notice in an + Executable version or related documentation in which You describe the + origin or ownership of the Covered Code. + + 3.4. Intellectual Property Matters + (a) Third Party Claims. + If Contributor has knowledge that a license under a third party's + intellectual property rights is required to exercise the rights + granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code + distribution titled "LEGAL" which describes the claim and the + party making the claim in sufficient detail that a recipient will + know whom to contact. If Contributor obtains such knowledge after + the Modification is made available as described in Section 3.2, + Contributor shall promptly modify the LEGAL file in all copies + Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) + reasonably calculated to inform those who received the Covered + Code that new knowledge has been obtained. + + (b) Contributor APIs. + If Contributor's Modifications include an application programming + interface and Contributor has knowledge of patent licenses which + are reasonably necessary to implement that API, Contributor must + also include this information in the LEGAL file. + + (c) Representations. + Contributor represents that, except as disclosed pursuant to + Section 3.4(a) above, Contributor believes that Contributor's + Modifications are Contributor's original creation(s) and/or + Contributor has sufficient rights to grant the rights conveyed by + this License. + + 3.5. Required Notices. + You must duplicate the notice in Exhibit A in each file of the Source + Code. If it is not possible to put such notice in a particular Source + Code file due to its structure, then You must include such notice in a + location (such as a relevant directory) where a user would be likely + to look for such a notice. If You created one or more Modification(s) + You may add your name as a Contributor to the notice described in + Exhibit A. You must also duplicate this License in any documentation + for the Source Code where You describe recipients' rights or ownership + rights relating to Covered Code. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear than + any such warranty, support, indemnity or liability obligation is + offered by You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of warranty, + support, indemnity or liability terms You offer. + + 3.6. Distribution of Executable Versions. + You may distribute Covered Code in Executable form only if the + requirements of Section 3.1-3.5 have been met for that Covered Code, + and if You include a notice stating that the Source Code version of + the Covered Code is available under the terms of this License, + including a description of how and where You have fulfilled the + obligations of Section 3.2. The notice must be conspicuously included + in any notice in an Executable version, related documentation or + collateral in which You describe recipients' rights relating to the + Covered Code. You may distribute the Executable version of Covered + Code or ownership rights under a license of Your choice, which may + contain terms different from this License, provided that You are in + compliance with the terms of this License and that the license for the + Executable version does not attempt to limit or alter the recipient's + rights in the Source Code version from the rights set forth in this + License. If You distribute the Executable version under a different + license You must make it absolutely clear that any terms which differ + from this License are offered by You alone, not by the Initial + Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such + terms You offer. + + 3.7. Larger Works. + You may create a Larger Work by combining Covered Code with other code + not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Code. + +4. Inability to Comply Due to Statute or Regulation. + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description + must be included in the LEGAL file described in Section 3.4 and must + be included with all distributions of the Source Code. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Application of this License. + + This License applies to code to which the Initial Developer has + attached the notice in Exhibit A and to related Covered Code. + +6. Versions of the License. + + 6.1. New Versions. + Netscape Communications Corporation ("Netscape") may publish revised + and/or new versions of the License from time to time. Each version + will be given a distinguishing version number. + + 6.2. Effect of New Versions. + Once Covered Code has been published under a particular version of the + License, You may always continue to use it under the terms of that + version. You may also choose to use such Covered Code under the terms + of any subsequent version of the License published by Netscape. No one + other than Netscape has the right to modify the terms applicable to + Covered Code created under this License. + + 6.3. Derivative Works. + If You create or use a modified version of this License (which you may + only do in order to apply it to code which is not already Covered Code + governed by this License), You must (a) rename Your license so that + the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", + "MPL", "NPL" or any confusingly similar phrase do not appear in your + license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license + contains terms which differ from the Mozilla Public License and + Netscape Public License. (Filling in the name of the Initial + Developer, Original Code or Contributor in the notice described in + Exhibit A shall not of themselves be deemed to be modifications of + this License.) + +7. DISCLAIMER OF WARRANTY. + + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE + IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, + YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE + COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER + OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +8. TERMINATION. + + 8.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall + survive any termination of this License. Provisions which, by their + nature, must remain in effect beyond the termination of this License + shall survive. + + 8.2. If You initiate litigation by asserting a patent infringement + claim (excluding declatory judgment actions) against Initial Developer + or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + + (a) such Participant's Contributor Version directly or indirectly + infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License + shall, upon 60 days notice from Participant terminate prospectively, + unless if within 60 days after receipt of notice You either: (i) + agree in writing to pay Participant a mutually agreeable reasonable + royalty for Your past and future use of Modifications made by such + Participant, or (ii) withdraw Your litigation claim with respect to + the Contributor Version against such Participant. If within 60 days + of notice, a reasonable royalty and payment arrangement are not + mutually agreed upon in writing by the parties or the litigation claim + is not withdrawn, the rights granted by Participant to You under + Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + + (b) any software, hardware, or device, other than such Participant's + Contributor Version, directly or indirectly infringes any patent, then + any rights granted to You by such Participant under Sections 2.1(b) + and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that + Participant. + + 8.3. If You assert a patent infringement claim against Participant + alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as + by license or settlement) prior to the initiation of patent + infringement litigation, then the reasonable value of the licenses + granted by such Participant under Sections 2.1 or 2.2 shall be taken + into account in determining the amount or value of any payment or + license. + + 8.4. In the event of termination under Sections 8.1 or 8.2 above, + all end user license agreements (excluding distributors and resellers) + which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination. + +9. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, + OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR + ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY + CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, + WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE + EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO + THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +10. U.S. GOVERNMENT END USERS. + + The Covered Code is a "commercial item," as that term is defined in + 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer + software" and "commercial computer software documentation," as such + terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 + C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), + all U.S. Government End Users acquire Covered Code with only those + rights set forth herein. + +11. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed by + California law provisions (except to the extent applicable law, if + any, provides otherwise), excluding its conflict-of-law provisions. + With respect to disputes in which at least one party is a citizen of, + or an entity chartered or registered to do business in the United + States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern + District of California, with venue lying in Santa Clara County, + California, with the losing party responsible for costs, including + without limitation, court costs and reasonable attorneys' fees and + expenses. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. + Any law or regulation which provides that the language of a contract + shall be construed against the drafter shall not apply to this + License. + +12. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, + out of its utilization of rights under this License and You agree to + work with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + +13. MULTIPLE-LICENSED CODE. + + Initial Developer may designate portions of the Covered Code as + "Multiple-Licensed". "Multiple-Licensed" means that the Initial + Developer permits you to utilize portions of the Covered Code under + Your choice of the NPL or the alternative licenses, if any, specified + by the Initial Developer in the file described in Exhibit A. + +EXHIBIT A -Mozilla Public License. + + ``The contents of this file are subject to the Mozilla Public License + Version 1.1 (the "License"); you may not use this file except in + compliance with the License. You may obtain a copy of the License at + http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + License for the specific language governing rights and limitations + under the License. + + The Original Code is RabbitMQ. + + The Initial Developer of the Original Code is GoPivotal, Inc. + Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved.'' + + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.] diff --git a/rabbitmq-server-3.3.5/plugins-src/licensing/license_info_eldap-wrapper b/rabbitmq-server-3.3.5/plugins-src/licensing/license_info_eldap-wrapper new file mode 100644 index 0000000..0a0e13c --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/licensing/license_info_eldap-wrapper @@ -0,0 +1,3 @@ +Eldap is "Copyright (c) 2010, Torbjorn Tornkvist" and is covered by +the MIT license. It was downloaded from https://github.com/etnt/eldap + diff --git a/rabbitmq-server-3.3.5/plugins-src/licensing/license_info_mochiweb-wrapper b/rabbitmq-server-3.3.5/plugins-src/licensing/license_info_mochiweb-wrapper new file mode 100644 index 0000000..c72a6af --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/licensing/license_info_mochiweb-wrapper @@ -0,0 +1,4 @@ +Mochiweb is "Copyright (c) 2007 Mochi Media, Inc." and is covered by +the MIT license. It was downloaded from +http://github.com/mochi/mochiweb/ + diff --git a/rabbitmq-server-3.3.5/plugins-src/licensing/license_info_rabbitmq-management b/rabbitmq-server-3.3.5/plugins-src/licensing/license_info_rabbitmq-management new file mode 100644 index 0000000..eabe119 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/licensing/license_info_rabbitmq-management @@ -0,0 +1,17 @@ +jQuery is "Copyright (c) 2010 John Resig" and is covered by the MIT +license. It was downloaded from http://jquery.com/ + +EJS is "Copyright (c) 2007 Edward Benson" and is covered by the MIT +license. It was downloaded from http://embeddedjs.com/ + +Sammy is "Copyright (c) 2008 Aaron Quint, Quirkey NYC, LLC" and is +covered by the MIT license. It was downloaded from +http://code.quirkey.com/sammy/ + +ExplorerCanvas is "Copyright 2006 Google Inc" and is covered by the +Apache License version 2.0. It was downloaded from +http://code.google.com/p/explorercanvas/ + +Flot is "Copyright (c) 2007-2013 IOLA and Ole Laursen" and is covered +by the MIT license. It was downloaded from +http://www.flotcharts.org/ diff --git a/rabbitmq-server-3.3.5/plugins-src/licensing/license_info_rabbitmq-management-visualiser b/rabbitmq-server-3.3.5/plugins-src/licensing/license_info_rabbitmq-management-visualiser new file mode 100644 index 0000000..9a8a906 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/licensing/license_info_rabbitmq-management-visualiser @@ -0,0 +1,4 @@ +glMatrix is "Copyright (c) 2011, Brandon Jones" and is covered by the +BSD 2-Clause license. It was downloaded from +http://code.google.com/p/glmatrix/ + diff --git a/rabbitmq-server-3.3.5/plugins-src/licensing/license_info_webmachine-wrapper b/rabbitmq-server-3.3.5/plugins-src/licensing/license_info_webmachine-wrapper new file mode 100644 index 0000000..c00fb92 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/licensing/license_info_webmachine-wrapper @@ -0,0 +1,3 @@ +Webmachine is Copyright (c) Basho Technologies and is covered by the +Apache License 2.0. It was downloaded from http://webmachine.basho.com/ + diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/.srcdist_done b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/.srcdist_done new file mode 100644 index 0000000..e69de29 diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/10-build-on-R12B-5.patch b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/10-build-on-R12B-5.patch new file mode 100644 index 0000000..af582a7 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/10-build-on-R12B-5.patch @@ -0,0 +1,303 @@ +diff --git a/src/mochiglobal.erl b/src/mochiglobal.erl +index ea645b0..6b20e41 100644 +--- a/src/mochiglobal.erl ++++ b/src/mochiglobal.erl +@@ -6,12 +6,12 @@ + -author("Bob Ippolito "). + -export([get/1, get/2, put/2, delete/1]). + +--spec get(atom()) -> any() | undefined. ++%% -spec get(atom()) -> any() | undefined. + %% @equiv get(K, undefined) + get(K) -> + get(K, undefined). + +--spec get(atom(), T) -> any() | T. ++%% -spec get(atom(), T) -> any() | T. + %% @doc Get the term for K or return Default. + get(K, Default) -> + get(K, Default, key_to_module(K)). +@@ -22,7 +22,7 @@ get(_K, Default, Mod) -> + Default + end. + +--spec put(atom(), any()) -> ok. ++%% -spec put(atom(), any()) -> ok. + %% @doc Store term V at K, replaces an existing term if present. + put(K, V) -> + put(K, V, key_to_module(K)). +@@ -33,7 +33,7 @@ put(_K, V, Mod) -> + {module, Mod} = code:load_binary(Mod, atom_to_list(Mod) ++ ".erl", Bin), + ok. + +--spec delete(atom()) -> boolean(). ++%% -spec delete(atom()) -> boolean(). + %% @doc Delete term stored at K, no-op if non-existent. + delete(K) -> + delete(K, key_to_module(K)). +@@ -42,21 +42,21 @@ delete(_K, Mod) -> + code:purge(Mod), + code:delete(Mod). + +--spec key_to_module(atom()) -> atom(). ++%% -spec key_to_module(atom()) -> atom(). + key_to_module(K) -> + list_to_atom("mochiglobal:" ++ atom_to_list(K)). + +--spec compile(atom(), any()) -> binary(). ++%% -spec compile(atom(), any()) -> binary(). + compile(Module, T) -> + {ok, Module, Bin} = compile:forms(forms(Module, T), + [verbose, report_errors]), + Bin. + +--spec forms(atom(), any()) -> [erl_syntax:syntaxTree()]. ++%% -spec forms(atom(), any()) -> [erl_syntax:syntaxTree()]. + forms(Module, T) -> + [erl_syntax:revert(X) || X <- term_to_abstract(Module, term, T)]. + +--spec term_to_abstract(atom(), atom(), any()) -> [erl_syntax:syntaxTree()]. ++%% -spec term_to_abstract(atom(), atom(), any()) -> [erl_syntax:syntaxTree()]. + term_to_abstract(Module, Getter, T) -> + [%% -module(Module). + erl_syntax:attribute( +diff --git a/src/mochiutf8.erl b/src/mochiutf8.erl +index 28f28c1..c9d2751 100644 +--- a/src/mochiutf8.erl ++++ b/src/mochiutf8.erl +@@ -11,11 +11,11 @@ + + %% External API + +--type unichar_low() :: 0..16#d7ff. +--type unichar_high() :: 16#e000..16#10ffff. +--type unichar() :: unichar_low() | unichar_high(). ++%% -type unichar_low() :: 0..16#d7ff. ++%% -type unichar_high() :: 16#e000..16#10ffff. ++%% -type unichar() :: unichar_low() | unichar_high(). + +--spec codepoint_to_bytes(unichar()) -> binary(). ++%% -spec codepoint_to_bytes(unichar()) -> binary(). + %% @doc Convert a unicode codepoint to UTF-8 bytes. + codepoint_to_bytes(C) when (C >= 16#00 andalso C =< 16#7f) -> + %% U+0000 - U+007F - 7 bits +@@ -40,12 +40,12 @@ codepoint_to_bytes(C) when (C >= 16#010000 andalso C =< 16#10FFFF) -> + 2#10:2, B1:6, + 2#10:2, B0:6>>. + +--spec codepoints_to_bytes([unichar()]) -> binary(). ++%% -spec codepoints_to_bytes([unichar()]) -> binary(). + %% @doc Convert a list of codepoints to a UTF-8 binary. + codepoints_to_bytes(L) -> + <<<<(codepoint_to_bytes(C))/binary>> || C <- L>>. + +--spec read_codepoint(binary()) -> {unichar(), binary(), binary()}. ++%% -spec read_codepoint(binary()) -> {unichar(), binary(), binary()}. + read_codepoint(Bin = <<2#0:1, C:7, Rest/binary>>) -> + %% U+0000 - U+007F - 7 bits + <> = Bin, +@@ -82,32 +82,32 @@ read_codepoint(Bin = <<2#11110:5, B3:3, + {C, B, Rest} + end. + +--spec codepoint_foldl(fun((unichar(), _) -> _), _, binary()) -> _. ++%% -spec codepoint_foldl(fun((unichar(), _) -> _), _, binary()) -> _. + codepoint_foldl(F, Acc, <<>>) when is_function(F, 2) -> + Acc; + codepoint_foldl(F, Acc, Bin) -> + {C, _, Rest} = read_codepoint(Bin), + codepoint_foldl(F, F(C, Acc), Rest). + +--spec bytes_foldl(fun((binary(), _) -> _), _, binary()) -> _. ++%% -spec bytes_foldl(fun((binary(), _) -> _), _, binary()) -> _. + bytes_foldl(F, Acc, <<>>) when is_function(F, 2) -> + Acc; + bytes_foldl(F, Acc, Bin) -> + {_, B, Rest} = read_codepoint(Bin), + bytes_foldl(F, F(B, Acc), Rest). + +--spec bytes_to_codepoints(binary()) -> [unichar()]. ++%% -spec bytes_to_codepoints(binary()) -> [unichar()]. + bytes_to_codepoints(B) -> + lists:reverse(codepoint_foldl(fun (C, Acc) -> [C | Acc] end, [], B)). + +--spec len(binary()) -> non_neg_integer(). ++%% -spec len(binary()) -> non_neg_integer(). + len(<<>>) -> + 0; + len(B) -> + {_, _, Rest} = read_codepoint(B), + 1 + len(Rest). + +--spec valid_utf8_bytes(B::binary()) -> binary(). ++%% -spec valid_utf8_bytes(B::binary()) -> binary(). + %% @doc Return only the bytes in B that represent valid UTF-8. Uses + %% the following recursive algorithm: skip one byte if B does not + %% follow UTF-8 syntax (a 1-4 byte encoding of some number), +@@ -118,7 +118,7 @@ valid_utf8_bytes(B) when is_binary(B) -> + + %% Internal API + +--spec binary_skip_bytes(binary(), [non_neg_integer()]) -> binary(). ++%% -spec binary_skip_bytes(binary(), [non_neg_integer()]) -> binary(). + %% @doc Return B, but skipping the 0-based indexes in L. + binary_skip_bytes(B, []) -> + B; +@@ -126,7 +126,7 @@ binary_skip_bytes(B, L) -> + binary_skip_bytes(B, L, 0, []). + + %% @private +--spec binary_skip_bytes(binary(), [non_neg_integer()], non_neg_integer(), iolist()) -> binary(). ++%% -spec binary_skip_bytes(binary(), [non_neg_integer()], non_neg_integer(), iolist()) -> binary(). + binary_skip_bytes(B, [], _N, Acc) -> + iolist_to_binary(lists:reverse([B | Acc])); + binary_skip_bytes(<<_, RestB/binary>>, [N | RestL], N, Acc) -> +@@ -134,13 +134,13 @@ binary_skip_bytes(<<_, RestB/binary>>, [N | RestL], N, Acc) -> + binary_skip_bytes(<>, L, N, Acc) -> + binary_skip_bytes(RestB, L, 1 + N, [C | Acc]). + +--spec invalid_utf8_indexes(binary()) -> [non_neg_integer()]. ++%% -spec invalid_utf8_indexes(binary()) -> [non_neg_integer()]. + %% @doc Return the 0-based indexes in B that are not valid UTF-8. + invalid_utf8_indexes(B) -> + invalid_utf8_indexes(B, 0, []). + + %% @private. +--spec invalid_utf8_indexes(binary(), non_neg_integer(), [non_neg_integer()]) -> [non_neg_integer()]. ++%% -spec invalid_utf8_indexes(binary(), non_neg_integer(), [non_neg_integer()]) -> [non_neg_integer()]. + invalid_utf8_indexes(<>, N, Acc) when C < 16#80 -> + %% U+0000 - U+007F - 7 bits + invalid_utf8_indexes(Rest, 1 + N, Acc); +diff --git a/src/mochiweb_charref.erl b/src/mochiweb_charref.erl +index 193c7c7..665d0f9 100644 +--- a/src/mochiweb_charref.erl ++++ b/src/mochiweb_charref.erl +@@ -11,7 +11,7 @@ + %% codepoint, or return undefined on failure. + %% The input should not include an ampersand or semicolon. + %% charref("#38") = 38, charref("#x26") = 38, charref("amp") = 38. +--spec charref(binary() | string()) -> integer() | [integer()] | undefined. ++%% -spec charref(binary() | string()) -> integer() | [integer()] | undefined. + charref(B) when is_binary(B) -> + charref(binary_to_list(B)); + charref([$#, C | L]) when C =:= $x orelse C =:= $X -> +diff --git a/src/mochiweb_http.erl b/src/mochiweb_http.erl +index 931ecd0..ae6410f 100644 +--- a/src/mochiweb_http.erl ++++ b/src/mochiweb_http.erl +@@ -121,12 +121,12 @@ call_body({M, F}, Req) -> + call_body(Body, Req) -> + Body(Req). + +--spec handle_invalid_request(term()) -> no_return(). ++%% -spec handle_invalid_request(term()) -> no_return(). + handle_invalid_request(Socket) -> + handle_invalid_request(Socket, {'GET', {abs_path, "/"}, {0,9}}, []), + exit(normal). + +--spec handle_invalid_request(term(), term(), term()) -> no_return(). ++%% -spec handle_invalid_request(term(), term(), term()) -> no_return(). + handle_invalid_request(Socket, Request, RevHeaders) -> + Req = new_request(Socket, Request, RevHeaders), + Req:respond({400, [], []}), +diff --git a/src/mochiweb_session.erl b/src/mochiweb_session.erl +index ac5d66b..ddf7c46 100644 +--- a/src/mochiweb_session.erl ++++ b/src/mochiweb_session.erl +@@ -21,11 +21,11 @@ + + %% @doc Generates a secure encrypted binary convining all the parameters. The + %% expiration time must be a 32-bit integer. +--spec generate_session_data( +- ExpirationTime :: expiration_time(), +- Data :: iolist(), +- FSessionKey :: key_fun(), +- ServerKey :: iolist()) -> binary(). ++%% -spec generate_session_data( ++%% ExpirationTime :: expiration_time(), ++%% Data :: iolist(), ++%% FSessionKey :: key_fun(), ++%% ServerKey :: iolist()) -> binary(). + generate_session_data(ExpirationTime, Data, FSessionKey, ServerKey) + when is_integer(ExpirationTime), is_function(FSessionKey)-> + BData = ensure_binary(Data), +@@ -39,11 +39,11 @@ generate_session_data(ExpirationTime, Data, FSessionKey, ServerKey) + %% @doc Convenience wrapper for generate_session_data that returns a + %% mochiweb cookie with "id" as the key, a max_age of 20000 seconds, + %% and the current local time as local time. +--spec generate_session_cookie( +- ExpirationTime :: expiration_time(), +- Data :: iolist(), +- FSessionKey :: key_fun(), +- ServerKey :: iolist()) -> header(). ++%% -spec generate_session_cookie( ++%% ExpirationTime :: expiration_time(), ++%% Data :: iolist(), ++%% FSessionKey :: key_fun(), ++%% ServerKey :: iolist()) -> header(). + generate_session_cookie(ExpirationTime, Data, FSessionKey, ServerKey) + when is_integer(ExpirationTime), is_function(FSessionKey)-> + CookieData = generate_session_data(ExpirationTime, Data, +@@ -55,13 +55,13 @@ generate_session_cookie(ExpirationTime, Data, FSessionKey, ServerKey) + calendar:universal_time())}]). + + %% TODO: This return type is messy to express in the type system. +--spec check_session_cookie( +- ECookie :: binary(), +- ExpirationTime :: string(), +- FSessionKey :: key_fun(), +- ServerKey :: iolist()) -> +- {Success :: boolean(), +- ExpTimeAndData :: [integer() | binary()]}. ++%% -spec check_session_cookie( ++ %% ECookie :: binary(), ++ %% ExpirationTime :: string(), ++ %% FSessionKey :: key_fun(), ++ %% ServerKey :: iolist()) -> ++ %% {Success :: boolean(), ++ %% ExpTimeAndData :: [integer() | binary()]}. + check_session_cookie(ECookie, ExpirationTime, FSessionKey, ServerKey) + when is_binary(ECookie), is_integer(ExpirationTime), + is_function(FSessionKey) -> +@@ -83,7 +83,7 @@ check_session_cookie(_ECookie, _ExpirationTime, _FSessionKey, _ServerKey) -> + {false, []}. + + %% 'Constant' time =:= operator for binary, to mitigate timing attacks. +--spec eq(binary(), binary()) -> boolean(). ++%% -spec eq(binary(), binary()) -> boolean(). + eq(A, B) when is_binary(A) andalso is_binary(B) -> + eq(A, B, 0). + +@@ -94,27 +94,27 @@ eq(<<>>, <<>>, 0) -> + eq(_As, _Bs, _Acc) -> + false. + +--spec ensure_binary(iolist()) -> binary(). ++%% -spec ensure_binary(iolist()) -> binary(). + ensure_binary(B) when is_binary(B) -> + B; + ensure_binary(L) when is_list(L) -> + iolist_to_binary(L). + +--spec encrypt_data(binary(), binary()) -> binary(). ++%% -spec encrypt_data(binary(), binary()) -> binary(). + encrypt_data(Data, Key) -> + IV = crypto:rand_bytes(16), + Crypt = crypto:aes_cfb_128_encrypt(Key, IV, Data), + <>. + +--spec decrypt_data(binary(), binary()) -> binary(). ++%% -spec decrypt_data(binary(), binary()) -> binary(). + decrypt_data(<>, Key) -> + crypto:aes_cfb_128_decrypt(Key, IV, Crypt). + +--spec gen_key(iolist(), iolist()) -> binary(). ++%% -spec gen_key(iolist(), iolist()) -> binary(). + gen_key(ExpirationTime, ServerKey)-> + crypto:md5_mac(ServerKey, [ExpirationTime]). + +--spec gen_hmac(iolist(), binary(), iolist(), binary()) -> binary(). ++%% -spec gen_hmac(iolist(), binary(), iolist(), binary()) -> binary(). + gen_hmac(ExpirationTime, Data, SessionKey, Key) -> + crypto:sha_mac(Key, [ExpirationTime, Data, SessionKey]). + diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/20-MAX_RECV_BODY.patch b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/20-MAX_RECV_BODY.patch new file mode 100644 index 0000000..2656fa2 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/20-MAX_RECV_BODY.patch @@ -0,0 +1,13 @@ +diff --git a/src/mochiweb_request.erl b/src/mochiweb_request.erl +index 5d89662..6765ab0 100644 +--- a/src/mochiweb_request.erl ++++ b/src/mochiweb_request.erl +@@ -42,7 +42,7 @@ + -define(IDLE_TIMEOUT, 300000). + + % Maximum recv_body() length of 1MB +--define(MAX_RECV_BODY, (1024*1024)). ++-define(MAX_RECV_BODY, 104857600). + + %% @spec get_header_value(K) -> undefined | Value + %% @doc Get the value of a given request header. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/30-remove-crypto-ssl-dependencies.patch b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/30-remove-crypto-ssl-dependencies.patch new file mode 100644 index 0000000..0d5c85a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/30-remove-crypto-ssl-dependencies.patch @@ -0,0 +1,104 @@ +diff --git a/src/mochitemp.erl b/src/mochitemp.erl +index dda7863..f64876d 100644 +--- a/src/mochitemp.erl ++++ b/src/mochitemp.erl +@@ -1,7 +1,7 @@ + %% @author Bob Ippolito + %% @copyright 2010 Mochi Media, Inc. + +-%% @doc Create temporary files and directories. Requires crypto to be started. ++%% @doc Create temporary files and directories. + + -module(mochitemp). + -export([gettempdir/0]). +@@ -87,7 +87,7 @@ rngchars(N) -> + [rngchar() | rngchars(N - 1)]. + + rngchar() -> +- rngchar(crypto:rand_uniform(0, tuple_size(?SAFE_CHARS))). ++ rngchar(mochiweb_util:rand_uniform(0, tuple_size(?SAFE_CHARS))). + + rngchar(C) -> + element(1 + C, ?SAFE_CHARS). +@@ -177,7 +177,6 @@ gettempdir_cwd_test() -> + ok. + + rngchars_test() -> +- crypto:start(), + ?assertEqual( + "", + rngchars(0)), +@@ -199,7 +198,6 @@ rngchar_test() -> + ok. + + mkdtemp_n_failonce_test() -> +- crypto:start(), + D = mkdtemp(), + Path = filename:join([D, "testdir"]), + %% Toggle the existence of a dir so that it fails +@@ -246,7 +244,6 @@ make_dir_fail_test() -> + ok. + + mkdtemp_test() -> +- crypto:start(), + D = mkdtemp(), + ?assertEqual( + true, +@@ -257,7 +254,6 @@ mkdtemp_test() -> + ok. + + rmtempdir_test() -> +- crypto:start(), + D1 = mkdtemp(), + ?assertEqual( + true, +diff --git a/src/mochiweb.app.src b/src/mochiweb.app.src +index 8d75a3a..c98d8a0 100644 +--- a/src/mochiweb.app.src ++++ b/src/mochiweb.app.src +@@ -5,5 +5,5 @@ + {modules, []}, + {registered, []}, + {env, []}, +- {applications, [kernel, stdlib, crypto, inets, ssl, xmerl, ++ {applications, [kernel, stdlib, inets, xmerl, + compiler, syntax_tools]}]}. +diff --git a/src/mochiweb_multipart.erl b/src/mochiweb_multipart.erl +index a83a88c..a4857d6 100644 +--- a/src/mochiweb_multipart.erl ++++ b/src/mochiweb_multipart.erl +@@ -38,7 +38,7 @@ parts_to_body([{Start, End, Body}], ContentType, Size) -> + {HeaderList, Body}; + parts_to_body(BodyList, ContentType, Size) when is_list(BodyList) -> + parts_to_multipart_body(BodyList, ContentType, Size, +- mochihex:to_hex(crypto:rand_bytes(8))). ++ mochihex:to_hex(mochiweb_util:rand_bytes(8))). + + %% @spec parts_to_multipart_body([bodypart()], ContentType::string(), + %% Size::integer(), Boundary::string()) -> +diff --git a/src/mochiweb_util.erl b/src/mochiweb_util.erl +index 4d39990..a0bc2bc 100644 +--- a/src/mochiweb_util.erl ++++ b/src/mochiweb_util.erl +@@ -13,7 +13,7 @@ + -export([record_to_proplist/2, record_to_proplist/3]). + -export([safe_relative_path/1, partition/2]). + -export([parse_qvalues/1, pick_accepted_encodings/3]). +--export([make_io/1]). ++-export([make_io/1, rand_bytes/1, rand_uniform/2]). + + -define(PERCENT, 37). % $\% + -define(FULLSTOP, 46). % $\. +@@ -581,6 +581,12 @@ make_io(Integer) when is_integer(Integer) -> + make_io(Io) when is_list(Io); is_binary(Io) -> + Io. + ++rand_bytes(Count) -> ++ list_to_binary([rand_uniform(0, 16#FF + 1) || _ <- lists:seq(1, Count)]). ++ ++rand_uniform(Lo, Hi) -> ++ random:uniform(Hi - Lo) + Lo - 1. ++ + %% + %% Tests + %% diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/40-remove-compiler-syntax_tools-dependencies.patch b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/40-remove-compiler-syntax_tools-dependencies.patch new file mode 100644 index 0000000..c9938e5 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/40-remove-compiler-syntax_tools-dependencies.patch @@ -0,0 +1,124 @@ +diff --git a/src/mochiglobal.erl b/src/mochiglobal.erl +deleted file mode 100644 +index 6b20e41..0000000 +--- a/src/mochiglobal.erl ++++ /dev/null +@@ -1,107 +0,0 @@ +-%% @author Bob Ippolito +-%% @copyright 2010 Mochi Media, Inc. +-%% @doc Abuse module constant pools as a "read-only shared heap" (since erts 5.6) +-%% [1]. +--module(mochiglobal). +--author("Bob Ippolito "). +--export([get/1, get/2, put/2, delete/1]). +- +-%% -spec get(atom()) -> any() | undefined. +-%% @equiv get(K, undefined) +-get(K) -> +- get(K, undefined). +- +-%% -spec get(atom(), T) -> any() | T. +-%% @doc Get the term for K or return Default. +-get(K, Default) -> +- get(K, Default, key_to_module(K)). +- +-get(_K, Default, Mod) -> +- try Mod:term() +- catch error:undef -> +- Default +- end. +- +-%% -spec put(atom(), any()) -> ok. +-%% @doc Store term V at K, replaces an existing term if present. +-put(K, V) -> +- put(K, V, key_to_module(K)). +- +-put(_K, V, Mod) -> +- Bin = compile(Mod, V), +- code:purge(Mod), +- {module, Mod} = code:load_binary(Mod, atom_to_list(Mod) ++ ".erl", Bin), +- ok. +- +-%% -spec delete(atom()) -> boolean(). +-%% @doc Delete term stored at K, no-op if non-existent. +-delete(K) -> +- delete(K, key_to_module(K)). +- +-delete(_K, Mod) -> +- code:purge(Mod), +- code:delete(Mod). +- +-%% -spec key_to_module(atom()) -> atom(). +-key_to_module(K) -> +- list_to_atom("mochiglobal:" ++ atom_to_list(K)). +- +-%% -spec compile(atom(), any()) -> binary(). +-compile(Module, T) -> +- {ok, Module, Bin} = compile:forms(forms(Module, T), +- [verbose, report_errors]), +- Bin. +- +-%% -spec forms(atom(), any()) -> [erl_syntax:syntaxTree()]. +-forms(Module, T) -> +- [erl_syntax:revert(X) || X <- term_to_abstract(Module, term, T)]. +- +-%% -spec term_to_abstract(atom(), atom(), any()) -> [erl_syntax:syntaxTree()]. +-term_to_abstract(Module, Getter, T) -> +- [%% -module(Module). +- erl_syntax:attribute( +- erl_syntax:atom(module), +- [erl_syntax:atom(Module)]), +- %% -export([Getter/0]). +- erl_syntax:attribute( +- erl_syntax:atom(export), +- [erl_syntax:list( +- [erl_syntax:arity_qualifier( +- erl_syntax:atom(Getter), +- erl_syntax:integer(0))])]), +- %% Getter() -> T. +- erl_syntax:function( +- erl_syntax:atom(Getter), +- [erl_syntax:clause([], none, [erl_syntax:abstract(T)])])]. +- +-%% +-%% Tests +-%% +--ifdef(TEST). +--include_lib("eunit/include/eunit.hrl"). +-get_put_delete_test() -> +- K = '$$test$$mochiglobal', +- delete(K), +- ?assertEqual( +- bar, +- get(K, bar)), +- try +- ?MODULE:put(K, baz), +- ?assertEqual( +- baz, +- get(K, bar)), +- ?MODULE:put(K, wibble), +- ?assertEqual( +- wibble, +- ?MODULE:get(K)) +- after +- delete(K) +- end, +- ?assertEqual( +- bar, +- get(K, bar)), +- ?assertEqual( +- undefined, +- ?MODULE:get(K)), +- ok. +--endif. +diff --git a/src/mochiweb.app.src b/src/mochiweb.app.src +index c98d8a0..4a6808e 100644 +--- a/src/mochiweb.app.src ++++ b/src/mochiweb.app.src +@@ -5,5 +5,4 @@ + {modules, []}, + {registered, []}, + {env, []}, +- {applications, [kernel, stdlib, inets, xmerl, +- compiler, syntax_tools]}]}. ++ {applications, [kernel, stdlib, inets, xmerl]}]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/50-remove-json.patch b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/50-remove-json.patch new file mode 100644 index 0000000..8c7597f --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/50-remove-json.patch @@ -0,0 +1,1255 @@ +diff --git a/src/mochijson2.erl b/src/mochijson2.erl +deleted file mode 100644 +index 2b8d16e..0000000 +--- a/src/mochijson2.erl ++++ /dev/null +@@ -1,889 +0,0 @@ +-%% @author Bob Ippolito +-%% @copyright 2007 Mochi Media, Inc. +- +-%% @doc Yet another JSON (RFC 4627) library for Erlang. mochijson2 works +-%% with binaries as strings, arrays as lists (without an {array, _}) +-%% wrapper and it only knows how to decode UTF-8 (and ASCII). +-%% +-%% JSON terms are decoded as follows (javascript -> erlang): +-%%
    +-%%
  • {"key": "value"} -> +-%% {struct, [{<<"key">>, <<"value">>}]}
  • +-%%
  • ["array", 123, 12.34, true, false, null] -> +-%% [<<"array">>, 123, 12.34, true, false, null] +-%%
  • +-%%
+-%%
    +-%%
  • Strings in JSON decode to UTF-8 binaries in Erlang
  • +-%%
  • Objects decode to {struct, PropList}
  • +-%%
  • Numbers decode to integer or float
  • +-%%
  • true, false, null decode to their respective terms.
  • +-%%
+-%% The encoder will accept the same format that the decoder will produce, +-%% but will also allow additional cases for leniency: +-%%
    +-%%
  • atoms other than true, false, null will be considered UTF-8 +-%% strings (even as a proplist key) +-%%
  • +-%%
  • {json, IoList} will insert IoList directly into the output +-%% with no validation +-%%
  • +-%%
  • {array, Array} will be encoded as Array +-%% (legacy mochijson style) +-%%
  • +-%%
  • A non-empty raw proplist will be encoded as an object as long +-%% as the first pair does not have an atom key of json, struct, +-%% or array +-%%
  • +-%%
+- +--module(mochijson2). +--author('bob@mochimedia.com'). +--export([encoder/1, encode/1]). +--export([decoder/1, decode/1, decode/2]). +- +-%% This is a macro to placate syntax highlighters.. +--define(Q, $\"). +--define(ADV_COL(S, N), S#decoder{offset=N+S#decoder.offset, +- column=N+S#decoder.column}). +--define(INC_COL(S), S#decoder{offset=1+S#decoder.offset, +- column=1+S#decoder.column}). +--define(INC_LINE(S), S#decoder{offset=1+S#decoder.offset, +- column=1, +- line=1+S#decoder.line}). +--define(INC_CHAR(S, C), +- case C of +- $\n -> +- S#decoder{column=1, +- line=1+S#decoder.line, +- offset=1+S#decoder.offset}; +- _ -> +- S#decoder{column=1+S#decoder.column, +- offset=1+S#decoder.offset} +- end). +--define(IS_WHITESPACE(C), +- (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)). +- +-%% @type json_string() = atom | binary() +-%% @type json_number() = integer() | float() +-%% @type json_array() = [json_term()] +-%% @type json_object() = {struct, [{json_string(), json_term()}]} +-%% @type json_eep18_object() = {[{json_string(), json_term()}]} +-%% @type json_iolist() = {json, iolist()} +-%% @type json_term() = json_string() | json_number() | json_array() | +-%% json_object() | json_eep18_object() | json_iolist() +- +--record(encoder, {handler=null, +- utf8=false}). +- +--record(decoder, {object_hook=null, +- offset=0, +- line=1, +- column=1, +- state=null}). +- +-%% @spec encoder([encoder_option()]) -> function() +-%% @doc Create an encoder/1 with the given options. +-%% @type encoder_option() = handler_option() | utf8_option() +-%% @type utf8_option() = boolean(). Emit unicode as utf8 (default - false) +-encoder(Options) -> +- State = parse_encoder_options(Options, #encoder{}), +- fun (O) -> json_encode(O, State) end. +- +-%% @spec encode(json_term()) -> iolist() +-%% @doc Encode the given as JSON to an iolist. +-encode(Any) -> +- json_encode(Any, #encoder{}). +- +-%% @spec decoder([decoder_option()]) -> function() +-%% @doc Create a decoder/1 with the given options. +-decoder(Options) -> +- State = parse_decoder_options(Options, #decoder{}), +- fun (O) -> json_decode(O, State) end. +- +-%% @spec decode(iolist(), [{format, proplist | eep18 | struct}]) -> json_term() +-%% @doc Decode the given iolist to Erlang terms using the given object format +-%% for decoding, where proplist returns JSON objects as [{binary(), json_term()}] +-%% proplists, eep18 returns JSON objects as {[binary(), json_term()]}, and struct +-%% returns them as-is. +-decode(S, Options) -> +- json_decode(S, parse_decoder_options(Options, #decoder{})). +- +-%% @spec decode(iolist()) -> json_term() +-%% @doc Decode the given iolist to Erlang terms. +-decode(S) -> +- json_decode(S, #decoder{}). +- +-%% Internal API +- +-parse_encoder_options([], State) -> +- State; +-parse_encoder_options([{handler, Handler} | Rest], State) -> +- parse_encoder_options(Rest, State#encoder{handler=Handler}); +-parse_encoder_options([{utf8, Switch} | Rest], State) -> +- parse_encoder_options(Rest, State#encoder{utf8=Switch}). +- +-parse_decoder_options([], State) -> +- State; +-parse_decoder_options([{object_hook, Hook} | Rest], State) -> +- parse_decoder_options(Rest, State#decoder{object_hook=Hook}); +-parse_decoder_options([{format, Format} | Rest], State) +- when Format =:= struct orelse Format =:= eep18 orelse Format =:= proplist -> +- parse_decoder_options(Rest, State#decoder{object_hook=Format}). +- +-json_encode(true, _State) -> +- <<"true">>; +-json_encode(false, _State) -> +- <<"false">>; +-json_encode(null, _State) -> +- <<"null">>; +-json_encode(I, _State) when is_integer(I) -> +- integer_to_list(I); +-json_encode(F, _State) when is_float(F) -> +- mochinum:digits(F); +-json_encode(S, State) when is_binary(S); is_atom(S) -> +- json_encode_string(S, State); +-json_encode([{K, _}|_] = Props, State) when (K =/= struct andalso +- K =/= array andalso +- K =/= json) -> +- json_encode_proplist(Props, State); +-json_encode({struct, Props}, State) when is_list(Props) -> +- json_encode_proplist(Props, State); +-json_encode({Props}, State) when is_list(Props) -> +- json_encode_proplist(Props, State); +-json_encode({}, State) -> +- json_encode_proplist([], State); +-json_encode(Array, State) when is_list(Array) -> +- json_encode_array(Array, State); +-json_encode({array, Array}, State) when is_list(Array) -> +- json_encode_array(Array, State); +-json_encode({json, IoList}, _State) -> +- IoList; +-json_encode(Bad, #encoder{handler=null}) -> +- exit({json_encode, {bad_term, Bad}}); +-json_encode(Bad, State=#encoder{handler=Handler}) -> +- json_encode(Handler(Bad), State). +- +-json_encode_array([], _State) -> +- <<"[]">>; +-json_encode_array(L, State) -> +- F = fun (O, Acc) -> +- [$,, json_encode(O, State) | Acc] +- end, +- [$, | Acc1] = lists:foldl(F, "[", L), +- lists:reverse([$\] | Acc1]). +- +-json_encode_proplist([], _State) -> +- <<"{}">>; +-json_encode_proplist(Props, State) -> +- F = fun ({K, V}, Acc) -> +- KS = json_encode_string(K, State), +- VS = json_encode(V, State), +- [$,, VS, $:, KS | Acc] +- end, +- [$, | Acc1] = lists:foldl(F, "{", Props), +- lists:reverse([$\} | Acc1]). +- +-json_encode_string(A, State) when is_atom(A) -> +- L = atom_to_list(A), +- case json_string_is_safe(L) of +- true -> +- [?Q, L, ?Q]; +- false -> +- json_encode_string_unicode(xmerl_ucs:from_utf8(L), State, [?Q]) +- end; +-json_encode_string(B, State) when is_binary(B) -> +- case json_bin_is_safe(B) of +- true -> +- [?Q, B, ?Q]; +- false -> +- json_encode_string_unicode(xmerl_ucs:from_utf8(B), State, [?Q]) +- end; +-json_encode_string(I, _State) when is_integer(I) -> +- [?Q, integer_to_list(I), ?Q]; +-json_encode_string(L, State) when is_list(L) -> +- case json_string_is_safe(L) of +- true -> +- [?Q, L, ?Q]; +- false -> +- json_encode_string_unicode(L, State, [?Q]) +- end. +- +-json_string_is_safe([]) -> +- true; +-json_string_is_safe([C | Rest]) -> +- case C of +- ?Q -> +- false; +- $\\ -> +- false; +- $\b -> +- false; +- $\f -> +- false; +- $\n -> +- false; +- $\r -> +- false; +- $\t -> +- false; +- C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF -> +- false; +- C when C < 16#7f -> +- json_string_is_safe(Rest); +- _ -> +- false +- end. +- +-json_bin_is_safe(<<>>) -> +- true; +-json_bin_is_safe(<>) -> +- case C of +- ?Q -> +- false; +- $\\ -> +- false; +- $\b -> +- false; +- $\f -> +- false; +- $\n -> +- false; +- $\r -> +- false; +- $\t -> +- false; +- C when C >= 0, C < $\s; C >= 16#7f -> +- false; +- C when C < 16#7f -> +- json_bin_is_safe(Rest) +- end. +- +-json_encode_string_unicode([], _State, Acc) -> +- lists:reverse([$\" | Acc]); +-json_encode_string_unicode([C | Cs], State, Acc) -> +- Acc1 = case C of +- ?Q -> +- [?Q, $\\ | Acc]; +- %% Escaping solidus is only useful when trying to protect +- %% against "" injection attacks which are only +- %% possible when JSON is inserted into a HTML document +- %% in-line. mochijson2 does not protect you from this, so +- %% if you do insert directly into HTML then you need to +- %% uncomment the following case or escape the output of encode. +- %% +- %% $/ -> +- %% [$/, $\\ | Acc]; +- %% +- $\\ -> +- [$\\, $\\ | Acc]; +- $\b -> +- [$b, $\\ | Acc]; +- $\f -> +- [$f, $\\ | Acc]; +- $\n -> +- [$n, $\\ | Acc]; +- $\r -> +- [$r, $\\ | Acc]; +- $\t -> +- [$t, $\\ | Acc]; +- C when C >= 0, C < $\s -> +- [unihex(C) | Acc]; +- C when C >= 16#7f, C =< 16#10FFFF, State#encoder.utf8 -> +- [xmerl_ucs:to_utf8(C) | Acc]; +- C when C >= 16#7f, C =< 16#10FFFF, not State#encoder.utf8 -> +- [unihex(C) | Acc]; +- C when C < 16#7f -> +- [C | Acc]; +- _ -> +- exit({json_encode, {bad_char, C}}) +- end, +- json_encode_string_unicode(Cs, State, Acc1). +- +-hexdigit(C) when C >= 0, C =< 9 -> +- C + $0; +-hexdigit(C) when C =< 15 -> +- C + $a - 10. +- +-unihex(C) when C < 16#10000 -> +- <> = <>, +- Digits = [hexdigit(D) || D <- [D3, D2, D1, D0]], +- [$\\, $u | Digits]; +-unihex(C) when C =< 16#10FFFF -> +- N = C - 16#10000, +- S1 = 16#d800 bor ((N bsr 10) band 16#3ff), +- S2 = 16#dc00 bor (N band 16#3ff), +- [unihex(S1), unihex(S2)]. +- +-json_decode(L, S) when is_list(L) -> +- json_decode(iolist_to_binary(L), S); +-json_decode(B, S) -> +- {Res, S1} = decode1(B, S), +- {eof, _} = tokenize(B, S1#decoder{state=trim}), +- Res. +- +-decode1(B, S=#decoder{state=null}) -> +- case tokenize(B, S#decoder{state=any}) of +- {{const, C}, S1} -> +- {C, S1}; +- {start_array, S1} -> +- decode_array(B, S1); +- {start_object, S1} -> +- decode_object(B, S1) +- end. +- +-make_object(V, #decoder{object_hook=N}) when N =:= null orelse N =:= struct -> +- V; +-make_object({struct, P}, #decoder{object_hook=eep18}) -> +- {P}; +-make_object({struct, P}, #decoder{object_hook=proplist}) -> +- P; +-make_object(V, #decoder{object_hook=Hook}) -> +- Hook(V). +- +-decode_object(B, S) -> +- decode_object(B, S#decoder{state=key}, []). +- +-decode_object(B, S=#decoder{state=key}, Acc) -> +- case tokenize(B, S) of +- {end_object, S1} -> +- V = make_object({struct, lists:reverse(Acc)}, S1), +- {V, S1#decoder{state=null}}; +- {{const, K}, S1} -> +- {colon, S2} = tokenize(B, S1), +- {V, S3} = decode1(B, S2#decoder{state=null}), +- decode_object(B, S3#decoder{state=comma}, [{K, V} | Acc]) +- end; +-decode_object(B, S=#decoder{state=comma}, Acc) -> +- case tokenize(B, S) of +- {end_object, S1} -> +- V = make_object({struct, lists:reverse(Acc)}, S1), +- {V, S1#decoder{state=null}}; +- {comma, S1} -> +- decode_object(B, S1#decoder{state=key}, Acc) +- end. +- +-decode_array(B, S) -> +- decode_array(B, S#decoder{state=any}, []). +- +-decode_array(B, S=#decoder{state=any}, Acc) -> +- case tokenize(B, S) of +- {end_array, S1} -> +- {lists:reverse(Acc), S1#decoder{state=null}}; +- {start_array, S1} -> +- {Array, S2} = decode_array(B, S1), +- decode_array(B, S2#decoder{state=comma}, [Array | Acc]); +- {start_object, S1} -> +- {Array, S2} = decode_object(B, S1), +- decode_array(B, S2#decoder{state=comma}, [Array | Acc]); +- {{const, Const}, S1} -> +- decode_array(B, S1#decoder{state=comma}, [Const | Acc]) +- end; +-decode_array(B, S=#decoder{state=comma}, Acc) -> +- case tokenize(B, S) of +- {end_array, S1} -> +- {lists:reverse(Acc), S1#decoder{state=null}}; +- {comma, S1} -> +- decode_array(B, S1#decoder{state=any}, Acc) +- end. +- +-tokenize_string(B, S=#decoder{offset=O}) -> +- case tokenize_string_fast(B, O) of +- {escape, O1} -> +- Length = O1 - O, +- S1 = ?ADV_COL(S, Length), +- <<_:O/binary, Head:Length/binary, _/binary>> = B, +- tokenize_string(B, S1, lists:reverse(binary_to_list(Head))); +- O1 -> +- Length = O1 - O, +- <<_:O/binary, String:Length/binary, ?Q, _/binary>> = B, +- {{const, String}, ?ADV_COL(S, Length + 1)} +- end. +- +-tokenize_string_fast(B, O) -> +- case B of +- <<_:O/binary, ?Q, _/binary>> -> +- O; +- <<_:O/binary, $\\, _/binary>> -> +- {escape, O}; +- <<_:O/binary, C1, _/binary>> when C1 < 128 -> +- tokenize_string_fast(B, 1 + O); +- <<_:O/binary, C1, C2, _/binary>> when C1 >= 194, C1 =< 223, +- C2 >= 128, C2 =< 191 -> +- tokenize_string_fast(B, 2 + O); +- <<_:O/binary, C1, C2, C3, _/binary>> when C1 >= 224, C1 =< 239, +- C2 >= 128, C2 =< 191, +- C3 >= 128, C3 =< 191 -> +- tokenize_string_fast(B, 3 + O); +- <<_:O/binary, C1, C2, C3, C4, _/binary>> when C1 >= 240, C1 =< 244, +- C2 >= 128, C2 =< 191, +- C3 >= 128, C3 =< 191, +- C4 >= 128, C4 =< 191 -> +- tokenize_string_fast(B, 4 + O); +- _ -> +- throw(invalid_utf8) +- end. +- +-tokenize_string(B, S=#decoder{offset=O}, Acc) -> +- case B of +- <<_:O/binary, ?Q, _/binary>> -> +- {{const, iolist_to_binary(lists:reverse(Acc))}, ?INC_COL(S)}; +- <<_:O/binary, "\\\"", _/binary>> -> +- tokenize_string(B, ?ADV_COL(S, 2), [$\" | Acc]); +- <<_:O/binary, "\\\\", _/binary>> -> +- tokenize_string(B, ?ADV_COL(S, 2), [$\\ | Acc]); +- <<_:O/binary, "\\/", _/binary>> -> +- tokenize_string(B, ?ADV_COL(S, 2), [$/ | Acc]); +- <<_:O/binary, "\\b", _/binary>> -> +- tokenize_string(B, ?ADV_COL(S, 2), [$\b | Acc]); +- <<_:O/binary, "\\f", _/binary>> -> +- tokenize_string(B, ?ADV_COL(S, 2), [$\f | Acc]); +- <<_:O/binary, "\\n", _/binary>> -> +- tokenize_string(B, ?ADV_COL(S, 2), [$\n | Acc]); +- <<_:O/binary, "\\r", _/binary>> -> +- tokenize_string(B, ?ADV_COL(S, 2), [$\r | Acc]); +- <<_:O/binary, "\\t", _/binary>> -> +- tokenize_string(B, ?ADV_COL(S, 2), [$\t | Acc]); +- <<_:O/binary, "\\u", C3, C2, C1, C0, Rest/binary>> -> +- C = erlang:list_to_integer([C3, C2, C1, C0], 16), +- if C > 16#D7FF, C < 16#DC00 -> +- %% coalesce UTF-16 surrogate pair +- <<"\\u", D3, D2, D1, D0, _/binary>> = Rest, +- D = erlang:list_to_integer([D3,D2,D1,D0], 16), +- [CodePoint] = xmerl_ucs:from_utf16be(<>), +- Acc1 = lists:reverse(xmerl_ucs:to_utf8(CodePoint), Acc), +- tokenize_string(B, ?ADV_COL(S, 12), Acc1); +- true -> +- Acc1 = lists:reverse(xmerl_ucs:to_utf8(C), Acc), +- tokenize_string(B, ?ADV_COL(S, 6), Acc1) +- end; +- <<_:O/binary, C1, _/binary>> when C1 < 128 -> +- tokenize_string(B, ?INC_CHAR(S, C1), [C1 | Acc]); +- <<_:O/binary, C1, C2, _/binary>> when C1 >= 194, C1 =< 223, +- C2 >= 128, C2 =< 191 -> +- tokenize_string(B, ?ADV_COL(S, 2), [C2, C1 | Acc]); +- <<_:O/binary, C1, C2, C3, _/binary>> when C1 >= 224, C1 =< 239, +- C2 >= 128, C2 =< 191, +- C3 >= 128, C3 =< 191 -> +- tokenize_string(B, ?ADV_COL(S, 3), [C3, C2, C1 | Acc]); +- <<_:O/binary, C1, C2, C3, C4, _/binary>> when C1 >= 240, C1 =< 244, +- C2 >= 128, C2 =< 191, +- C3 >= 128, C3 =< 191, +- C4 >= 128, C4 =< 191 -> +- tokenize_string(B, ?ADV_COL(S, 4), [C4, C3, C2, C1 | Acc]); +- _ -> +- throw(invalid_utf8) +- end. +- +-tokenize_number(B, S) -> +- case tokenize_number(B, sign, S, []) of +- {{int, Int}, S1} -> +- {{const, list_to_integer(Int)}, S1}; +- {{float, Float}, S1} -> +- {{const, list_to_float(Float)}, S1} +- end. +- +-tokenize_number(B, sign, S=#decoder{offset=O}, []) -> +- case B of +- <<_:O/binary, $-, _/binary>> -> +- tokenize_number(B, int, ?INC_COL(S), [$-]); +- _ -> +- tokenize_number(B, int, S, []) +- end; +-tokenize_number(B, int, S=#decoder{offset=O}, Acc) -> +- case B of +- <<_:O/binary, $0, _/binary>> -> +- tokenize_number(B, frac, ?INC_COL(S), [$0 | Acc]); +- <<_:O/binary, C, _/binary>> when C >= $1 andalso C =< $9 -> +- tokenize_number(B, int1, ?INC_COL(S), [C | Acc]) +- end; +-tokenize_number(B, int1, S=#decoder{offset=O}, Acc) -> +- case B of +- <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> +- tokenize_number(B, int1, ?INC_COL(S), [C | Acc]); +- _ -> +- tokenize_number(B, frac, S, Acc) +- end; +-tokenize_number(B, frac, S=#decoder{offset=O}, Acc) -> +- case B of +- <<_:O/binary, $., C, _/binary>> when C >= $0, C =< $9 -> +- tokenize_number(B, frac1, ?ADV_COL(S, 2), [C, $. | Acc]); +- <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E -> +- tokenize_number(B, esign, ?INC_COL(S), [$e, $0, $. | Acc]); +- _ -> +- {{int, lists:reverse(Acc)}, S} +- end; +-tokenize_number(B, frac1, S=#decoder{offset=O}, Acc) -> +- case B of +- <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> +- tokenize_number(B, frac1, ?INC_COL(S), [C | Acc]); +- <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E -> +- tokenize_number(B, esign, ?INC_COL(S), [$e | Acc]); +- _ -> +- {{float, lists:reverse(Acc)}, S} +- end; +-tokenize_number(B, esign, S=#decoder{offset=O}, Acc) -> +- case B of +- <<_:O/binary, C, _/binary>> when C =:= $- orelse C=:= $+ -> +- tokenize_number(B, eint, ?INC_COL(S), [C | Acc]); +- _ -> +- tokenize_number(B, eint, S, Acc) +- end; +-tokenize_number(B, eint, S=#decoder{offset=O}, Acc) -> +- case B of +- <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> +- tokenize_number(B, eint1, ?INC_COL(S), [C | Acc]) +- end; +-tokenize_number(B, eint1, S=#decoder{offset=O}, Acc) -> +- case B of +- <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> +- tokenize_number(B, eint1, ?INC_COL(S), [C | Acc]); +- _ -> +- {{float, lists:reverse(Acc)}, S} +- end. +- +-tokenize(B, S=#decoder{offset=O}) -> +- case B of +- <<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) -> +- tokenize(B, ?INC_CHAR(S, C)); +- <<_:O/binary, "{", _/binary>> -> +- {start_object, ?INC_COL(S)}; +- <<_:O/binary, "}", _/binary>> -> +- {end_object, ?INC_COL(S)}; +- <<_:O/binary, "[", _/binary>> -> +- {start_array, ?INC_COL(S)}; +- <<_:O/binary, "]", _/binary>> -> +- {end_array, ?INC_COL(S)}; +- <<_:O/binary, ",", _/binary>> -> +- {comma, ?INC_COL(S)}; +- <<_:O/binary, ":", _/binary>> -> +- {colon, ?INC_COL(S)}; +- <<_:O/binary, "null", _/binary>> -> +- {{const, null}, ?ADV_COL(S, 4)}; +- <<_:O/binary, "true", _/binary>> -> +- {{const, true}, ?ADV_COL(S, 4)}; +- <<_:O/binary, "false", _/binary>> -> +- {{const, false}, ?ADV_COL(S, 5)}; +- <<_:O/binary, "\"", _/binary>> -> +- tokenize_string(B, ?INC_COL(S)); +- <<_:O/binary, C, _/binary>> when (C >= $0 andalso C =< $9) +- orelse C =:= $- -> +- tokenize_number(B, S); +- <<_:O/binary>> -> +- trim = S#decoder.state, +- {eof, S} +- end. +-%% +-%% Tests +-%% +--ifdef(TEST). +--include_lib("eunit/include/eunit.hrl"). +- +- +-%% testing constructs borrowed from the Yaws JSON implementation. +- +-%% Create an object from a list of Key/Value pairs. +- +-obj_new() -> +- {struct, []}. +- +-is_obj({struct, Props}) -> +- F = fun ({K, _}) when is_binary(K) -> true end, +- lists:all(F, Props). +- +-obj_from_list(Props) -> +- Obj = {struct, Props}, +- ?assert(is_obj(Obj)), +- Obj. +- +-%% Test for equivalence of Erlang terms. +-%% Due to arbitrary order of construction, equivalent objects might +-%% compare unequal as erlang terms, so we need to carefully recurse +-%% through aggregates (tuples and objects). +- +-equiv({struct, Props1}, {struct, Props2}) -> +- equiv_object(Props1, Props2); +-equiv(L1, L2) when is_list(L1), is_list(L2) -> +- equiv_list(L1, L2); +-equiv(N1, N2) when is_number(N1), is_number(N2) -> N1 == N2; +-equiv(B1, B2) when is_binary(B1), is_binary(B2) -> B1 == B2; +-equiv(A, A) when A =:= true orelse A =:= false orelse A =:= null -> true. +- +-%% Object representation and traversal order is unknown. +-%% Use the sledgehammer and sort property lists. +- +-equiv_object(Props1, Props2) -> +- L1 = lists:keysort(1, Props1), +- L2 = lists:keysort(1, Props2), +- Pairs = lists:zip(L1, L2), +- true = lists:all(fun({{K1, V1}, {K2, V2}}) -> +- equiv(K1, K2) and equiv(V1, V2) +- end, Pairs). +- +-%% Recursively compare tuple elements for equivalence. +- +-equiv_list([], []) -> +- true; +-equiv_list([V1 | L1], [V2 | L2]) -> +- equiv(V1, V2) andalso equiv_list(L1, L2). +- +-decode_test() -> +- [1199344435545.0, 1] = decode(<<"[1199344435545.0,1]">>), +- <<16#F0,16#9D,16#9C,16#95>> = decode([34,"\\ud835","\\udf15",34]). +- +-e2j_vec_test() -> +- test_one(e2j_test_vec(utf8), 1). +- +-test_one([], _N) -> +- %% io:format("~p tests passed~n", [N-1]), +- ok; +-test_one([{E, J} | Rest], N) -> +- %% io:format("[~p] ~p ~p~n", [N, E, J]), +- true = equiv(E, decode(J)), +- true = equiv(E, decode(encode(E))), +- test_one(Rest, 1+N). +- +-e2j_test_vec(utf8) -> +- [ +- {1, "1"}, +- {3.1416, "3.14160"}, %% text representation may truncate, trail zeroes +- {-1, "-1"}, +- {-3.1416, "-3.14160"}, +- {12.0e10, "1.20000e+11"}, +- {1.234E+10, "1.23400e+10"}, +- {-1.234E-10, "-1.23400e-10"}, +- {10.0, "1.0e+01"}, +- {123.456, "1.23456E+2"}, +- {10.0, "1e1"}, +- {<<"foo">>, "\"foo\""}, +- {<<"foo", 5, "bar">>, "\"foo\\u0005bar\""}, +- {<<"">>, "\"\""}, +- {<<"\n\n\n">>, "\"\\n\\n\\n\""}, +- {<<"\" \b\f\r\n\t\"">>, "\"\\\" \\b\\f\\r\\n\\t\\\"\""}, +- {obj_new(), "{}"}, +- {obj_from_list([{<<"foo">>, <<"bar">>}]), "{\"foo\":\"bar\"}"}, +- {obj_from_list([{<<"foo">>, <<"bar">>}, {<<"baz">>, 123}]), +- "{\"foo\":\"bar\",\"baz\":123}"}, +- {[], "[]"}, +- {[[]], "[[]]"}, +- {[1, <<"foo">>], "[1,\"foo\"]"}, +- +- %% json array in a json object +- {obj_from_list([{<<"foo">>, [123]}]), +- "{\"foo\":[123]}"}, +- +- %% json object in a json object +- {obj_from_list([{<<"foo">>, obj_from_list([{<<"bar">>, true}])}]), +- "{\"foo\":{\"bar\":true}}"}, +- +- %% fold evaluation order +- {obj_from_list([{<<"foo">>, []}, +- {<<"bar">>, obj_from_list([{<<"baz">>, true}])}, +- {<<"alice">>, <<"bob">>}]), +- "{\"foo\":[],\"bar\":{\"baz\":true},\"alice\":\"bob\"}"}, +- +- %% json object in a json array +- {[-123, <<"foo">>, obj_from_list([{<<"bar">>, []}]), null], +- "[-123,\"foo\",{\"bar\":[]},null]"} +- ]. +- +-%% test utf8 encoding +-encoder_utf8_test() -> +- %% safe conversion case (default) +- [34,"\\u0001","\\u0442","\\u0435","\\u0441","\\u0442",34] = +- encode(<<1,"\321\202\320\265\321\201\321\202">>), +- +- %% raw utf8 output (optional) +- Enc = mochijson2:encoder([{utf8, true}]), +- [34,"\\u0001",[209,130],[208,181],[209,129],[209,130],34] = +- Enc(<<1,"\321\202\320\265\321\201\321\202">>). +- +-input_validation_test() -> +- Good = [ +- {16#00A3, <>}, %% pound +- {16#20AC, <>}, %% euro +- {16#10196, <>} %% denarius +- ], +- lists:foreach(fun({CodePoint, UTF8}) -> +- Expect = list_to_binary(xmerl_ucs:to_utf8(CodePoint)), +- Expect = decode(UTF8) +- end, Good), +- +- Bad = [ +- %% 2nd, 3rd, or 4th byte of a multi-byte sequence w/o leading byte +- <>, +- %% missing continuations, last byte in each should be 80-BF +- <>, +- <>, +- <>, +- %% we don't support code points > 10FFFF per RFC 3629 +- <>, +- %% escape characters trigger a different code path +- <> +- ], +- lists:foreach( +- fun(X) -> +- ok = try decode(X) catch invalid_utf8 -> ok end, +- %% could be {ucs,{bad_utf8_character_code}} or +- %% {json_encode,{bad_char,_}} +- {'EXIT', _} = (catch encode(X)) +- end, Bad). +- +-inline_json_test() -> +- ?assertEqual(<<"\"iodata iodata\"">>, +- iolist_to_binary( +- encode({json, [<<"\"iodata">>, " iodata\""]}))), +- ?assertEqual({struct, [{<<"key">>, <<"iodata iodata">>}]}, +- decode( +- encode({struct, +- [{key, {json, [<<"\"iodata">>, " iodata\""]}}]}))), +- ok. +- +-big_unicode_test() -> +- UTF8Seq = list_to_binary(xmerl_ucs:to_utf8(16#0001d120)), +- ?assertEqual( +- <<"\"\\ud834\\udd20\"">>, +- iolist_to_binary(encode(UTF8Seq))), +- ?assertEqual( +- UTF8Seq, +- decode(iolist_to_binary(encode(UTF8Seq)))), +- ok. +- +-custom_decoder_test() -> +- ?assertEqual( +- {struct, [{<<"key">>, <<"value">>}]}, +- (decoder([]))("{\"key\": \"value\"}")), +- F = fun ({struct, [{<<"key">>, <<"value">>}]}) -> win end, +- ?assertEqual( +- win, +- (decoder([{object_hook, F}]))("{\"key\": \"value\"}")), +- ok. +- +-atom_test() -> +- %% JSON native atoms +- [begin +- ?assertEqual(A, decode(atom_to_list(A))), +- ?assertEqual(iolist_to_binary(atom_to_list(A)), +- iolist_to_binary(encode(A))) +- end || A <- [true, false, null]], +- %% Atom to string +- ?assertEqual( +- <<"\"foo\"">>, +- iolist_to_binary(encode(foo))), +- ?assertEqual( +- <<"\"\\ud834\\udd20\"">>, +- iolist_to_binary(encode(list_to_atom(xmerl_ucs:to_utf8(16#0001d120))))), +- ok. +- +-key_encode_test() -> +- %% Some forms are accepted as keys that would not be strings in other +- %% cases +- ?assertEqual( +- <<"{\"foo\":1}">>, +- iolist_to_binary(encode({struct, [{foo, 1}]}))), +- ?assertEqual( +- <<"{\"foo\":1}">>, +- iolist_to_binary(encode({struct, [{<<"foo">>, 1}]}))), +- ?assertEqual( +- <<"{\"foo\":1}">>, +- iolist_to_binary(encode({struct, [{"foo", 1}]}))), +- ?assertEqual( +- <<"{\"foo\":1}">>, +- iolist_to_binary(encode([{foo, 1}]))), +- ?assertEqual( +- <<"{\"foo\":1}">>, +- iolist_to_binary(encode([{<<"foo">>, 1}]))), +- ?assertEqual( +- <<"{\"foo\":1}">>, +- iolist_to_binary(encode([{"foo", 1}]))), +- ?assertEqual( +- <<"{\"\\ud834\\udd20\":1}">>, +- iolist_to_binary( +- encode({struct, [{[16#0001d120], 1}]}))), +- ?assertEqual( +- <<"{\"1\":1}">>, +- iolist_to_binary(encode({struct, [{1, 1}]}))), +- ok. +- +-unsafe_chars_test() -> +- Chars = "\"\\\b\f\n\r\t", +- [begin +- ?assertEqual(false, json_string_is_safe([C])), +- ?assertEqual(false, json_bin_is_safe(<>)), +- ?assertEqual(<>, decode(encode(<>))) +- end || C <- Chars], +- ?assertEqual( +- false, +- json_string_is_safe([16#0001d120])), +- ?assertEqual( +- false, +- json_bin_is_safe(list_to_binary(xmerl_ucs:to_utf8(16#0001d120)))), +- ?assertEqual( +- [16#0001d120], +- xmerl_ucs:from_utf8( +- binary_to_list( +- decode(encode(list_to_atom(xmerl_ucs:to_utf8(16#0001d120))))))), +- ?assertEqual( +- false, +- json_string_is_safe([16#110000])), +- ?assertEqual( +- false, +- json_bin_is_safe(list_to_binary(xmerl_ucs:to_utf8([16#110000])))), +- %% solidus can be escaped but isn't unsafe by default +- ?assertEqual( +- <<"/">>, +- decode(<<"\"\\/\"">>)), +- ok. +- +-int_test() -> +- ?assertEqual(0, decode("0")), +- ?assertEqual(1, decode("1")), +- ?assertEqual(11, decode("11")), +- ok. +- +-large_int_test() -> +- ?assertEqual(<<"-2147483649214748364921474836492147483649">>, +- iolist_to_binary(encode(-2147483649214748364921474836492147483649))), +- ?assertEqual(<<"2147483649214748364921474836492147483649">>, +- iolist_to_binary(encode(2147483649214748364921474836492147483649))), +- ok. +- +-float_test() -> +- ?assertEqual(<<"-2147483649.0">>, iolist_to_binary(encode(-2147483649.0))), +- ?assertEqual(<<"2147483648.0">>, iolist_to_binary(encode(2147483648.0))), +- ok. +- +-handler_test() -> +- ?assertEqual( +- {'EXIT',{json_encode,{bad_term,{x,y}}}}, +- catch encode({x,y})), +- F = fun ({x,y}) -> [] end, +- ?assertEqual( +- <<"[]">>, +- iolist_to_binary((encoder([{handler, F}]))({x, y}))), +- ok. +- +-encode_empty_test_() -> +- [{A, ?_assertEqual(<<"{}">>, iolist_to_binary(encode(B)))} +- || {A, B} <- [{"eep18 {}", {}}, +- {"eep18 {[]}", {[]}}, +- {"{struct, []}", {struct, []}}]]. +- +-encode_test_() -> +- P = [{<<"k">>, <<"v">>}], +- JSON = iolist_to_binary(encode({struct, P})), +- [{atom_to_list(F), +- ?_assertEqual(JSON, iolist_to_binary(encode(decode(JSON, [{format, F}]))))} +- || F <- [struct, eep18, proplist]]. +- +-format_test_() -> +- P = [{<<"k">>, <<"v">>}], +- JSON = iolist_to_binary(encode({struct, P})), +- [{atom_to_list(F), +- ?_assertEqual(A, decode(JSON, [{format, F}]))} +- || {F, A} <- [{struct, {struct, P}}, +- {eep18, {P}}, +- {proplist, P}]]. +- +--endif. +diff --git a/src/mochinum.erl b/src/mochinum.erl +deleted file mode 100644 +index c52b15c..0000000 +--- a/src/mochinum.erl ++++ /dev/null +@@ -1,354 +0,0 @@ +-%% @copyright 2007 Mochi Media, Inc. +-%% @author Bob Ippolito +- +-%% @doc Useful numeric algorithms for floats that cover some deficiencies +-%% in the math module. More interesting is digits/1, which implements +-%% the algorithm from: +-%% http://www.cs.indiana.edu/~burger/fp/index.html +-%% See also "Printing Floating-Point Numbers Quickly and Accurately" +-%% in Proceedings of the SIGPLAN '96 Conference on Programming Language +-%% Design and Implementation. +- +--module(mochinum). +--author("Bob Ippolito "). +--export([digits/1, frexp/1, int_pow/2, int_ceil/1]). +- +-%% IEEE 754 Float exponent bias +--define(FLOAT_BIAS, 1022). +--define(MIN_EXP, -1074). +--define(BIG_POW, 4503599627370496). +- +-%% External API +- +-%% @spec digits(number()) -> string() +-%% @doc Returns a string that accurately represents the given integer or float +-%% using a conservative amount of digits. Great for generating +-%% human-readable output, or compact ASCII serializations for floats. +-digits(N) when is_integer(N) -> +- integer_to_list(N); +-digits(0.0) -> +- "0.0"; +-digits(Float) -> +- {Frac1, Exp1} = frexp_int(Float), +- [Place0 | Digits0] = digits1(Float, Exp1, Frac1), +- {Place, Digits} = transform_digits(Place0, Digits0), +- R = insert_decimal(Place, Digits), +- case Float < 0 of +- true -> +- [$- | R]; +- _ -> +- R +- end. +- +-%% @spec frexp(F::float()) -> {Frac::float(), Exp::float()} +-%% @doc Return the fractional and exponent part of an IEEE 754 double, +-%% equivalent to the libc function of the same name. +-%% F = Frac * pow(2, Exp). +-frexp(F) -> +- frexp1(unpack(F)). +- +-%% @spec int_pow(X::integer(), N::integer()) -> Y::integer() +-%% @doc Moderately efficient way to exponentiate integers. +-%% int_pow(10, 2) = 100. +-int_pow(_X, 0) -> +- 1; +-int_pow(X, N) when N > 0 -> +- int_pow(X, N, 1). +- +-%% @spec int_ceil(F::float()) -> integer() +-%% @doc Return the ceiling of F as an integer. The ceiling is defined as +-%% F when F == trunc(F); +-%% trunc(F) when F < 0; +-%% trunc(F) + 1 when F > 0. +-int_ceil(X) -> +- T = trunc(X), +- case (X - T) of +- Pos when Pos > 0 -> T + 1; +- _ -> T +- end. +- +- +-%% Internal API +- +-int_pow(X, N, R) when N < 2 -> +- R * X; +-int_pow(X, N, R) -> +- int_pow(X * X, N bsr 1, case N band 1 of 1 -> R * X; 0 -> R end). +- +-insert_decimal(0, S) -> +- "0." ++ S; +-insert_decimal(Place, S) when Place > 0 -> +- L = length(S), +- case Place - L of +- 0 -> +- S ++ ".0"; +- N when N < 0 -> +- {S0, S1} = lists:split(L + N, S), +- S0 ++ "." ++ S1; +- N when N < 6 -> +- %% More places than digits +- S ++ lists:duplicate(N, $0) ++ ".0"; +- _ -> +- insert_decimal_exp(Place, S) +- end; +-insert_decimal(Place, S) when Place > -6 -> +- "0." ++ lists:duplicate(abs(Place), $0) ++ S; +-insert_decimal(Place, S) -> +- insert_decimal_exp(Place, S). +- +-insert_decimal_exp(Place, S) -> +- [C | S0] = S, +- S1 = case S0 of +- [] -> +- "0"; +- _ -> +- S0 +- end, +- Exp = case Place < 0 of +- true -> +- "e-"; +- false -> +- "e+" +- end, +- [C] ++ "." ++ S1 ++ Exp ++ integer_to_list(abs(Place - 1)). +- +- +-digits1(Float, Exp, Frac) -> +- Round = ((Frac band 1) =:= 0), +- case Exp >= 0 of +- true -> +- BExp = 1 bsl Exp, +- case (Frac =/= ?BIG_POW) of +- true -> +- scale((Frac * BExp * 2), 2, BExp, BExp, +- Round, Round, Float); +- false -> +- scale((Frac * BExp * 4), 4, (BExp * 2), BExp, +- Round, Round, Float) +- end; +- false -> +- case (Exp =:= ?MIN_EXP) orelse (Frac =/= ?BIG_POW) of +- true -> +- scale((Frac * 2), 1 bsl (1 - Exp), 1, 1, +- Round, Round, Float); +- false -> +- scale((Frac * 4), 1 bsl (2 - Exp), 2, 1, +- Round, Round, Float) +- end +- end. +- +-scale(R, S, MPlus, MMinus, LowOk, HighOk, Float) -> +- Est = int_ceil(math:log10(abs(Float)) - 1.0e-10), +- %% Note that the scheme implementation uses a 326 element look-up table +- %% for int_pow(10, N) where we do not. +- case Est >= 0 of +- true -> +- fixup(R, S * int_pow(10, Est), MPlus, MMinus, Est, +- LowOk, HighOk); +- false -> +- Scale = int_pow(10, -Est), +- fixup(R * Scale, S, MPlus * Scale, MMinus * Scale, Est, +- LowOk, HighOk) +- end. +- +-fixup(R, S, MPlus, MMinus, K, LowOk, HighOk) -> +- TooLow = case HighOk of +- true -> +- (R + MPlus) >= S; +- false -> +- (R + MPlus) > S +- end, +- case TooLow of +- true -> +- [(K + 1) | generate(R, S, MPlus, MMinus, LowOk, HighOk)]; +- false -> +- [K | generate(R * 10, S, MPlus * 10, MMinus * 10, LowOk, HighOk)] +- end. +- +-generate(R0, S, MPlus, MMinus, LowOk, HighOk) -> +- D = R0 div S, +- R = R0 rem S, +- TC1 = case LowOk of +- true -> +- R =< MMinus; +- false -> +- R < MMinus +- end, +- TC2 = case HighOk of +- true -> +- (R + MPlus) >= S; +- false -> +- (R + MPlus) > S +- end, +- case TC1 of +- false -> +- case TC2 of +- false -> +- [D | generate(R * 10, S, MPlus * 10, MMinus * 10, +- LowOk, HighOk)]; +- true -> +- [D + 1] +- end; +- true -> +- case TC2 of +- false -> +- [D]; +- true -> +- case R * 2 < S of +- true -> +- [D]; +- false -> +- [D + 1] +- end +- end +- end. +- +-unpack(Float) -> +- <> = <>, +- {Sign, Exp, Frac}. +- +-frexp1({_Sign, 0, 0}) -> +- {0.0, 0}; +-frexp1({Sign, 0, Frac}) -> +- Exp = log2floor(Frac), +- <> = <>, +- {Frac1, -(?FLOAT_BIAS) - 52 + Exp}; +-frexp1({Sign, Exp, Frac}) -> +- <> = <>, +- {Frac1, Exp - ?FLOAT_BIAS}. +- +-log2floor(Int) -> +- log2floor(Int, 0). +- +-log2floor(0, N) -> +- N; +-log2floor(Int, N) -> +- log2floor(Int bsr 1, 1 + N). +- +- +-transform_digits(Place, [0 | Rest]) -> +- transform_digits(Place, Rest); +-transform_digits(Place, Digits) -> +- {Place, [$0 + D || D <- Digits]}. +- +- +-frexp_int(F) -> +- case unpack(F) of +- {_Sign, 0, Frac} -> +- {Frac, ?MIN_EXP}; +- {_Sign, Exp, Frac} -> +- {Frac + (1 bsl 52), Exp - 53 - ?FLOAT_BIAS} +- end. +- +-%% +-%% Tests +-%% +--ifdef(TEST). +--include_lib("eunit/include/eunit.hrl"). +- +-int_ceil_test() -> +- ?assertEqual(1, int_ceil(0.0001)), +- ?assertEqual(0, int_ceil(0.0)), +- ?assertEqual(1, int_ceil(0.99)), +- ?assertEqual(1, int_ceil(1.0)), +- ?assertEqual(-1, int_ceil(-1.5)), +- ?assertEqual(-2, int_ceil(-2.0)), +- ok. +- +-int_pow_test() -> +- ?assertEqual(1, int_pow(1, 1)), +- ?assertEqual(1, int_pow(1, 0)), +- ?assertEqual(1, int_pow(10, 0)), +- ?assertEqual(10, int_pow(10, 1)), +- ?assertEqual(100, int_pow(10, 2)), +- ?assertEqual(1000, int_pow(10, 3)), +- ok. +- +-digits_test() -> +- ?assertEqual("0", +- digits(0)), +- ?assertEqual("0.0", +- digits(0.0)), +- ?assertEqual("1.0", +- digits(1.0)), +- ?assertEqual("-1.0", +- digits(-1.0)), +- ?assertEqual("0.1", +- digits(0.1)), +- ?assertEqual("0.01", +- digits(0.01)), +- ?assertEqual("0.001", +- digits(0.001)), +- ?assertEqual("1.0e+6", +- digits(1000000.0)), +- ?assertEqual("0.5", +- digits(0.5)), +- ?assertEqual("4503599627370496.0", +- digits(4503599627370496.0)), +- %% small denormalized number +- %% 4.94065645841246544177e-324 =:= 5.0e-324 +- <> = <<0,0,0,0,0,0,0,1>>, +- ?assertEqual("5.0e-324", +- digits(SmallDenorm)), +- ?assertEqual(SmallDenorm, +- list_to_float(digits(SmallDenorm))), +- %% large denormalized number +- %% 2.22507385850720088902e-308 +- <> = <<0,15,255,255,255,255,255,255>>, +- ?assertEqual("2.225073858507201e-308", +- digits(BigDenorm)), +- ?assertEqual(BigDenorm, +- list_to_float(digits(BigDenorm))), +- %% small normalized number +- %% 2.22507385850720138309e-308 +- <> = <<0,16,0,0,0,0,0,0>>, +- ?assertEqual("2.2250738585072014e-308", +- digits(SmallNorm)), +- ?assertEqual(SmallNorm, +- list_to_float(digits(SmallNorm))), +- %% large normalized number +- %% 1.79769313486231570815e+308 +- <> = <<127,239,255,255,255,255,255,255>>, +- ?assertEqual("1.7976931348623157e+308", +- digits(LargeNorm)), +- ?assertEqual(LargeNorm, +- list_to_float(digits(LargeNorm))), +- %% issue #10 - mochinum:frexp(math:pow(2, -1074)). +- ?assertEqual("5.0e-324", +- digits(math:pow(2, -1074))), +- ok. +- +-frexp_test() -> +- %% zero +- ?assertEqual({0.0, 0}, frexp(0.0)), +- %% one +- ?assertEqual({0.5, 1}, frexp(1.0)), +- %% negative one +- ?assertEqual({-0.5, 1}, frexp(-1.0)), +- %% small denormalized number +- %% 4.94065645841246544177e-324 +- <> = <<0,0,0,0,0,0,0,1>>, +- ?assertEqual({0.5, -1073}, frexp(SmallDenorm)), +- %% large denormalized number +- %% 2.22507385850720088902e-308 +- <> = <<0,15,255,255,255,255,255,255>>, +- ?assertEqual( +- {0.99999999999999978, -1022}, +- frexp(BigDenorm)), +- %% small normalized number +- %% 2.22507385850720138309e-308 +- <> = <<0,16,0,0,0,0,0,0>>, +- ?assertEqual({0.5, -1021}, frexp(SmallNorm)), +- %% large normalized number +- %% 1.79769313486231570815e+308 +- <> = <<127,239,255,255,255,255,255,255>>, +- ?assertEqual( +- {0.99999999999999989, 1024}, +- frexp(LargeNorm)), +- %% issue #10 - mochinum:frexp(math:pow(2, -1074)). +- ?assertEqual( +- {0.5, -1073}, +- frexp(math:pow(2, -1074))), +- ok. +- +--endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/LICENSE-MIT-Mochi b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/LICENSE-MIT-Mochi new file mode 100644 index 0000000..c85b65a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/LICENSE-MIT-Mochi @@ -0,0 +1,9 @@ +This is the MIT license. + +Copyright (c) 2007 Mochi Media, Inc. + +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/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/Makefile b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/Makefile new file mode 100644 index 0000000..482105a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/Makefile @@ -0,0 +1 @@ +include ../umbrella.mk diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/hash.mk b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/hash.mk new file mode 100644 index 0000000..d1cebfa --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/hash.mk @@ -0,0 +1 @@ +UPSTREAM_SHORT_HASH:=680dba8 diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/license_info b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/license_info new file mode 100644 index 0000000..c72a6af --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/license_info @@ -0,0 +1,4 @@ +Mochiweb is "Copyright (c) 2007 Mochi Media, Inc." and is covered by +the MIT license. It was downloaded from +http://github.com/mochi/mochiweb/ + diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/.done b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/.done new file mode 100644 index 0000000..e69de29 diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/.travis.yml b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/.travis.yml new file mode 100644 index 0000000..43dad1a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/.travis.yml @@ -0,0 +1,7 @@ +language: erlang +notifications: + email: false +otp_release: + - R15B02 + - R15B03 + - R16B diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/CHANGES.md b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/CHANGES.md new file mode 100644 index 0000000..06a8b5f --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/CHANGES.md @@ -0,0 +1,91 @@ +Version 2.7.0 released XXXX-XX-XX + +* `mochiweb_socket_server:stop/1` is now a synchronous + call instead of an asynchronous cast +* `mochiweb_html:parse_tokens/1` (and `parse/1`) will now create a + html element to wrap documents that have a HTML5 doctype + (``) but no html element + https://github.com/mochi/mochiweb/issues/110 + +Version 2.6.0 released 2013-04-15 + +* Enable R15B gen_tcp workaround only on R15B + https://github.com/mochi/mochiweb/pull/107 + +Version 2.5.0 released 2013-03-04 + +* Replace now() with os:timestamp() in acceptor (optimization) + https://github.com/mochi/mochiweb/pull/102 +* New mochiweb_session module for managing session cookies. + NOTE: this module is only supported on R15B02 and later! + https://github.com/mochi/mochiweb/pull/94 +* New mochiweb_base64url module for base64url encoding + (URL and Filename safe alphabet, see RFC 4648). +* Fix rebar.config in mochiwebapp_skel to use {branch, "master"} + https://github.com/mochi/mochiweb/issues/105 + +Version 2.4.2 released 2013-02-05 + +* Fixed issue in mochiweb_response introduced in v2.4.0 + https://github.com/mochi/mochiweb/pull/100 + +Version 2.4.1 released 2013-01-30 + +* Fixed issue in mochiweb_request introduced in v2.4.0 + https://github.com/mochi/mochiweb/issues/97 +* Fixed issue in mochifmt_records introduced in v2.4.0 + https://github.com/mochi/mochiweb/issues/96 + +Version 2.4.0 released 2013-01-23 + +* Switch from parameterized modules to explicit tuple module calls for + R16 compatibility (#95) +* Fix for mochiweb_acceptor crash with extra-long HTTP headers under + R15B02 (#91) +* Fix case in handling range headers (#85) +* Handle combined Content-Length header (#88) +* Windows security fix for `safe_relative_path`, any path with a + backslash on any platform is now considered unsafe (#92) + +Version 2.3.2 released 2012-07-27 + +* Case insensitive match for "Connection: close" (#81) + +Version 2.3.1 released 2012-03-31 + +* Fix edoc warnings (#63) +* Fix mochiweb_html handling of invalid charref sequences (unescaped &) (#69). +* Add a manual garbage collection between requests to avoid worst case behavior + on keep-alive sockets. +* Fix dst cookie bug (#73) +* Removed unnecessary template_dir option, see + https://github.com/basho/rebar/issues/203 + +Version 2.3.0 released 2011-10-14 + +* Handle ssl_closed message in mochiweb_http (#59) +* Added support for new MIME types (otf, eot, m4v, svg, svgz, ttc, ttf, + vcf, webm, webp, woff) (#61) +* Updated mochiweb_charref to support all HTML5 entities. Note that + if you are using this module directly, the spec has changed to return + `[integer()]` for some entities. (#64) + +Version 2.2.1 released 2011-08-31 + +* Removed `mochiweb_skel` module from the pre-rebar era + +Version 2.2.0 released 2011-08-29 + +* Added new `mochiweb_http:start_link/1` and + `mochiweb_socket_server:start_link/1` APIs to explicitly start linked + servers. Also added `{link, false}` option to the `start/1` variants + to explicitly start unlinked. This is in expectation that we will + eventually change the default behavior of `start/1` to be unlinked as you + would expect it to. See https://github.com/mochi/mochiweb/issues/58 for + discussion. + +Version 2.1.0 released 2011-08-29 + +* Added new `mochijson2:decode/2` with `{format, struct | proplist | eep18}` + options for easy decoding to various proplist formats. Also added encoding + support for eep18 style objects. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/LICENSE b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/LICENSE new file mode 100644 index 0000000..c85b65a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/LICENSE @@ -0,0 +1,9 @@ +This is the MIT license. + +Copyright (c) 2007 Mochi Media, Inc. + +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/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/Makefile b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/Makefile new file mode 100644 index 0000000..9de1944 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/Makefile @@ -0,0 +1,29 @@ + +PREFIX:=../ +DEST:=$(PREFIX)$(PROJECT) + +REBAR=./rebar + +all: + @$(REBAR) get-deps compile + +edoc: + @$(REBAR) doc + +test: + @rm -rf .eunit + @mkdir -p .eunit + @$(REBAR) skip_deps=true eunit + +clean: + @$(REBAR) clean + +build_plt: + @$(REBAR) build-plt + +dialyzer: + @$(REBAR) dialyze + +app: + @$(REBAR) create template=mochiwebapp dest=$(DEST) appid=$(PROJECT) + diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/README b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/README new file mode 100644 index 0000000..80ee6e4 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/README @@ -0,0 +1,17 @@ +MochiWeb is an Erlang library for building lightweight HTTP servers. + +The latest version of MochiWeb is available at http://github.com/mochi/mochiweb + +The mailing list for MochiWeb is at http://groups.google.com/group/mochiweb/ + +R12B compatibility: +The master of MochiWeb is tested with R14A and later. A branch compatible +with R12B is maintained separately at http://github.com/lemenkov/mochiweb +The R12B branch of that repository is mirrored in the official repository +occasionally for convenience. + +To create a new mochiweb using project: + make app PROJECT=project_name + +To create a new mochiweb using project in a specific directory: + make app PROJECT=project_name PREFIX=$HOME/projects/ diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/hmac_api/README b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/hmac_api/README new file mode 100644 index 0000000..3771323 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/hmac_api/README @@ -0,0 +1,206 @@ +Introduction +------------ + +This example shows how to make an Amazon-style HMAC authentication system for an API with mochiweb. + +Purpose +------- + +The purpose of this example is to: +* make it easy to implement an API in mochiweb + - using a proven approach so that 'amateurs' don't have to reinvent crypto +* make it easy to generate client libraries for that API so that client-side implementers can: + - reuse closely related code examples + - build compatibility unit tests instead of fiddling around debugging their library against live implementations of the system + +Scope +----- + +The scope of this document is: +* a description of the client-server exchange +* a reference implementation of + - the server-side implementation of the exchange + - the client-side implementation of the exchange +* developing a custom implementation of an API +* deploying that implementation to new client-side users to build their client libraries + +Contents +-------- + +Subsequent sections of this document are: +* the client-server exchange +* the reference implementation in this example +* building a custom implementation +* deploying a custom implementation + +The Client-Server Exchange +-------------------------- + +OVERVIEW + +This section describes the client-server exchange for an Amazon-style API authentication schema. It has the following characteristics: +* based on a public key/private key +* used to authenticate non-SSL api requests +* not a full once-use schema and is vulnerable to replay attacks within a short time window + +TYPE OF API + +The api described in this document is: +* suitable for machine-machine communication + +The api described in this document is NOT: +* an implementation of 2-legged OAUTH + - see https://github.com/tim/erlang-oauth +* an implementation of 3-legged OAUTH + +It is not suitable for use in applications where an end user has to log into a service and piggy-back on top of a keypair security system. + +THE CLIENT LIBRARY HERE IS **NOT** AN AMAZON CLIENT LIBRARY. AMAZON DOES FUNKY STUFF WITH HOSTNAMES AND PUSHES THEM ONTO THE URL IN CANONICALIZATION! THE CLIENT LIBRARY IS AMAZON-A-LIKE ENOUGH TO USE THE AMAZON DOCOS TO BUILD A TEST SUITE. + +STEP 1 + +The client is issued with a pair of keys, one public, one private, for example: +* public: "bcaa49f2a4f7d4f92ac36c8bf66d5bb6" +* private: "92bc93d6b8aaec1cde772f903e06daf5" + +In the Amazon docs these are referred to as: +* AWSAccessKeyId (public) +* AWSSecretAccessKey (private) + +These can be generated by the function: +hmac_api_lib:get_api_keypair/0 + +This function returns cryptographically strong random numbers using the openSSL crypto library under the covers. + +The public key is used as a declaration of identity, "I am bcaa49..." + +The private key is never passed over the wire and is used to construct the same hash on both the client- and the server-side. + +STEP 2 + +The client prepares their request: +* url +* time of request +* action (GET, POST, etc) +* type of request (application/json, etc) +* contents of request +* etc, etc + +These components are then turned into a string called the canonical form. + +The HTTP protocol is permissive; it treats different requests as if they were the same. For instance it doesn't care about the order in which headers are sent, and allows the same header to contain multiple values as a list or be specified multiple times as a key-value pair. + +Intermediate machines between the client and server MAY pack and repack the HTTP request as long as they don't alter its meaning in a narrow sense. This means that the format of the HTTP request is not guaranteed to be maintained. + +The canonical form simply ensures that all the valid ways of making the same request are represented by the same string - irrespective of how this is done. + +The canonical form handles POST bodies and query parameters and silently discards anchors in URL's. + +A hash of this string is made with the private key. + +STEP 3 + +The client makes the request to the server: +* the signature is included in the request in the standard HTTPAuthorization header. (As the Amazon documentation points out this is infelicitous as it is being used for Authentication not Authorization, but hey!). + +The Authorization header constructed has the form: + + +An Amazon one looks like: +Authorization: AWS 0PN5J17HBGZHT7JJ3X82:frJIUN8DYpKDtOLCwo//yllqDzg= + --- -------------------- ---------------------------- + sch public key signature + +The HTTP request is made. + +STEP 4 + +The request is processed: +* the server receives the request +* the server constructs the canonical form from the attributes of the request: + - url + - date header + - action (GET, POST, etc) + - content type of request (application/json, etc) + - some custom headers + - etc, etc +* the server takes the client's public key from the HTTPAuthorization header and looks up the client's private key +* the server signs the canonical form with the private key +* the server compares: + - the signature in the request to the signature it has just generated + - the time encoded in the request with the server time +* the request is accepted or denied + +The time comparison is 'fuzzy'. Different server's clocks will be out of sync to a degree, the request may have acquired a time from an intermediate machine along the way, etc, etc. Normally a 'clock skew' time is allowed - in Amazon's case this is 15 minutes. + +NOTA BENE: THIS CLOCK SKEW TIME ALLOWS FOR REPLAY ATTACKS WHERE A BAD GUY SIMPLY CAPTURES AND REPLAYS TRAFFIC. + +EXTENSION + +It is possible to extend this schema to prevent replay attacks. The server issues a nonce token (a random string) which is included in the signature. When the server authorizes the request it stores the token and prevents any request with that token (ie a replay) being authorized again. + +The client receives its next nonce token in the response to a successful request. + +The Reference Implementation In This Example +-------------------------------------------- + +The reference implementation used in this example is that described in the Amazon documentation here: +http://docs.amazonwebservices.com/AmazonS3/latest/dev/index.html?RESTAuthentication.html + +The try out the reference implementation: +* create a new mochiweb project as per the mochiweb README + - make app PROJECT=project_name +* copy hmac_api_lib.erl and hmac_api_client.erl into project_name/src +* copy hmac_api.hrl into project_name/include +* edit project_name_web.erl and add a call to hmac_api_lib:authorize_request/1 + +authorize/request/1 should be called in the loop of project_name_web.erl as per: + + loop(Req, DocRoot) -> + Auth = hmac_api_lib:authorize_request(Req), + io:format("Auth is ~p~n", [Auth]), + "/" ++ Path = Req:get(path), + ... + +When this is done you are ready to test the api: +* run 'make' in project_name/ to build the Erlang +* start the web server with 'start-dev.sh' in project_name/ (this will also open an Erlang shell to the Erlang VM) + +To test the api run this command in the Erlang shell: +* hmac_api_client:fire(). + +The reference implementation uses 5 constants defined in hmac_api.hrl. +* schema +* headerprefix +* dateheader +* publickey +* privatekey + +Building A Custom Implementation +-------------------------------- + +The simplest custom implementation is to simply take the existing code and change the values of the following constants: +* schema +* headerprefix +* dateheader + +If the API is to be used 'as is', please use the values which are commented out in hmac_api.hrl. This will make easier for software developers to work out which version of which client-side libraries they can use. + +Client libraries written in other languages than Erlang can reemployment the test suite in hmac_api_lib.erl. + +More sophisticated changes will involve changes to the canonicalization functions. + +Use of a generic schema should make reuse of client libraries easier across different platforms. + +If you develop an ‘as-is’ client-side library in another language please consider submitting its code to this example. + +Deploying A Custom Implementation +--------------------------------- + +When deploying a custom implementation, the server-side code should be released with unit tests so the client-side developer can easily build a robust client. + +In addition to that you will need to specify: +* description of how the API works: + - ie the acceptable methods and urls + - custom headers and their usage (if appropriate) + diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/hmac_api/hmac_api.hrl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/hmac_api/hmac_api.hrl new file mode 100644 index 0000000..ddce280 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/hmac_api/hmac_api.hrl @@ -0,0 +1,43 @@ +-author("Hypernumbers Ltd "). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% %%% +%%% Reference values for testing against Amazon documents %%% +%%% %%% +%%% These need to be changed in production! %%% +%%% %%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-define(schema, "AWS"). +%% defines the prefix for headers to be included in the signature +-define(headerprefix, "x-amz-"). +%% defines the date header +-define(dateheader, "x-amz-date"). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% %%% +%%% Default values for defining a generic API %%% +%%% %%% +%%% Only change these if you alter the canonicalisation %%% +%%% %%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%-define(schema, "MOCHIAPI"). +%%-define(headerprefix, "x-mochiapi-"). +%%-define(dateheader, "x-mochiapi-date"). + +%% a couple of keys for testing +%% these are taken from the document +%% % http://docs.amazonwebservices.com/AmazonS3/latest/dev/index.html?RESTAuthentication.html +%% they are not valid keys! +-define(publickey, "0PN5J17HBGZHT7JJ3X82"). +-define(privatekey, "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o"). + + +-record(hmac_signature, + { + method, + contentmd5, + contenttype, + date, + headers, + resource + }). diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/hmac_api/hmac_api_client.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/hmac_api/hmac_api_client.erl new file mode 100644 index 0000000..a38fa96 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/hmac_api/hmac_api_client.erl @@ -0,0 +1,34 @@ +-module(hmac_api_client). + +-export([ + fire/0 + ]). + +-include("hmac_api.hrl"). +-author("Hypernumbers Ltd "). + +fire() -> + URL = "http://127.0.0.1:8080/some/page/yeah/", + %% Dates SHOULD conform to Section 3.3 of RFC2616 + %% the examples from the RFC are: + %% Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 + %% Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 + %% Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format + + %% Dates can be conveniently generated using dh_date.erl + %% https://github.com/daleharvey/dh_date + %% which is largely compatible with + %% http://uk.php.net/date + + %% You MIGHT find it convenient to insist on times in UTC only + %% as it reduces the errors caused by summer time and other + %% conversion issues + Method = post, + Headers = [{"content-type", "application/json"}, + {"date", "Sun, 10 Jul 2011 05:07:19"}], + ContentType = "application/json", + Body = "blah", + HTTPAuthHeader = hmac_api_lib:sign(?privatekey, Method, URL, + Headers, ContentType), + httpc:request(Method, {URL, [HTTPAuthHeader | Headers], + ContentType, Body}, [], []). diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/hmac_api/hmac_api_lib.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/hmac_api/hmac_api_lib.erl new file mode 100644 index 0000000..6d04954 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/hmac_api/hmac_api_lib.erl @@ -0,0 +1,435 @@ +-module(hmac_api_lib). + +-include("hmac_api.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-author("Hypernumbers Ltd "). + +%%% this library supports the hmac_sha api on both the client-side +%%% AND the server-side +%%% +%%% sign/5 is used client-side to sign a request +%%% - it returns an HTTPAuthorization header +%%% +%%% authorize_request/1 takes a mochiweb Request as an arguement +%%% and checks that the request matches the signature +%%% +%%% get_api_keypair/0 creates a pair of public/private keys +%%% +%%% THIS LIB DOESN'T IMPLEMENT THE AMAZON API IT ONLY IMPLEMENTS +%%% ENOUGH OF IT TO GENERATE A TEST SUITE. +%%% +%%% THE AMAZON API MUNGES HOSTNAME AND PATHS IN A CUSTOM WAY +%%% THIS IMPLEMENTATION DOESN'T +-export([ + authorize_request/1, + sign/5, + get_api_keypair/0 + ]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% %%% +%%% API %%% +%%% %%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +authorize_request(Req) -> + Method = Req:get(method), + Path = Req:get(path), + Headers = normalise(mochiweb_headers:to_list(Req:get(headers))), + ContentMD5 = get_header(Headers, "content-md5"), + ContentType = get_header(Headers, "content-type"), + Date = get_header(Headers, "date"), + IncAuth = get_header(Headers, "authorization"), + {_Schema, _PublicKey, _Sig} = breakout(IncAuth), + %% normally you would use the public key to look up the private key + PrivateKey = ?privatekey, + Signature = #hmac_signature{method = Method, + contentmd5 = ContentMD5, + contenttype = ContentType, + date = Date, + headers = Headers, + resource = Path}, + Signed = sign_data(PrivateKey, Signature), + {_, AuthHeader} = make_HTTPAuth_header(Signed), + case AuthHeader of + IncAuth -> "match"; + _ -> "no_match" + end. + +sign(PrivateKey, Method, URL, Headers, ContentType) -> + Headers2 = normalise(Headers), + ContentMD5 = get_header(Headers2, "content-md5"), + Date = get_header(Headers2, "date"), + Signature = #hmac_signature{method = Method, + contentmd5 = ContentMD5, + contenttype = ContentType, + date = Date, + headers = Headers, + resource = URL}, + SignedSig = sign_data(PrivateKey, Signature), + make_HTTPAuth_header(SignedSig). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% %%% +%%% Internal Functions %%% +%%% %%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +breakout(Header) -> + [Schema, Tail] = string:tokens(Header, " "), + [PublicKey, Signature] = string:tokens(Tail, ":"), + {Schema, PublicKey, Signature}. + +get_api_keypair() -> + Public = mochihex:to_hex(binary_to_list(crypto:strong_rand_bytes(16))), + Private = mochihex:to_hex(binary_to_list(crypto:strong_rand_bytes(16))), + {Public, Private}. + +make_HTTPAuth_header(Signature) -> + {"Authorization", ?schema ++ " " + ++ ?publickey ++ ":" ++ Signature}. + +make_signature_string(#hmac_signature{} = S) -> + Date = get_date(S#hmac_signature.headers, S#hmac_signature.date), + string:to_upper(atom_to_list(S#hmac_signature.method)) ++ "\n" + ++ S#hmac_signature.contentmd5 ++ "\n" + ++ S#hmac_signature.contenttype ++ "\n" + ++ Date ++ "\n" + ++ canonicalise_headers(S#hmac_signature.headers) + ++ canonicalise_resource(S#hmac_signature.resource). + +sign_data(PrivateKey, #hmac_signature{} = Signature) -> + Str = make_signature_string(Signature), + sign2(PrivateKey, Str). + +%% this fn is the entry point for a unit test which is why it is broken out... +%% if yer encryption and utf8 and base45 doo-dahs don't work then +%% yer Donald is well and truly Ducked so ye may as weel test it... +sign2(PrivateKey, Str) -> + Sign = xmerl_ucs:to_utf8(Str), + binary_to_list(base64:encode(crypto:sha_mac(PrivateKey, Sign))). + +canonicalise_headers([]) -> "\n"; +canonicalise_headers(List) when is_list(List) -> + List2 = [{string:to_lower(K), V} || {K, V} <- lists:sort(List)], + c_headers2(consolidate(List2, []), []). + +c_headers2([], Acc) -> string:join(Acc, "\n") ++ "\n"; +c_headers2([{?headerprefix ++ Rest, Key} | T], Acc) -> + Hd = string:strip(?headerprefix ++ Rest) ++ ":" ++ string:strip(Key), + c_headers2(T, [Hd | Acc]); +c_headers2([_H | T], Acc) -> c_headers2(T, Acc). + +consolidate([H | []], Acc) -> [H | Acc]; +consolidate([{H, K1}, {H, K2} | Rest], Acc) -> + consolidate([{H, join(K1, K2)} | Rest], Acc); +consolidate([{H1, K1}, {H2, K2} | Rest], Acc) -> + consolidate([{rectify(H2), rectify(K2)} | Rest], [{H1, K1} | Acc]). + +join(A, B) -> string:strip(A) ++ ";" ++ string:strip(B). + +%% removes line spacing as per RFC 2616 Section 4.2 +rectify(String) -> + Re = "[\x20* | \t*]+", + re:replace(String, Re, " ", [{return, list}, global]). + +canonicalise_resource("http://" ++ Rest) -> c_res2(Rest); +canonicalise_resource("https://" ++ Rest) -> c_res2(Rest); +canonicalise_resource(X) -> c_res3(X). + +c_res2(Rest) -> + N = string:str(Rest, "/"), + {_, Tail} = lists:split(N, Rest), + c_res3("/" ++ Tail). + +c_res3(Tail) -> + URL = case string:str(Tail, "#") of + 0 -> Tail; + N -> {U, _Anchor} = lists:split(N, Tail), + U + end, + U3 = case string:str(URL, "?") of + 0 -> URL; + N2 -> {U2, Q} = lists:split(N2, URL), + U2 ++ canonicalise_query(Q) + end, + string:to_lower(U3). + +canonicalise_query(List) -> + List1 = string:to_lower(List), + List2 = string:tokens(List1, "&"), + string:join(lists:sort(List2), "&"). + +%% if there's a header date take it and ditch the date +get_date([], Date) -> Date; +get_date([{K, _V} | T], Date) -> case string:to_lower(K) of + ?dateheader -> []; + _ -> get_date(T, Date) + end. + +normalise(List) -> norm2(List, []). + +norm2([], Acc) -> Acc; +norm2([{K, V} | T], Acc) when is_atom(K) -> + norm2(T, [{string:to_lower(atom_to_list(K)), V} | Acc]); +norm2([H | T], Acc) -> norm2(T, [H | Acc]). + +get_header(Headers, Type) -> + case lists:keyfind(Type, 1, Headers) of + false -> []; + {_K, V} -> V + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% %%% +%%% Unit Tests %%% +%%% %%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + % taken from Amazon docs +%% http://docs.amazonwebservices.com/AmazonS3/latest/dev/index.html?RESTAuthentication.html +hash_test1(_) -> + Sig = "DELETE\n\n\n\nx-amz-date:Tue, 27 Mar 2007 21:20:26 +0000\n/johnsmith/photos/puppy.jpg", + Key = ?privatekey, + Hash = sign2(Key, Sig), + Expected = "k3nL7gH3+PadhTEVn5Ip83xlYzk=", + ?assertEqual(Expected, Hash). + +%% taken from Amazon docs +%% http://docs.amazonwebservices.com/AmazonS3/latest/dev/index.html?RESTAuthentication.html +hash_test2(_) -> + Sig = "GET\n\n\nTue, 27 Mar 2007 19:44:46 +0000\n/johnsmith/?acl", + Key = "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o", + Hash = sign2(Key, Sig), + Expected = "thdUi9VAkzhkniLj96JIrOPGi0g=", + ?assertEqual(Expected, Hash). + +%% taken from Amazon docs +%% http://docs.amazonwebservices.com/AmazonS3/latest/dev/index.html?RESTAuthentication.html +hash_test3(_) -> + Sig = "GET\n\n\nWed, 28 Mar 2007 01:49:49 +0000\n/dictionary/" + ++ "fran%C3%A7ais/pr%c3%a9f%c3%a8re", + Key = "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o", + Hash = sign2(Key, Sig), + Expected = "dxhSBHoI6eVSPcXJqEghlUzZMnY=", + ?assertEqual(Expected, Hash). + +signature_test1(_) -> + URL = "http://example.com:90/tongs/ya/bas", + Method = post, + ContentMD5 = "", + ContentType = "", + Date = "Sun, 10 Jul 2011 05:07:19 UTC", + Headers = [], + Signature = #hmac_signature{method = Method, + contentmd5 = ContentMD5, + contenttype = ContentType, + date = Date, + headers = Headers, + resource = URL}, + Sig = make_signature_string(Signature), + Expected = "POST\n\n\nSun, 10 Jul 2011 05:07:19 UTC\n\n/tongs/ya/bas", + ?assertEqual(Expected, Sig). + +signature_test2(_) -> + URL = "http://example.com:90/tongs/ya/bas", + Method = get, + ContentMD5 = "", + ContentType = "", + Date = "Sun, 10 Jul 2011 05:07:19 UTC", + Headers = [{"x-amz-acl", "public-read"}], + Signature = #hmac_signature{method = Method, + contentmd5 = ContentMD5, + contenttype = ContentType, + date = Date, + headers = Headers, + resource = URL}, + Sig = make_signature_string(Signature), + Expected = "GET\n\n\nSun, 10 Jul 2011 05:07:19 UTC\nx-amz-acl:public-read\n/tongs/ya/bas", + ?assertEqual(Expected, Sig). + +signature_test3(_) -> + URL = "http://example.com:90/tongs/ya/bas", + Method = get, + ContentMD5 = "", + ContentType = "", + Date = "Sun, 10 Jul 2011 05:07:19 UTC", + Headers = [{"x-amz-acl", "public-read"}, + {"yantze", "blast-off"}, + {"x-amz-doobie", "bongwater"}, + {"x-amz-acl", "public-write"}], + Signature = #hmac_signature{method = Method, + contentmd5 = ContentMD5, + contenttype = ContentType, + date = Date, + headers = Headers, + resource = URL}, + Sig = make_signature_string(Signature), + Expected = "GET\n\n\nSun, 10 Jul 2011 05:07:19 UTC\nx-amz-acl:public-read;public-write\nx-amz-doobie:bongwater\n/tongs/ya/bas", + ?assertEqual(Expected, Sig). + +signature_test4(_) -> + URL = "http://example.com:90/tongs/ya/bas", + Method = get, + ContentMD5 = "", + ContentType = "", + Date = "Sun, 10 Jul 2011 05:07:19 UTC", + Headers = [{"x-amz-acl", "public-read"}, + {"yantze", "blast-off"}, + {"x-amz-doobie oobie \t boobie ", "bongwater"}, + {"x-amz-acl", "public-write"}], + Signature = #hmac_signature{method = Method, + contentmd5 = ContentMD5, + contenttype = ContentType, + date = Date, + headers = Headers, + resource = URL}, + Sig = make_signature_string(Signature), + Expected = "GET\n\n\nSun, 10 Jul 2011 05:07:19 UTC\nx-amz-acl:public-read;public-write\nx-amz-doobie oobie boobie:bongwater\n/tongs/ya/bas", + ?assertEqual(Expected, Sig). + +signature_test5(_) -> + URL = "http://example.com:90/tongs/ya/bas", + Method = get, + ContentMD5 = "", + ContentType = "", + Date = "Sun, 10 Jul 2011 05:07:19 UTC", + Headers = [{"x-amz-acl", "public-Read"}, + {"yantze", "Blast-Off"}, + {"x-amz-doobie Oobie \t boobie ", "bongwater"}, + {"x-amz-acl", "public-write"}], + Signature = #hmac_signature{method = Method, + contentmd5 = ContentMD5, + contenttype = ContentType, + date = Date, + headers = Headers, + resource = URL}, + Sig = make_signature_string(Signature), + Expected = "GET\n\n\nSun, 10 Jul 2011 05:07:19 UTC\nx-amz-acl:public-Read;public-write\nx-amz-doobie oobie boobie:bongwater\n/tongs/ya/bas", + ?assertEqual(Expected, Sig). + +signature_test6(_) -> + URL = "http://example.com:90/tongs/ya/bas/?andy&zbish=bash&bosh=burp", + Method = get, + ContentMD5 = "", + ContentType = "", + Date = "Sun, 10 Jul 2011 05:07:19 UTC", + Headers = [], + Signature = #hmac_signature{method = Method, + contentmd5 = ContentMD5, + contenttype = ContentType, + date = Date, + headers = Headers, + resource = URL}, + Sig = make_signature_string(Signature), + Expected = "GET\n\n\nSun, 10 Jul 2011 05:07:19 UTC\n\n" + ++ "/tongs/ya/bas/?andy&bosh=burp&zbish=bash", + ?assertEqual(Expected, Sig). + +signature_test7(_) -> + URL = "http://exAMPLE.Com:90/tONgs/ya/bas/?ANdy&ZBish=Bash&bOsh=burp", + Method = get, + ContentMD5 = "", + ContentType = "", + Date = "Sun, 10 Jul 2011 05:07:19 UTC", + Headers = [], + Signature = #hmac_signature{method = Method, + contentmd5 = ContentMD5, + contenttype = ContentType, + date = Date, + headers = Headers, + resource = URL}, + Sig = make_signature_string(Signature), + Expected = "GET\n\n\nSun, 10 Jul 2011 05:07:19 UTC\n\n" + ++"/tongs/ya/bas/?andy&bosh=burp&zbish=bash", + ?assertEqual(Expected, Sig). + +signature_test8(_) -> + URL = "http://exAMPLE.Com:90/tONgs/ya/bas/?ANdy&ZBish=Bash&bOsh=burp", + Method = get, + ContentMD5 = "", + ContentType = "", + Date = "", + Headers = [{"x-aMz-daTe", "Tue, 27 Mar 2007 21:20:26 +0000"}], + Signature = #hmac_signature{method = Method, + contentmd5 = ContentMD5, + contenttype = ContentType, + date = Date, + headers = Headers, + resource = URL}, + Sig = make_signature_string(Signature), + Expected = "GET\n\n\n\n" + ++"x-amz-date:Tue, 27 Mar 2007 21:20:26 +0000\n" + ++"/tongs/ya/bas/?andy&bosh=burp&zbish=bash", + ?assertEqual(Expected, Sig). + +signature_test9(_) -> + URL = "http://exAMPLE.Com:90/tONgs/ya/bas/?ANdy&ZBish=Bash&bOsh=burp", + Method = get, + ContentMD5 = "", + ContentType = "", + Date = "Sun, 10 Jul 2011 05:07:19 UTC", + Headers = [{"x-amz-date", "Tue, 27 Mar 2007 21:20:26 +0000"}], + Signature = #hmac_signature{method = Method, + contentmd5 = ContentMD5, + contenttype = ContentType, + date = Date, + headers = Headers, + resource = URL}, + Sig = make_signature_string(Signature), + Expected = "GET\n\n\n\n" + ++"x-amz-date:Tue, 27 Mar 2007 21:20:26 +0000\n" + ++"/tongs/ya/bas/?andy&bosh=burp&zbish=bash", + ?assertEqual(Expected, Sig). + +amazon_test1(_) -> + URL = "http://exAMPLE.Com:90/johnsmith/photos/puppy.jpg", + Method = delete, + ContentMD5 = "", + ContentType = "", + Date = "", + Headers = [{"x-amz-date", "Tue, 27 Mar 2007 21:20:26 +0000"}], + Signature = #hmac_signature{method = Method, + contentmd5 = ContentMD5, + contenttype = ContentType, + date = Date, + headers = Headers, + resource = URL}, + Sig = sign_data(?privatekey, Signature), + Expected = "k3nL7gH3+PadhTEVn5Ip83xlYzk=", + ?assertEqual(Expected, Sig). + +unit_test_() -> + Setup = fun() -> ok end, + Cleanup = fun(_) -> ok end, + + Series1 = [ + fun hash_test1/1, + fun hash_test2/1, + fun hash_test3/1 + ], + + Series2 = [ + fun signature_test1/1, + fun signature_test2/1, + fun signature_test3/1, + fun signature_test4/1, + fun signature_test5/1, + fun signature_test6/1, + fun signature_test7/1, + fun signature_test8/1, + fun signature_test9/1 + ], + + Series3 = [ + fun amazon_test1/1 + ], + + {setup, Setup, Cleanup, [ + {with, [], Series1}, + {with, [], Series2}, + {with, [], Series3} + ]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/https/https_store.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/https/https_store.erl new file mode 100644 index 0000000..959cc00 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/https/https_store.erl @@ -0,0 +1,146 @@ + +%% Trivial web storage app. It's available over both HTTP (port 8442) +%% and HTTPS (port 8443). You use a PUT to store items, a GET to +%% retrieve them and DELETE to delete them. The HTTP POST method is +%% invalid for this application. Example (using HTTPS transport): +%% +%% $ curl -k --verbose https://localhost:8443/flintstones +%% ... +%% 404 Not Found +%% ... +%% $ echo -e "Fred\nWilma\nBarney" | +%% curl -k --verbose https://localhost:8443/flintstones \ +%% -X PUT -H "Content-Type: text/plain" --data-binary @- +%% ... +%% 201 Created +%% ... +%% $ curl -k --verbose https://localhost:8443/flintstones +%% ... +%% Fred +%% Wilma +%% Barney +%% ... +%% $ curl -k --verbose https://localhost:8443/flintstones -X DELETE +%% ... +%% 200 OK +%% ... +%% $ curl -k --verbose https://localhost:8443/flintstones +%% ... +%% 404 Not Found +%% ... +%% +%% All submitted data is stored in memory (in an ets table). Could be +%% useful for ad-hoc testing. + +-module(https_store). + +-export([start/0, + stop/0, + dispatch/1, + loop/1 + ]). + +-define(HTTP_OPTS, [ + {loop, {?MODULE, dispatch}}, + {port, 8442}, + {name, http_8442} + ]). + +-define(HTTPS_OPTS, [ + {loop, {?MODULE, dispatch}}, + {port, 8443}, + {name, https_8443}, + {ssl, true}, + {ssl_opts, [ + {certfile, "server_cert.pem"}, + {keyfile, "server_key.pem"}]} + ]). + +-record(sd, {http, https}). +-record(resource, {type, data}). + +start() -> + {ok, Http} = mochiweb_http:start(?HTTP_OPTS), + {ok, Https} = mochiweb_http:start(?HTTPS_OPTS), + SD = #sd{http=Http, https=Https}, + Pid = spawn_link(fun() -> + ets:new(?MODULE, [named_table]), + loop(SD) + end), + register(http_store, Pid), + ok. + +stop() -> + http_store ! stop, + ok. + +dispatch(Req) -> + case Req:get(method) of + 'GET' -> + get_resource(Req); + 'PUT' -> + put_resource(Req); + 'DELETE' -> + delete_resource(Req); + _ -> + Headers = [{"Allow", "GET,PUT,DELETE"}], + Req:respond({405, Headers, "405 Method Not Allowed\r\n"}) + end. + +get_resource(Req) -> + Path = Req:get(path), + case ets:lookup(?MODULE, Path) of + [{Path, #resource{type=Type, data=Data}}] -> + Req:ok({Type, Data}); + [] -> + Req:respond({404, [], "404 Not Found\r\n"}) + end. + +put_resource(Req) -> + ContentType = case Req:get_header_value("Content-Type") of + undefined -> + "application/octet-stream"; + S -> + S + end, + Resource = #resource{type=ContentType, data=Req:recv_body()}, + http_store ! {self(), {put, Req:get(path), Resource}}, + Pid = whereis(http_store), + receive + {Pid, created} -> + Req:respond({201, [], "201 Created\r\n"}); + {Pid, updated} -> + Req:respond({200, [], "200 OK\r\n"}) + end. + +delete_resource(Req) -> + http_store ! {self(), {delete, Req:get(path)}}, + Pid = whereis(http_store), + receive + {Pid, ok} -> + Req:respond({200, [], "200 OK\r\n"}) + end. + +loop(#sd{http=Http, https=Https} = SD) -> + receive + stop -> + ok = mochiweb_http:stop(Http), + ok = mochiweb_http:stop(Https), + exit(normal); + {From, {put, Key, Val}} -> + Exists = ets:member(?MODULE, Key), + ets:insert(?MODULE, {Key, Val}), + case Exists of + true -> + From ! {self(), updated}; + false -> + From ! {self(), created} + end; + {From, {delete, Key}} -> + ets:delete(?MODULE, Key), + From ! {self(), ok}; + _ -> + ignore + end, + ?MODULE:loop(SD). + diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/https/server_cert.pem b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/https/server_cert.pem new file mode 100644 index 0000000..f84ccca --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/https/server_cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDIDCCAgigAwIBAgIJAJLkNZzERPIUMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV +BAMTCWxvY2FsaG9zdDAeFw0xMDAzMTgxOTM5MThaFw0yMDAzMTUxOTM5MThaMBQx +EjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAJeUCOZxbmtngF4S5lXckjSDLc+8C+XjMBYBPyy5eKdJY20AQ1s9/hhp3ulI +8pAvl+xVo4wQ+iBSvOzcy248Q+Xi6+zjceF7UNRgoYPgtJjKhdwcHV3mvFFrS/fp +9ggoAChaJQWDO1OCfUgTWXImhkw+vcDR11OVMAJ/h73dqzJPI9mfq44PTTHfYtgr +v4LAQAOlhXIAa2B+a6PlF6sqDqJaW5jLTcERjsBwnRhUGi7JevQzkejujX/vdA+N +jRBjKH/KLU5h3Q7wUchvIez0PXWVTCnZjpA9aR4m7YV05nKQfxtGd71czYDYk+j8 +hd005jetT4ir7JkAWValBybJVksCAwEAAaN1MHMwHQYDVR0OBBYEFJl9s51SnjJt +V/wgKWqV5Q6jnv1ZMEQGA1UdIwQ9MDuAFJl9s51SnjJtV/wgKWqV5Q6jnv1ZoRik +FjAUMRIwEAYDVQQDEwlsb2NhbGhvc3SCCQCS5DWcxETyFDAMBgNVHRMEBTADAQH/ +MA0GCSqGSIb3DQEBBQUAA4IBAQB2ldLeLCc+lxK5i0EZquLamMBJwDIjGpT0JMP9 +b4XQOK2JABIu54BQIZhwcjk3FDJz/uOW5vm8k1kYni8FCjNZAaRZzCUfiUYTbTKL +Rq9LuIAODyP2dnTqyKaQOOJHvrx9MRZ3XVecXPS0Tib4aO57vCaAbIkmhtYpTWmw +e3t8CAIDVtgvjR6Se0a1JA4LktR7hBu22tDImvCSJn1nVAaHpani6iPBPPdMuMsP +TBoeQfj8VpqBUjCStqJGa8ytjDFX73YaxV2mgrtGwPNme1x3YNRR11yTu7tksyMO +GrmgxNriqYRchBhNEf72AKF0LR1ByKwfbDB9rIsV00HtCgOp +-----END CERTIFICATE----- diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/https/server_key.pem b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/https/server_key.pem new file mode 100644 index 0000000..69bbf82 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/https/server_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAl5QI5nFua2eAXhLmVdySNIMtz7wL5eMwFgE/LLl4p0ljbQBD +Wz3+GGne6UjykC+X7FWjjBD6IFK87NzLbjxD5eLr7ONx4XtQ1GChg+C0mMqF3Bwd +Xea8UWtL9+n2CCgAKFolBYM7U4J9SBNZciaGTD69wNHXU5UwAn+Hvd2rMk8j2Z+r +jg9NMd9i2Cu/gsBAA6WFcgBrYH5ro+UXqyoOolpbmMtNwRGOwHCdGFQaLsl69DOR +6O6Nf+90D42NEGMof8otTmHdDvBRyG8h7PQ9dZVMKdmOkD1pHibthXTmcpB/G0Z3 +vVzNgNiT6PyF3TTmN61PiKvsmQBZVqUHJslWSwIDAQABAoIBACI8Ky5xHDFh9RpK +Rn/KC7OUlTpADKflgizWJ0Cgu2F9L9mkn5HyFHvLHa+u7CootbWJOiEejH/UcBtH +WyMQtX0snYCpdkUpJv5wvMoebGu+AjHOn8tfm9T/2O6rhwgckLyMb6QpGbMo28b1 +p9QiY17BJPZx7qJQJcHKsAvwDwSThlb7MFmWf42LYWlzybpeYQvwpd+UY4I0WXLu +/dqJIS9Npq+5Y5vbo2kAEAssb2hSCvhCfHmwFdKmBzlvgOn4qxgZ1iHQgfKI6Z3Y +J0573ZgOVTuacn+lewtdg5AaHFcl/zIYEr9SNqRoPNGbPliuv6k6N2EYcufWL5lR +sCmmmHECgYEAxm+7OpepGr++K3+O1e1MUhD7vSPkKJrCzNtUxbOi2NWj3FFUSPRU +adWhuxvUnZgTcgM1+KuQ0fB2VmxXe9IDcrSFS7PKFGtd2kMs/5mBw4UgDZkOQh+q +kDiBEV3HYYJWRq0w3NQ/9Iy1jxxdENHtGmG9aqamHxNtuO608wGW2S8CgYEAw4yG +ZyAic0Q/U9V2OHI0MLxLCzuQz17C2wRT1+hBywNZuil5YeTuIt2I46jro6mJmWI2 +fH4S/geSZzg2RNOIZ28+aK79ab2jWBmMnvFCvaru+odAuser4N9pfAlHZvY0pT+S +1zYX3f44ygiio+oosabLC5nWI0zB2gG8pwaJlaUCgYEAgr7poRB+ZlaCCY0RYtjo +mYYBKD02vp5BzdKSB3V1zeLuBWM84pjB6b3Nw0fyDig+X7fH3uHEGN+USRs3hSj6 +BqD01s1OT6fyfbYXNw5A1r+nP+5h26Wbr0zblcKxdQj4qbbBZC8hOJNhqTqqA0Qe +MmzF7jiBaiZV/Cyj4x1f9BcCgYEAhjL6SeuTuOctTqs/5pz5lDikh6DpUGcH8qaV +o6aRAHHcMhYkZzpk8yh1uUdD7516APmVyvn6rrsjjhLVq4ZAJjwB6HWvE9JBN0TR +bILF+sREHUqU8Zn2Ku0nxyfXCKIOnxlx/J/y4TaGYqBqfXNFWiXNUrjQbIlQv/xR +K48g/MECgYBZdQlYbMSDmfPCC5cxkdjrkmAl0EgV051PWAi4wR+hLxIMRjHBvAk7 +IweobkFvT4TICulgroLkYcSa5eOZGxB/DHqcQCbWj3reFV0VpzmTDoFKG54sqBRl +vVntGt0pfA40fF17VoS7riAdHF53ippTtsovHEsg5tq5NrBl5uKm2g== +-----END RSA PRIVATE KEY----- diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/keepalive/keepalive.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/keepalive/keepalive.erl new file mode 100644 index 0000000..965a17e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/examples/keepalive/keepalive.erl @@ -0,0 +1,81 @@ +-module(keepalive). + +%% your web app can push data to clients using a technique called comet long +%% polling. browsers make a request and your server waits to send a +%% response until data is available. see wikipedia for a better explanation: +%% http://en.wikipedia.org/wiki/Comet_(programming)#Ajax_with_long_polling +%% +%% since the majority of your http handlers will be idle at any given moment, +%% you might consider making them hibernate while they wait for more data from +%% another process. however, since the execution stack is discarded when a +%% process hibernates, the handler would usually terminate after your response +%% code runs. this means http keep alives wouldn't work; the handler process +%% would terminate after each response and close its socket rather than +%% returning to the big @mochiweb_http@ loop and processing another request. +%% +%% however, if mochiweb exposes a continuation that encapsulates the return to +%% the top of the big loop in @mochiweb_http@, we can call that after the +%% response. if you do that then control flow returns to the proper place, +%% and keep alives work like they would if you hadn't hibernated. + +-export([ start/1, loop/1 + ]). + +%% internal export (so hibernate can reach it) +-export([ resume/3 + ]). + +-define(LOOP, {?MODULE, loop}). + +start(Options = [{port, _Port}]) -> + mochiweb_http:start([{name, ?MODULE}, {loop, ?LOOP} | Options]). + +loop(Req) -> + Path = Req:get(path), + case string:tokens(Path, "/") of + ["longpoll" | RestOfPath] -> + %% the "reentry" is a continuation -- what @mochiweb_http@ + %% needs to do to start its loop back at the top + Reentry = mochiweb_http:reentry(?LOOP), + + %% here we could send a message to some other process and hope + %% to get an interesting message back after a while. for + %% simplicity let's just send ourselves a message after a few + %% seconds + erlang:send_after(2000, self(), "honk honk"), + + %% since we expect to wait for a long time before getting a + %% reply, let's hibernate. memory usage will be minimized, so + %% we won't be wasting memory just sitting in a @receive@ + proc_lib:hibernate(?MODULE, resume, [Req, RestOfPath, Reentry]), + + %% we'll never reach this point, and this function @loop/1@ + %% won't ever return control to @mochiweb_http@. luckily + %% @resume/3@ will take care of that. + io:format("not gonna happen~n", []); + + _ -> + ok(Req, io_lib:format("some other page: ~p", [Path])) + end, + + io:format("restarting loop normally in ~p~n", [Path]), + ok. + +%% this is the function that's called when a message arrives. +resume(Req, RestOfPath, Reentry) -> + receive + Msg -> + Text = io_lib:format("wake up message: ~p~nrest of path: ~p", [Msg, RestOfPath]), + ok(Req, Text) + end, + + %% if we didn't call @Reentry@ here then the function would finish and the + %% process would exit. calling @Reentry@ takes care of returning control + %% to @mochiweb_http@ + io:format("reentering loop via continuation in ~p~n", [Req:get(path)]), + Reentry(Req). + +ok(Req, Response) -> + Req:ok({_ContentType = "text/plain", + _Headers = [], + Response}). diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/include/internal.hrl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/include/internal.hrl new file mode 100644 index 0000000..6db899a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/include/internal.hrl @@ -0,0 +1,3 @@ + +-define(RECBUF_SIZE, 8192). + diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/rebar b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/rebar new file mode 100755 index 0000000000000000000000000000000000000000..8082f142fd527c7fa291eecad48d465f8d9bff93 GIT binary patch literal 95259 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

+&u(RRy}Gr;)|S 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%0Afc}k1&J_nS0~;LGP>C#H~Iu$_2~v+jjmd< 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 Nf!6_fgY!4&{{uDhM9BaE literal 0 HcmV?d00001 diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/rebar.config b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/rebar.config new file mode 100644 index 0000000..101930a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/rebar.config @@ -0,0 +1,16 @@ +% -*- mode: erlang -*- +{erl_opts, [debug_info, + {platform_define, "R15", 'gen_tcp_r15b_workaround'}]}. +{cover_enabled, true}. +{eunit_opts, [verbose, {report,{eunit_surefire,[{dir,"."}]}}]}. +{dialyzer_opts, [{warnings, [no_return, + no_unused, + no_improper_lists, + no_fun_app, + no_match, + no_opaque, + no_fail_call, + error_handling, + race_conditions, + behaviours, + unmatched_returns]}]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/scripts/entities.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/scripts/entities.erl new file mode 100755 index 0000000..e329ec7 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/scripts/entities.erl @@ -0,0 +1,45 @@ +#!/usr/bin/env escript +%% -*- mode: erlang -*- +-export([main/1]). + +%% @doc Script used to generate mochiweb_charref.erl table. + +main(_) -> + application:start(inets), + code:add_patha("ebin"), + {ok, {_, _, HTML}} = httpc:request("http://www.w3.org/TR/html5/named-character-references.html"), + print(lists:sort(search(mochiweb_html:parse(HTML)))). + +print([F | T]) -> + io:put_chars([clause(F), ";\n"]), + print(T); +print([]) -> + io:put_chars(["entity(_) -> undefined.\n"]), + ok. + +clause({Title, [Codepoint]}) -> + ["entity(\"", Title, "\") -> 16#", Codepoint]; +clause({Title, [First | Rest]}) -> + ["entity(\"", Title, "\") -> [16#", First, + [[", 16#", Codepoint] || Codepoint <- Rest], + "]"]. + + +search(Elem) -> + search(Elem, []). + +search({<<"tr">>, [{<<"id">>, <<"entity-", _/binary>>} | _], Children}, Acc) -> + %% HTML5 charrefs can have more than one code point(!) + [{<<"td">>, _, [{<<"code">>, _, [TitleSemi]}]}, + {<<"td">>, [], [RawCPs]} | _] = Children, + L = byte_size(TitleSemi) - 1, + <> = TitleSemi, + {match, Matches} = re:run(RawCPs, "(?:\\s*U\\+)([a-fA-F0-9]+)", + [{capture, all, binary}, global]), + [{Title, [CP || [_, CP] <- Matches]} | Acc]; +search({Tag, Attrs, [H | T]}, Acc) -> + search({Tag, Attrs, T}, search(H, Acc)); +search({_Tag, _Attrs, []}, Acc) -> + Acc; +search(<<_/binary>>, Acc) -> + Acc. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/scripts/new_mochiweb.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/scripts/new_mochiweb.erl new file mode 100755 index 0000000..f49ed39 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/scripts/new_mochiweb.erl @@ -0,0 +1,23 @@ +#!/usr/bin/env escript +%% -*- mode: erlang -*- +-export([main/1]). + +%% External API + +main(_) -> + usage(). + +%% Internal API + +usage() -> + io:format( + "new_mochiweb.erl has been replaced by a rebar template!\n" + "\n" + "To create a new mochiweb using project:\n" + " make app PROJECT=project_name\n" + "\n" + "To create a new mochiweb using project in a specific directory:\n" + " make app PROJECT=project_name PREFIX=$HOME/projects/\n" + "\n" + ), + halt(1). diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochifmt.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochifmt.erl new file mode 100644 index 0000000..fc95e4f --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochifmt.erl @@ -0,0 +1,425 @@ +%% @author Bob Ippolito +%% @copyright 2008 Mochi Media, Inc. + +%% @doc String Formatting for Erlang, inspired by Python 2.6 +%% (PEP 3101). +%% +-module(mochifmt). +-author('bob@mochimedia.com'). +-export([format/2, format_field/2, convert_field/2, get_value/2, get_field/2]). +-export([tokenize/1, format/3, get_field/3, format_field/3]). +-export([bformat/2, bformat/3]). +-export([f/2, f/3]). + +-record(conversion, {length, precision, ctype, align, fill_char, sign}). + +%% @spec tokenize(S::string()) -> tokens() +%% @doc Tokenize a format string into mochifmt's internal format. +tokenize(S) -> + {?MODULE, tokenize(S, "", [])}. + +%% @spec convert_field(Arg, Conversion::conversion()) -> term() +%% @doc Process Arg according to the given explicit conversion specifier. +convert_field(Arg, "") -> + Arg; +convert_field(Arg, "r") -> + repr(Arg); +convert_field(Arg, "s") -> + str(Arg). + +%% @spec get_value(Key::string(), Args::args()) -> term() +%% @doc Get the Key from Args. If Args is a tuple then convert Key to +%% an integer and get element(1 + Key, Args). If Args is a list and Key +%% can be parsed as an integer then use lists:nth(1 + Key, Args), +%% otherwise try and look for Key in Args as a proplist, converting +%% Key to an atom or binary if necessary. +get_value(Key, Args) when is_tuple(Args) -> + element(1 + list_to_integer(Key), Args); +get_value(Key, Args) when is_list(Args) -> + try lists:nth(1 + list_to_integer(Key), Args) + catch error:_ -> + {_K, V} = proplist_lookup(Key, Args), + V + end. + +%% @spec get_field(Key::string(), Args) -> term() +%% @doc Consecutively call get_value/2 on parts of Key delimited by ".", +%% replacing Args with the result of the previous get_value. This +%% is used to implement formats such as {0.0}. +get_field(Key, Args) -> + get_field(Key, Args, ?MODULE). + +%% @spec get_field(Key::string(), Args, Module) -> term() +%% @doc Consecutively call Module:get_value/2 on parts of Key delimited by ".", +%% replacing Args with the result of the previous get_value. This +%% is used to implement formats such as {0.0}. +get_field(Key, Args, Module) -> + {Name, Next} = lists:splitwith(fun (C) -> C =/= $. end, Key), + Res = try Module:get_value(Name, Args) + catch error:undef -> get_value(Name, Args) end, + case Next of + "" -> + Res; + "." ++ S1 -> + get_field(S1, Res, Module) + end. + +%% @spec format(Format::string(), Args) -> iolist() +%% @doc Format Args with Format. +format(Format, Args) -> + format(Format, Args, ?MODULE). + +%% @spec format(Format::string(), Args, Module) -> iolist() +%% @doc Format Args with Format using Module. +format({?MODULE, Parts}, Args, Module) -> + format2(Parts, Args, Module, []); +format(S, Args, Module) -> + format(tokenize(S), Args, Module). + +%% @spec format_field(Arg, Format) -> iolist() +%% @doc Format Arg with Format. +format_field(Arg, Format) -> + format_field(Arg, Format, ?MODULE). + +%% @spec format_field(Arg, Format, _Module) -> iolist() +%% @doc Format Arg with Format. +format_field(Arg, Format, _Module) -> + F = default_ctype(Arg, parse_std_conversion(Format)), + fix_padding(fix_sign(convert2(Arg, F), F), F). + +%% @spec f(Format::string(), Args) -> string() +%% @doc Format Args with Format and return a string(). +f(Format, Args) -> + f(Format, Args, ?MODULE). + +%% @spec f(Format::string(), Args, Module) -> string() +%% @doc Format Args with Format using Module and return a string(). +f(Format, Args, Module) -> + case lists:member(${, Format) of + true -> + binary_to_list(bformat(Format, Args, Module)); + false -> + Format + end. + +%% @spec bformat(Format::string(), Args) -> binary() +%% @doc Format Args with Format and return a binary(). +bformat(Format, Args) -> + iolist_to_binary(format(Format, Args)). + +%% @spec bformat(Format::string(), Args, Module) -> binary() +%% @doc Format Args with Format using Module and return a binary(). +bformat(Format, Args, Module) -> + iolist_to_binary(format(Format, Args, Module)). + +%% Internal API + +add_raw("", Acc) -> + Acc; +add_raw(S, Acc) -> + [{raw, lists:reverse(S)} | Acc]. + +tokenize([], S, Acc) -> + lists:reverse(add_raw(S, Acc)); +tokenize("{{" ++ Rest, S, Acc) -> + tokenize(Rest, "{" ++ S, Acc); +tokenize("{" ++ Rest, S, Acc) -> + {Format, Rest1} = tokenize_format(Rest), + tokenize(Rest1, "", [{format, make_format(Format)} | add_raw(S, Acc)]); +tokenize("}}" ++ Rest, S, Acc) -> + tokenize(Rest, "}" ++ S, Acc); +tokenize([C | Rest], S, Acc) -> + tokenize(Rest, [C | S], Acc). + +tokenize_format(S) -> + tokenize_format(S, 1, []). + +tokenize_format("}" ++ Rest, 1, Acc) -> + {lists:reverse(Acc), Rest}; +tokenize_format("}" ++ Rest, N, Acc) -> + tokenize_format(Rest, N - 1, "}" ++ Acc); +tokenize_format("{" ++ Rest, N, Acc) -> + tokenize_format(Rest, 1 + N, "{" ++ Acc); +tokenize_format([C | Rest], N, Acc) -> + tokenize_format(Rest, N, [C | Acc]). + +make_format(S) -> + {Name0, Spec} = case lists:splitwith(fun (C) -> C =/= $: end, S) of + {_, ""} -> + {S, ""}; + {SN, ":" ++ SS} -> + {SN, SS} + end, + {Name, Transform} = case lists:splitwith(fun (C) -> C =/= $! end, Name0) of + {_, ""} -> + {Name0, ""}; + {TN, "!" ++ TT} -> + {TN, TT} + end, + {Name, Transform, Spec}. + +proplist_lookup(S, P) -> + A = try list_to_existing_atom(S) + catch error:_ -> make_ref() end, + B = try list_to_binary(S) + catch error:_ -> make_ref() end, + proplist_lookup2({S, A, B}, P). + +proplist_lookup2({KS, KA, KB}, [{K, V} | _]) + when KS =:= K orelse KA =:= K orelse KB =:= K -> + {K, V}; +proplist_lookup2(Keys, [_ | Rest]) -> + proplist_lookup2(Keys, Rest). + +format2([], _Args, _Module, Acc) -> + lists:reverse(Acc); +format2([{raw, S} | Rest], Args, Module, Acc) -> + format2(Rest, Args, Module, [S | Acc]); +format2([{format, {Key, Convert, Format0}} | Rest], Args, Module, Acc) -> + Format = f(Format0, Args, Module), + V = case Module of + ?MODULE -> + V0 = get_field(Key, Args), + V1 = convert_field(V0, Convert), + format_field(V1, Format); + _ -> + V0 = try Module:get_field(Key, Args) + catch error:undef -> get_field(Key, Args, Module) end, + V1 = try Module:convert_field(V0, Convert) + catch error:undef -> convert_field(V0, Convert) end, + try Module:format_field(V1, Format) + catch error:undef -> format_field(V1, Format, Module) end + end, + format2(Rest, Args, Module, [V | Acc]). + +default_ctype(_Arg, C=#conversion{ctype=N}) when N =/= undefined -> + C; +default_ctype(Arg, C) when is_integer(Arg) -> + C#conversion{ctype=decimal}; +default_ctype(Arg, C) when is_float(Arg) -> + C#conversion{ctype=general}; +default_ctype(_Arg, C) -> + C#conversion{ctype=string}. + +fix_padding(Arg, #conversion{length=undefined}) -> + Arg; +fix_padding(Arg, F=#conversion{length=Length, fill_char=Fill0, align=Align0, + ctype=Type}) -> + Padding = Length - iolist_size(Arg), + Fill = case Fill0 of + undefined -> + $\s; + _ -> + Fill0 + end, + Align = case Align0 of + undefined -> + case Type of + string -> + left; + _ -> + right + end; + _ -> + Align0 + end, + case Padding > 0 of + true -> + do_padding(Arg, Padding, Fill, Align, F); + false -> + Arg + end. + +do_padding(Arg, Padding, Fill, right, _F) -> + [lists:duplicate(Padding, Fill), Arg]; +do_padding(Arg, Padding, Fill, center, _F) -> + LPadding = lists:duplicate(Padding div 2, Fill), + RPadding = case Padding band 1 of + 1 -> + [Fill | LPadding]; + _ -> + LPadding + end, + [LPadding, Arg, RPadding]; +do_padding([$- | Arg], Padding, Fill, sign_right, _F) -> + [[$- | lists:duplicate(Padding, Fill)], Arg]; +do_padding(Arg, Padding, Fill, sign_right, #conversion{sign=$-}) -> + [lists:duplicate(Padding, Fill), Arg]; +do_padding([S | Arg], Padding, Fill, sign_right, #conversion{sign=S}) -> + [[S | lists:duplicate(Padding, Fill)], Arg]; +do_padding(Arg, Padding, Fill, sign_right, #conversion{sign=undefined}) -> + [lists:duplicate(Padding, Fill), Arg]; +do_padding(Arg, Padding, Fill, left, _F) -> + [Arg | lists:duplicate(Padding, Fill)]. + +fix_sign(Arg, #conversion{sign=$+}) when Arg >= 0 -> + [$+, Arg]; +fix_sign(Arg, #conversion{sign=$\s}) when Arg >= 0 -> + [$\s, Arg]; +fix_sign(Arg, _F) -> + Arg. + +ctype($\%) -> percent; +ctype($s) -> string; +ctype($b) -> bin; +ctype($o) -> oct; +ctype($X) -> upper_hex; +ctype($x) -> hex; +ctype($c) -> char; +ctype($d) -> decimal; +ctype($g) -> general; +ctype($f) -> fixed; +ctype($e) -> exp. + +align($<) -> left; +align($>) -> right; +align($^) -> center; +align($=) -> sign_right. + +convert2(Arg, F=#conversion{ctype=percent}) -> + [convert2(100.0 * Arg, F#conversion{ctype=fixed}), $\%]; +convert2(Arg, #conversion{ctype=string}) -> + str(Arg); +convert2(Arg, #conversion{ctype=bin}) -> + erlang:integer_to_list(Arg, 2); +convert2(Arg, #conversion{ctype=oct}) -> + erlang:integer_to_list(Arg, 8); +convert2(Arg, #conversion{ctype=upper_hex}) -> + erlang:integer_to_list(Arg, 16); +convert2(Arg, #conversion{ctype=hex}) -> + string:to_lower(erlang:integer_to_list(Arg, 16)); +convert2(Arg, #conversion{ctype=char}) when Arg < 16#80 -> + [Arg]; +convert2(Arg, #conversion{ctype=char}) -> + xmerl_ucs:to_utf8(Arg); +convert2(Arg, #conversion{ctype=decimal}) -> + integer_to_list(Arg); +convert2(Arg, #conversion{ctype=general, precision=undefined}) -> + try mochinum:digits(Arg) + catch error:undef -> io_lib:format("~g", [Arg]) end; +convert2(Arg, #conversion{ctype=fixed, precision=undefined}) -> + io_lib:format("~f", [Arg]); +convert2(Arg, #conversion{ctype=exp, precision=undefined}) -> + io_lib:format("~e", [Arg]); +convert2(Arg, #conversion{ctype=general, precision=P}) -> + io_lib:format("~." ++ integer_to_list(P) ++ "g", [Arg]); +convert2(Arg, #conversion{ctype=fixed, precision=P}) -> + io_lib:format("~." ++ integer_to_list(P) ++ "f", [Arg]); +convert2(Arg, #conversion{ctype=exp, precision=P}) -> + io_lib:format("~." ++ integer_to_list(P) ++ "e", [Arg]). + +str(A) when is_atom(A) -> + atom_to_list(A); +str(I) when is_integer(I) -> + integer_to_list(I); +str(F) when is_float(F) -> + try mochinum:digits(F) + catch error:undef -> io_lib:format("~g", [F]) end; +str(L) when is_list(L) -> + L; +str(B) when is_binary(B) -> + B; +str(P) -> + repr(P). + +repr(P) when is_float(P) -> + try mochinum:digits(P) + catch error:undef -> float_to_list(P) end; +repr(P) -> + io_lib:format("~p", [P]). + +parse_std_conversion(S) -> + parse_std_conversion(S, #conversion{}). + +parse_std_conversion("", Acc) -> + Acc; +parse_std_conversion([Fill, Align | Spec], Acc) + when Align =:= $< orelse Align =:= $> orelse Align =:= $= orelse Align =:= $^ -> + parse_std_conversion(Spec, Acc#conversion{fill_char=Fill, + align=align(Align)}); +parse_std_conversion([Align | Spec], Acc) + when Align =:= $< orelse Align =:= $> orelse Align =:= $= orelse Align =:= $^ -> + parse_std_conversion(Spec, Acc#conversion{align=align(Align)}); +parse_std_conversion([Sign | Spec], Acc) + when Sign =:= $+ orelse Sign =:= $- orelse Sign =:= $\s -> + parse_std_conversion(Spec, Acc#conversion{sign=Sign}); +parse_std_conversion("0" ++ Spec, Acc) -> + Align = case Acc#conversion.align of + undefined -> + sign_right; + A -> + A + end, + parse_std_conversion(Spec, Acc#conversion{fill_char=$0, align=Align}); +parse_std_conversion(Spec=[D|_], Acc) when D >= $0 andalso D =< $9 -> + {W, Spec1} = lists:splitwith(fun (C) -> C >= $0 andalso C =< $9 end, Spec), + parse_std_conversion(Spec1, Acc#conversion{length=list_to_integer(W)}); +parse_std_conversion([$. | Spec], Acc) -> + case lists:splitwith(fun (C) -> C >= $0 andalso C =< $9 end, Spec) of + {"", Spec1} -> + parse_std_conversion(Spec1, Acc); + {P, Spec1} -> + parse_std_conversion(Spec1, + Acc#conversion{precision=list_to_integer(P)}) + end; +parse_std_conversion([Type], Acc) -> + parse_std_conversion("", Acc#conversion{ctype=ctype(Type)}). + + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +tokenize_test() -> + {?MODULE, [{raw, "ABC"}]} = tokenize("ABC"), + {?MODULE, [{format, {"0", "", ""}}]} = tokenize("{0}"), + {?MODULE, [{raw, "ABC"}, {format, {"1", "", ""}}, {raw, "DEF"}]} = + tokenize("ABC{1}DEF"), + ok. + +format_test() -> + <<" -4">> = bformat("{0:4}", [-4]), + <<" 4">> = bformat("{0:4}", [4]), + <<" 4">> = bformat("{0:{0}}", [4]), + <<"4 ">> = bformat("{0:4}", ["4"]), + <<"4 ">> = bformat("{0:{0}}", ["4"]), + <<"1.2yoDEF">> = bformat("{2}{0}{1}{3}", {yo, "DE", 1.2, <<"F">>}), + <<"cafebabe">> = bformat("{0:x}", {16#cafebabe}), + <<"CAFEBABE">> = bformat("{0:X}", {16#cafebabe}), + <<"CAFEBABE">> = bformat("{0:X}", {16#cafebabe}), + <<"755">> = bformat("{0:o}", {8#755}), + <<"a">> = bformat("{0:c}", {97}), + %% Horizontal ellipsis + <<226, 128, 166>> = bformat("{0:c}", {16#2026}), + <<"11">> = bformat("{0:b}", {3}), + <<"11">> = bformat("{0:b}", [3]), + <<"11">> = bformat("{three:b}", [{three, 3}]), + <<"11">> = bformat("{three:b}", [{"three", 3}]), + <<"11">> = bformat("{three:b}", [{<<"three">>, 3}]), + <<"\"foo\"">> = bformat("{0!r}", {"foo"}), + <<"2008-5-4">> = bformat("{0.0}-{0.1}-{0.2}", {{2008,5,4}}), + <<"2008-05-04">> = bformat("{0.0:04}-{0.1:02}-{0.2:02}", {{2008,5,4}}), + <<"foo6bar-6">> = bformat("foo{1}{0}-{1}", {bar, 6}), + <<"-'atom test'-">> = bformat("-{arg!r}-", [{arg, 'atom test'}]), + <<"2008-05-04">> = bformat("{0.0:0{1.0}}-{0.1:0{1.1}}-{0.2:0{1.2}}", + {{2008,5,4}, {4, 2, 2}}), + ok. + +std_test() -> + M = mochifmt_std:new(), + <<"01">> = bformat("{0}{1}", [0, 1], M), + ok. + +records_test() -> + M = mochifmt_records:new([{conversion, record_info(fields, conversion)}]), + R = #conversion{length=long, precision=hard, sign=peace}, + long = M:get_value("length", R), + hard = M:get_value("precision", R), + peace = M:get_value("sign", R), + <<"long hard">> = bformat("{length} {precision}", R, M), + <<"long hard">> = bformat("{0.length} {0.precision}", [R], M), + ok. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochifmt_records.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochifmt_records.erl new file mode 100644 index 0000000..7d166ff --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochifmt_records.erl @@ -0,0 +1,42 @@ +%% @author Bob Ippolito +%% @copyright 2008 Mochi Media, Inc. + +%% @doc Formatter that understands records. +%% +%% Usage: +%% +%% 1> M = mochifmt_records:new([{rec, record_info(fields, rec)}]), +%% M:format("{0.bar}", [#rec{bar=foo}]). +%% foo + +-module(mochifmt_records). +-author('bob@mochimedia.com'). +-export([new/1, get_value/3]). + +new([{_Rec, RecFields}]=Recs) when is_list(RecFields) -> + {?MODULE, Recs}. + +get_value(Key, Rec, {?MODULE, Recs}) + when is_tuple(Rec) and is_atom(element(1, Rec)) -> + try begin + Atom = list_to_existing_atom(Key), + {_, Fields} = proplists:lookup(element(1, Rec), Recs), + element(get_rec_index(Atom, Fields, 2), Rec) + end + catch error:_ -> mochifmt:get_value(Key, Rec) + end; +get_value(Key, Args, {?MODULE, _Recs}) -> + mochifmt:get_value(Key, Args). + +get_rec_index(Atom, [Atom | _], Index) -> + Index; +get_rec_index(Atom, [_ | Rest], Index) -> + get_rec_index(Atom, Rest, 1 + Index). + + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochifmt_std.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochifmt_std.erl new file mode 100644 index 0000000..ea68c4a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochifmt_std.erl @@ -0,0 +1,33 @@ +%% @author Bob Ippolito +%% @copyright 2008 Mochi Media, Inc. + +%% @doc Template module for a mochifmt formatter. + +-module(mochifmt_std). +-author('bob@mochimedia.com'). +-export([new/0, format/3, get_value/3, format_field/3, get_field/3, convert_field/3]). + +new() -> + {?MODULE}. + +format(Format, Args, {?MODULE}=THIS) -> + mochifmt:format(Format, Args, THIS). + +get_field(Key, Args, {?MODULE}=THIS) -> + mochifmt:get_field(Key, Args, THIS). + +convert_field(Key, Args, {?MODULE}) -> + mochifmt:convert_field(Key, Args). + +get_value(Key, Args, {?MODULE}) -> + mochifmt:get_value(Key, Args). + +format_field(Arg, Format, {?MODULE}=THIS) -> + mochifmt:format_field(Arg, Format, THIS). + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochihex.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochihex.erl new file mode 100644 index 0000000..796f3ad --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochihex.erl @@ -0,0 +1,88 @@ +%% @author Bob Ippolito +%% @copyright 2006 Mochi Media, Inc. + +%% @doc Utilities for working with hexadecimal strings. + +-module(mochihex). +-author('bob@mochimedia.com'). + +-export([to_hex/1, to_bin/1, to_int/1, dehex/1, hexdigit/1]). + +%% @spec to_hex(integer | iolist()) -> string() +%% @doc Convert an iolist to a hexadecimal string. +to_hex(0) -> + "0"; +to_hex(I) when is_integer(I), I > 0 -> + to_hex_int(I, []); +to_hex(B) -> + to_hex(iolist_to_binary(B), []). + +%% @spec to_bin(string()) -> binary() +%% @doc Convert a hexadecimal string to a binary. +to_bin(L) -> + to_bin(L, []). + +%% @spec to_int(string()) -> integer() +%% @doc Convert a hexadecimal string to an integer. +to_int(L) -> + erlang:list_to_integer(L, 16). + +%% @spec dehex(char()) -> integer() +%% @doc Convert a hex digit to its integer value. +dehex(C) when C >= $0, C =< $9 -> + C - $0; +dehex(C) when C >= $a, C =< $f -> + C - $a + 10; +dehex(C) when C >= $A, C =< $F -> + C - $A + 10. + +%% @spec hexdigit(integer()) -> char() +%% @doc Convert an integer less than 16 to a hex digit. +hexdigit(C) when C >= 0, C =< 9 -> + C + $0; +hexdigit(C) when C =< 15 -> + C + $a - 10. + +%% Internal API + +to_hex(<<>>, Acc) -> + lists:reverse(Acc); +to_hex(<>, Acc) -> + to_hex(Rest, [hexdigit(C2), hexdigit(C1) | Acc]). + +to_hex_int(0, Acc) -> + Acc; +to_hex_int(I, Acc) -> + to_hex_int(I bsr 4, [hexdigit(I band 15) | Acc]). + +to_bin([], Acc) -> + iolist_to_binary(lists:reverse(Acc)); +to_bin([C1, C2 | Rest], Acc) -> + to_bin(Rest, [(dehex(C1) bsl 4) bor dehex(C2) | Acc]). + + + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +to_hex_test() -> + "ff000ff1" = to_hex([255, 0, 15, 241]), + "ff000ff1" = to_hex(16#ff000ff1), + "0" = to_hex(16#0), + ok. + +to_bin_test() -> + <<255, 0, 15, 241>> = to_bin("ff000ff1"), + <<255, 0, 10, 161>> = to_bin("Ff000aA1"), + ok. + +to_int_test() -> + 16#ff000ff1 = to_int("ff000ff1"), + 16#ff000aa1 = to_int("FF000Aa1"), + 16#0 = to_int("0"), + ok. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochijson.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochijson.erl new file mode 100644 index 0000000..d283189 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochijson.erl @@ -0,0 +1,529 @@ +%% @author Bob Ippolito +%% @copyright 2006 Mochi Media, Inc. + +%% @doc Yet another JSON (RFC 4627) library for Erlang. +-module(mochijson). +-author('bob@mochimedia.com'). +-export([encoder/1, encode/1]). +-export([decoder/1, decode/1]). +-export([binary_encoder/1, binary_encode/1]). +-export([binary_decoder/1, binary_decode/1]). + +% This is a macro to placate syntax highlighters.. +-define(Q, $\"). +-define(ADV_COL(S, N), S#decoder{column=N+S#decoder.column}). +-define(INC_COL(S), S#decoder{column=1+S#decoder.column}). +-define(INC_LINE(S), S#decoder{column=1, line=1+S#decoder.line}). + +%% @type json_string() = atom | string() | binary() +%% @type json_number() = integer() | float() +%% @type json_array() = {array, [json_term()]} +%% @type json_object() = {struct, [{json_string(), json_term()}]} +%% @type json_term() = json_string() | json_number() | json_array() | +%% json_object() +%% @type encoding() = utf8 | unicode +%% @type encoder_option() = {input_encoding, encoding()} | +%% {handler, function()} +%% @type decoder_option() = {input_encoding, encoding()} | +%% {object_hook, function()} +%% @type bjson_string() = binary() +%% @type bjson_number() = integer() | float() +%% @type bjson_array() = [bjson_term()] +%% @type bjson_object() = {struct, [{bjson_string(), bjson_term()}]} +%% @type bjson_term() = bjson_string() | bjson_number() | bjson_array() | +%% bjson_object() +%% @type binary_encoder_option() = {handler, function()} +%% @type binary_decoder_option() = {object_hook, function()} + +-record(encoder, {input_encoding=unicode, + handler=null}). + +-record(decoder, {input_encoding=utf8, + object_hook=null, + line=1, + column=1, + state=null}). + +%% @spec encoder([encoder_option()]) -> function() +%% @doc Create an encoder/1 with the given options. +encoder(Options) -> + State = parse_encoder_options(Options, #encoder{}), + fun (O) -> json_encode(O, State) end. + +%% @spec encode(json_term()) -> iolist() +%% @doc Encode the given as JSON to an iolist. +encode(Any) -> + json_encode(Any, #encoder{}). + +%% @spec decoder([decoder_option()]) -> function() +%% @doc Create a decoder/1 with the given options. +decoder(Options) -> + State = parse_decoder_options(Options, #decoder{}), + fun (O) -> json_decode(O, State) end. + +%% @spec decode(iolist()) -> json_term() +%% @doc Decode the given iolist to Erlang terms. +decode(S) -> + json_decode(S, #decoder{}). + +%% @spec binary_decoder([binary_decoder_option()]) -> function() +%% @doc Create a binary_decoder/1 with the given options. +binary_decoder(Options) -> + mochijson2:decoder(Options). + +%% @spec binary_encoder([binary_encoder_option()]) -> function() +%% @doc Create a binary_encoder/1 with the given options. +binary_encoder(Options) -> + mochijson2:encoder(Options). + +%% @spec binary_encode(bjson_term()) -> iolist() +%% @doc Encode the given as JSON to an iolist, using lists for arrays and +%% binaries for strings. +binary_encode(Any) -> + mochijson2:encode(Any). + +%% @spec binary_decode(iolist()) -> bjson_term() +%% @doc Decode the given iolist to Erlang terms, using lists for arrays and +%% binaries for strings. +binary_decode(S) -> + mochijson2:decode(S). + +%% Internal API + +parse_encoder_options([], State) -> + State; +parse_encoder_options([{input_encoding, Encoding} | Rest], State) -> + parse_encoder_options(Rest, State#encoder{input_encoding=Encoding}); +parse_encoder_options([{handler, Handler} | Rest], State) -> + parse_encoder_options(Rest, State#encoder{handler=Handler}). + +parse_decoder_options([], State) -> + State; +parse_decoder_options([{input_encoding, Encoding} | Rest], State) -> + parse_decoder_options(Rest, State#decoder{input_encoding=Encoding}); +parse_decoder_options([{object_hook, Hook} | Rest], State) -> + parse_decoder_options(Rest, State#decoder{object_hook=Hook}). + +json_encode(true, _State) -> + "true"; +json_encode(false, _State) -> + "false"; +json_encode(null, _State) -> + "null"; +json_encode(I, _State) when is_integer(I) -> + integer_to_list(I); +json_encode(F, _State) when is_float(F) -> + mochinum:digits(F); +json_encode(L, State) when is_list(L); is_binary(L); is_atom(L) -> + json_encode_string(L, State); +json_encode({array, Props}, State) when is_list(Props) -> + json_encode_array(Props, State); +json_encode({struct, Props}, State) when is_list(Props) -> + json_encode_proplist(Props, State); +json_encode(Bad, #encoder{handler=null}) -> + exit({json_encode, {bad_term, Bad}}); +json_encode(Bad, State=#encoder{handler=Handler}) -> + json_encode(Handler(Bad), State). + +json_encode_array([], _State) -> + "[]"; +json_encode_array(L, State) -> + F = fun (O, Acc) -> + [$,, json_encode(O, State) | Acc] + end, + [$, | Acc1] = lists:foldl(F, "[", L), + lists:reverse([$\] | Acc1]). + +json_encode_proplist([], _State) -> + "{}"; +json_encode_proplist(Props, State) -> + F = fun ({K, V}, Acc) -> + KS = case K of + K when is_atom(K) -> + json_encode_string_utf8(atom_to_list(K)); + K when is_integer(K) -> + json_encode_string(integer_to_list(K), State); + K when is_list(K); is_binary(K) -> + json_encode_string(K, State) + end, + VS = json_encode(V, State), + [$,, VS, $:, KS | Acc] + end, + [$, | Acc1] = lists:foldl(F, "{", Props), + lists:reverse([$\} | Acc1]). + +json_encode_string(A, _State) when is_atom(A) -> + json_encode_string_unicode(xmerl_ucs:from_utf8(atom_to_list(A))); +json_encode_string(B, _State) when is_binary(B) -> + json_encode_string_unicode(xmerl_ucs:from_utf8(B)); +json_encode_string(S, #encoder{input_encoding=utf8}) -> + json_encode_string_utf8(S); +json_encode_string(S, #encoder{input_encoding=unicode}) -> + json_encode_string_unicode(S). + +json_encode_string_utf8(S) -> + [?Q | json_encode_string_utf8_1(S)]. + +json_encode_string_utf8_1([C | Cs]) when C >= 0, C =< 16#7f -> + NewC = case C of + $\\ -> "\\\\"; + ?Q -> "\\\""; + _ when C >= $\s, C < 16#7f -> C; + $\t -> "\\t"; + $\n -> "\\n"; + $\r -> "\\r"; + $\f -> "\\f"; + $\b -> "\\b"; + _ when C >= 0, C =< 16#7f -> unihex(C); + _ -> exit({json_encode, {bad_char, C}}) + end, + [NewC | json_encode_string_utf8_1(Cs)]; +json_encode_string_utf8_1(All=[C | _]) when C >= 16#80, C =< 16#10FFFF -> + [?Q | Rest] = json_encode_string_unicode(xmerl_ucs:from_utf8(All)), + Rest; +json_encode_string_utf8_1([]) -> + "\"". + +json_encode_string_unicode(S) -> + [?Q | json_encode_string_unicode_1(S)]. + +json_encode_string_unicode_1([C | Cs]) -> + NewC = case C of + $\\ -> "\\\\"; + ?Q -> "\\\""; + _ when C >= $\s, C < 16#7f -> C; + $\t -> "\\t"; + $\n -> "\\n"; + $\r -> "\\r"; + $\f -> "\\f"; + $\b -> "\\b"; + _ when C >= 0, C =< 16#10FFFF -> unihex(C); + _ -> exit({json_encode, {bad_char, C}}) + end, + [NewC | json_encode_string_unicode_1(Cs)]; +json_encode_string_unicode_1([]) -> + "\"". + +dehex(C) when C >= $0, C =< $9 -> + C - $0; +dehex(C) when C >= $a, C =< $f -> + C - $a + 10; +dehex(C) when C >= $A, C =< $F -> + C - $A + 10. + +hexdigit(C) when C >= 0, C =< 9 -> + C + $0; +hexdigit(C) when C =< 15 -> + C + $a - 10. + +unihex(C) when C < 16#10000 -> + <> = <>, + Digits = [hexdigit(D) || D <- [D3, D2, D1, D0]], + [$\\, $u | Digits]; +unihex(C) when C =< 16#10FFFF -> + N = C - 16#10000, + S1 = 16#d800 bor ((N bsr 10) band 16#3ff), + S2 = 16#dc00 bor (N band 16#3ff), + [unihex(S1), unihex(S2)]. + +json_decode(B, S) when is_binary(B) -> + json_decode(binary_to_list(B), S); +json_decode(L, S) -> + {Res, L1, S1} = decode1(L, S), + {eof, [], _} = tokenize(L1, S1#decoder{state=trim}), + Res. + +decode1(L, S=#decoder{state=null}) -> + case tokenize(L, S#decoder{state=any}) of + {{const, C}, L1, S1} -> + {C, L1, S1}; + {start_array, L1, S1} -> + decode_array(L1, S1#decoder{state=any}, []); + {start_object, L1, S1} -> + decode_object(L1, S1#decoder{state=key}, []) + end. + +make_object(V, #decoder{object_hook=null}) -> + V; +make_object(V, #decoder{object_hook=Hook}) -> + Hook(V). + +decode_object(L, S=#decoder{state=key}, Acc) -> + case tokenize(L, S) of + {end_object, Rest, S1} -> + V = make_object({struct, lists:reverse(Acc)}, S1), + {V, Rest, S1#decoder{state=null}}; + {{const, K}, Rest, S1} when is_list(K) -> + {colon, L2, S2} = tokenize(Rest, S1), + {V, L3, S3} = decode1(L2, S2#decoder{state=null}), + decode_object(L3, S3#decoder{state=comma}, [{K, V} | Acc]) + end; +decode_object(L, S=#decoder{state=comma}, Acc) -> + case tokenize(L, S) of + {end_object, Rest, S1} -> + V = make_object({struct, lists:reverse(Acc)}, S1), + {V, Rest, S1#decoder{state=null}}; + {comma, Rest, S1} -> + decode_object(Rest, S1#decoder{state=key}, Acc) + end. + +decode_array(L, S=#decoder{state=any}, Acc) -> + case tokenize(L, S) of + {end_array, Rest, S1} -> + {{array, lists:reverse(Acc)}, Rest, S1#decoder{state=null}}; + {start_array, Rest, S1} -> + {Array, Rest1, S2} = decode_array(Rest, S1#decoder{state=any}, []), + decode_array(Rest1, S2#decoder{state=comma}, [Array | Acc]); + {start_object, Rest, S1} -> + {Array, Rest1, S2} = decode_object(Rest, S1#decoder{state=key}, []), + decode_array(Rest1, S2#decoder{state=comma}, [Array | Acc]); + {{const, Const}, Rest, S1} -> + decode_array(Rest, S1#decoder{state=comma}, [Const | Acc]) + end; +decode_array(L, S=#decoder{state=comma}, Acc) -> + case tokenize(L, S) of + {end_array, Rest, S1} -> + {{array, lists:reverse(Acc)}, Rest, S1#decoder{state=null}}; + {comma, Rest, S1} -> + decode_array(Rest, S1#decoder{state=any}, Acc) + end. + +tokenize_string(IoList=[C | _], S=#decoder{input_encoding=utf8}, Acc) + when is_list(C); is_binary(C); C >= 16#7f -> + List = xmerl_ucs:from_utf8(iolist_to_binary(IoList)), + tokenize_string(List, S#decoder{input_encoding=unicode}, Acc); +tokenize_string("\"" ++ Rest, S, Acc) -> + {lists:reverse(Acc), Rest, ?INC_COL(S)}; +tokenize_string("\\\"" ++ Rest, S, Acc) -> + tokenize_string(Rest, ?ADV_COL(S, 2), [$\" | Acc]); +tokenize_string("\\\\" ++ Rest, S, Acc) -> + tokenize_string(Rest, ?ADV_COL(S, 2), [$\\ | Acc]); +tokenize_string("\\/" ++ Rest, S, Acc) -> + tokenize_string(Rest, ?ADV_COL(S, 2), [$/ | Acc]); +tokenize_string("\\b" ++ Rest, S, Acc) -> + tokenize_string(Rest, ?ADV_COL(S, 2), [$\b | Acc]); +tokenize_string("\\f" ++ Rest, S, Acc) -> + tokenize_string(Rest, ?ADV_COL(S, 2), [$\f | Acc]); +tokenize_string("\\n" ++ Rest, S, Acc) -> + tokenize_string(Rest, ?ADV_COL(S, 2), [$\n | Acc]); +tokenize_string("\\r" ++ Rest, S, Acc) -> + tokenize_string(Rest, ?ADV_COL(S, 2), [$\r | Acc]); +tokenize_string("\\t" ++ Rest, S, Acc) -> + tokenize_string(Rest, ?ADV_COL(S, 2), [$\t | Acc]); +tokenize_string([$\\, $u, C3, C2, C1, C0 | Rest], S, Acc) -> + % coalesce UTF-16 surrogate pair? + C = dehex(C0) bor + (dehex(C1) bsl 4) bor + (dehex(C2) bsl 8) bor + (dehex(C3) bsl 12), + tokenize_string(Rest, ?ADV_COL(S, 6), [C | Acc]); +tokenize_string([C | Rest], S, Acc) when C >= $\s; C < 16#10FFFF -> + tokenize_string(Rest, ?ADV_COL(S, 1), [C | Acc]). + +tokenize_number(IoList=[C | _], Mode, S=#decoder{input_encoding=utf8}, Acc) + when is_list(C); is_binary(C); C >= 16#7f -> + List = xmerl_ucs:from_utf8(iolist_to_binary(IoList)), + tokenize_number(List, Mode, S#decoder{input_encoding=unicode}, Acc); +tokenize_number([$- | Rest], sign, S, []) -> + tokenize_number(Rest, int, ?INC_COL(S), [$-]); +tokenize_number(Rest, sign, S, []) -> + tokenize_number(Rest, int, S, []); +tokenize_number([$0 | Rest], int, S, Acc) -> + tokenize_number(Rest, frac, ?INC_COL(S), [$0 | Acc]); +tokenize_number([C | Rest], int, S, Acc) when C >= $1, C =< $9 -> + tokenize_number(Rest, int1, ?INC_COL(S), [C | Acc]); +tokenize_number([C | Rest], int1, S, Acc) when C >= $0, C =< $9 -> + tokenize_number(Rest, int1, ?INC_COL(S), [C | Acc]); +tokenize_number(Rest, int1, S, Acc) -> + tokenize_number(Rest, frac, S, Acc); +tokenize_number([$., C | Rest], frac, S, Acc) when C >= $0, C =< $9 -> + tokenize_number(Rest, frac1, ?ADV_COL(S, 2), [C, $. | Acc]); +tokenize_number([E | Rest], frac, S, Acc) when E == $e; E == $E -> + tokenize_number(Rest, esign, ?INC_COL(S), [$e, $0, $. | Acc]); +tokenize_number(Rest, frac, S, Acc) -> + {{int, lists:reverse(Acc)}, Rest, S}; +tokenize_number([C | Rest], frac1, S, Acc) when C >= $0, C =< $9 -> + tokenize_number(Rest, frac1, ?INC_COL(S), [C | Acc]); +tokenize_number([E | Rest], frac1, S, Acc) when E == $e; E == $E -> + tokenize_number(Rest, esign, ?INC_COL(S), [$e | Acc]); +tokenize_number(Rest, frac1, S, Acc) -> + {{float, lists:reverse(Acc)}, Rest, S}; +tokenize_number([C | Rest], esign, S, Acc) when C == $-; C == $+ -> + tokenize_number(Rest, eint, ?INC_COL(S), [C | Acc]); +tokenize_number(Rest, esign, S, Acc) -> + tokenize_number(Rest, eint, S, Acc); +tokenize_number([C | Rest], eint, S, Acc) when C >= $0, C =< $9 -> + tokenize_number(Rest, eint1, ?INC_COL(S), [C | Acc]); +tokenize_number([C | Rest], eint1, S, Acc) when C >= $0, C =< $9 -> + tokenize_number(Rest, eint1, ?INC_COL(S), [C | Acc]); +tokenize_number(Rest, eint1, S, Acc) -> + {{float, lists:reverse(Acc)}, Rest, S}. + +tokenize([], S=#decoder{state=trim}) -> + {eof, [], S}; +tokenize([L | Rest], S) when is_list(L) -> + tokenize(L ++ Rest, S); +tokenize([B | Rest], S) when is_binary(B) -> + tokenize(xmerl_ucs:from_utf8(B) ++ Rest, S); +tokenize("\r\n" ++ Rest, S) -> + tokenize(Rest, ?INC_LINE(S)); +tokenize("\n" ++ Rest, S) -> + tokenize(Rest, ?INC_LINE(S)); +tokenize([C | Rest], S) when C == $\s; C == $\t -> + tokenize(Rest, ?INC_COL(S)); +tokenize("{" ++ Rest, S) -> + {start_object, Rest, ?INC_COL(S)}; +tokenize("}" ++ Rest, S) -> + {end_object, Rest, ?INC_COL(S)}; +tokenize("[" ++ Rest, S) -> + {start_array, Rest, ?INC_COL(S)}; +tokenize("]" ++ Rest, S) -> + {end_array, Rest, ?INC_COL(S)}; +tokenize("," ++ Rest, S) -> + {comma, Rest, ?INC_COL(S)}; +tokenize(":" ++ Rest, S) -> + {colon, Rest, ?INC_COL(S)}; +tokenize("null" ++ Rest, S) -> + {{const, null}, Rest, ?ADV_COL(S, 4)}; +tokenize("true" ++ Rest, S) -> + {{const, true}, Rest, ?ADV_COL(S, 4)}; +tokenize("false" ++ Rest, S) -> + {{const, false}, Rest, ?ADV_COL(S, 5)}; +tokenize("\"" ++ Rest, S) -> + {String, Rest1, S1} = tokenize_string(Rest, ?INC_COL(S), []), + {{const, String}, Rest1, S1}; +tokenize(L=[C | _], S) when C >= $0, C =< $9; C == $- -> + case tokenize_number(L, sign, S, []) of + {{int, Int}, Rest, S1} -> + {{const, list_to_integer(Int)}, Rest, S1}; + {{float, Float}, Rest, S1} -> + {{const, list_to_float(Float)}, Rest, S1} + end. + + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +%% testing constructs borrowed from the Yaws JSON implementation. + +%% Create an object from a list of Key/Value pairs. + +obj_new() -> + {struct, []}. + +is_obj({struct, Props}) -> + F = fun ({K, _}) when is_list(K) -> + true; + (_) -> + false + end, + lists:all(F, Props). + +obj_from_list(Props) -> + Obj = {struct, Props}, + case is_obj(Obj) of + true -> Obj; + false -> exit(json_bad_object) + end. + +%% Test for equivalence of Erlang terms. +%% Due to arbitrary order of construction, equivalent objects might +%% compare unequal as erlang terms, so we need to carefully recurse +%% through aggregates (tuples and objects). + +equiv({struct, Props1}, {struct, Props2}) -> + equiv_object(Props1, Props2); +equiv({array, L1}, {array, L2}) -> + equiv_list(L1, L2); +equiv(N1, N2) when is_number(N1), is_number(N2) -> N1 == N2; +equiv(S1, S2) when is_list(S1), is_list(S2) -> S1 == S2; +equiv(true, true) -> true; +equiv(false, false) -> true; +equiv(null, null) -> true. + +%% Object representation and traversal order is unknown. +%% Use the sledgehammer and sort property lists. + +equiv_object(Props1, Props2) -> + L1 = lists:keysort(1, Props1), + L2 = lists:keysort(1, Props2), + Pairs = lists:zip(L1, L2), + true = lists:all(fun({{K1, V1}, {K2, V2}}) -> + equiv(K1, K2) and equiv(V1, V2) + end, Pairs). + +%% Recursively compare tuple elements for equivalence. + +equiv_list([], []) -> + true; +equiv_list([V1 | L1], [V2 | L2]) -> + equiv(V1, V2) andalso equiv_list(L1, L2). + +e2j_vec_test() -> + test_one(e2j_test_vec(utf8), 1). + +issue33_test() -> + %% http://code.google.com/p/mochiweb/issues/detail?id=33 + Js = {struct, [{"key", [194, 163]}]}, + Encoder = encoder([{input_encoding, utf8}]), + "{\"key\":\"\\u00a3\"}" = lists:flatten(Encoder(Js)). + +test_one([], _N) -> + %% io:format("~p tests passed~n", [N-1]), + ok; +test_one([{E, J} | Rest], N) -> + %% io:format("[~p] ~p ~p~n", [N, E, J]), + true = equiv(E, decode(J)), + true = equiv(E, decode(encode(E))), + test_one(Rest, 1+N). + +e2j_test_vec(utf8) -> + [ + {1, "1"}, + {3.1416, "3.14160"}, % text representation may truncate, trail zeroes + {-1, "-1"}, + {-3.1416, "-3.14160"}, + {12.0e10, "1.20000e+11"}, + {1.234E+10, "1.23400e+10"}, + {-1.234E-10, "-1.23400e-10"}, + {10.0, "1.0e+01"}, + {123.456, "1.23456E+2"}, + {10.0, "1e1"}, + {"foo", "\"foo\""}, + {"foo" ++ [5] ++ "bar", "\"foo\\u0005bar\""}, + {"", "\"\""}, + {"\"", "\"\\\"\""}, + {"\n\n\n", "\"\\n\\n\\n\""}, + {"\\", "\"\\\\\""}, + {"\" \b\f\r\n\t\"", "\"\\\" \\b\\f\\r\\n\\t\\\"\""}, + {obj_new(), "{}"}, + {obj_from_list([{"foo", "bar"}]), "{\"foo\":\"bar\"}"}, + {obj_from_list([{"foo", "bar"}, {"baz", 123}]), + "{\"foo\":\"bar\",\"baz\":123}"}, + {{array, []}, "[]"}, + {{array, [{array, []}]}, "[[]]"}, + {{array, [1, "foo"]}, "[1,\"foo\"]"}, + + % json array in a json object + {obj_from_list([{"foo", {array, [123]}}]), + "{\"foo\":[123]}"}, + + % json object in a json object + {obj_from_list([{"foo", obj_from_list([{"bar", true}])}]), + "{\"foo\":{\"bar\":true}}"}, + + % fold evaluation order + {obj_from_list([{"foo", {array, []}}, + {"bar", obj_from_list([{"baz", true}])}, + {"alice", "bob"}]), + "{\"foo\":[],\"bar\":{\"baz\":true},\"alice\":\"bob\"}"}, + + % json object in a json array + {{array, [-123, "foo", obj_from_list([{"bar", {array, []}}]), null]}, + "[-123,\"foo\",{\"bar\":[]},null]"} + ]. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochilists.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochilists.erl new file mode 100644 index 0000000..d93b241 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochilists.erl @@ -0,0 +1,104 @@ +%% @copyright Copyright (c) 2010 Mochi Media, Inc. +%% @author David Reid + +%% @doc Utility functions for dealing with proplists. + +-module(mochilists). +-author("David Reid "). +-export([get_value/2, get_value/3, is_defined/2, set_default/2, set_defaults/2]). + +%% @spec set_default({Key::term(), Value::term()}, Proplist::list()) -> list() +%% +%% @doc Return new Proplist with {Key, Value} set if not is_defined(Key, Proplist). +set_default({Key, Value}, Proplist) -> + case is_defined(Key, Proplist) of + true -> + Proplist; + false -> + [{Key, Value} | Proplist] + end. + +%% @spec set_defaults([{Key::term(), Value::term()}], Proplist::list()) -> list() +%% +%% @doc Return new Proplist with {Key, Value} set if not is_defined(Key, Proplist). +set_defaults(DefaultProps, Proplist) -> + lists:foldl(fun set_default/2, Proplist, DefaultProps). + + +%% @spec is_defined(Key::term(), Proplist::list()) -> bool() +%% +%% @doc Returns true if Propist contains at least one entry associated +%% with Key, otherwise false is returned. +is_defined(Key, Proplist) -> + lists:keyfind(Key, 1, Proplist) =/= false. + + +%% @spec get_value(Key::term(), Proplist::list()) -> term() | undefined +%% +%% @doc Return the value of Key or undefined +get_value(Key, Proplist) -> + get_value(Key, Proplist, undefined). + +%% @spec get_value(Key::term(), Proplist::list(), Default::term()) -> term() +%% +%% @doc Return the value of Key or Default +get_value(_Key, [], Default) -> + Default; +get_value(Key, Proplist, Default) -> + case lists:keyfind(Key, 1, Proplist) of + false -> + Default; + {Key, Value} -> + Value + end. + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +set_defaults_test() -> + ?assertEqual( + [{k, v}], + set_defaults([{k, v}], [])), + ?assertEqual( + [{k, v}], + set_defaults([{k, vee}], [{k, v}])), + ?assertEqual( + lists:sort([{kay, vee}, {k, v}]), + lists:sort(set_defaults([{k, vee}, {kay, vee}], [{k, v}]))), + ok. + +set_default_test() -> + ?assertEqual( + [{k, v}], + set_default({k, v}, [])), + ?assertEqual( + [{k, v}], + set_default({k, vee}, [{k, v}])), + ok. + +get_value_test() -> + ?assertEqual( + undefined, + get_value(foo, [])), + ?assertEqual( + undefined, + get_value(foo, [{bar, baz}])), + ?assertEqual( + bar, + get_value(foo, [{foo, bar}])), + ?assertEqual( + default, + get_value(foo, [], default)), + ?assertEqual( + default, + get_value(foo, [{bar, baz}], default)), + ?assertEqual( + bar, + get_value(foo, [{foo, bar}], default)), + ok. + +-endif. + diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochilogfile2.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochilogfile2.erl new file mode 100644 index 0000000..b4a7e3c --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochilogfile2.erl @@ -0,0 +1,140 @@ +%% @author Bob Ippolito +%% @copyright 2010 Mochi Media, Inc. + +%% @doc Write newline delimited log files, ensuring that if a truncated +%% entry is found on log open then it is fixed before writing. Uses +%% delayed writes and raw files for performance. +-module(mochilogfile2). +-author('bob@mochimedia.com'). + +-export([open/1, write/2, close/1, name/1]). + +%% @spec open(Name) -> Handle +%% @doc Open the log file Name, creating or appending as necessary. All data +%% at the end of the file will be truncated until a newline is found, to +%% ensure that all records are complete. +open(Name) -> + {ok, FD} = file:open(Name, [raw, read, write, delayed_write, binary]), + fix_log(FD), + {?MODULE, Name, FD}. + +%% @spec name(Handle) -> string() +%% @doc Return the path of the log file. +name({?MODULE, Name, _FD}) -> + Name. + +%% @spec write(Handle, IoData) -> ok +%% @doc Write IoData to the log file referenced by Handle. +write({?MODULE, _Name, FD}, IoData) -> + ok = file:write(FD, [IoData, $\n]), + ok. + +%% @spec close(Handle) -> ok +%% @doc Close the log file referenced by Handle. +close({?MODULE, _Name, FD}) -> + ok = file:sync(FD), + ok = file:close(FD), + ok. + +fix_log(FD) -> + {ok, Location} = file:position(FD, eof), + Seek = find_last_newline(FD, Location), + {ok, Seek} = file:position(FD, Seek), + ok = file:truncate(FD), + ok. + +%% Seek backwards to the last valid log entry +find_last_newline(_FD, N) when N =< 1 -> + 0; +find_last_newline(FD, Location) -> + case file:pread(FD, Location - 1, 1) of + {ok, <<$\n>>} -> + Location; + {ok, _} -> + find_last_newline(FD, Location - 1) + end. + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +name_test() -> + D = mochitemp:mkdtemp(), + FileName = filename:join(D, "open_close_test.log"), + H = open(FileName), + ?assertEqual( + FileName, + name(H)), + close(H), + file:delete(FileName), + file:del_dir(D), + ok. + +open_close_test() -> + D = mochitemp:mkdtemp(), + FileName = filename:join(D, "open_close_test.log"), + OpenClose = fun () -> + H = open(FileName), + ?assertEqual( + true, + filelib:is_file(FileName)), + ok = close(H), + ?assertEqual( + {ok, <<>>}, + file:read_file(FileName)), + ok + end, + OpenClose(), + OpenClose(), + file:delete(FileName), + file:del_dir(D), + ok. + +write_test() -> + D = mochitemp:mkdtemp(), + FileName = filename:join(D, "write_test.log"), + F = fun () -> + H = open(FileName), + write(H, "test line"), + close(H), + ok + end, + F(), + ?assertEqual( + {ok, <<"test line\n">>}, + file:read_file(FileName)), + F(), + ?assertEqual( + {ok, <<"test line\ntest line\n">>}, + file:read_file(FileName)), + file:delete(FileName), + file:del_dir(D), + ok. + +fix_log_test() -> + D = mochitemp:mkdtemp(), + FileName = filename:join(D, "write_test.log"), + file:write_file(FileName, <<"first line good\nsecond line bad">>), + F = fun () -> + H = open(FileName), + write(H, "test line"), + close(H), + ok + end, + F(), + ?assertEqual( + {ok, <<"first line good\ntest line\n">>}, + file:read_file(FileName)), + file:write_file(FileName, <<"first line bad">>), + F(), + ?assertEqual( + {ok, <<"test line\n">>}, + file:read_file(FileName)), + F(), + ?assertEqual( + {ok, <<"test line\ntest line\n">>}, + file:read_file(FileName)), + ok. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochitemp.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochitemp.erl new file mode 100644 index 0000000..f64876d --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochitemp.erl @@ -0,0 +1,307 @@ +%% @author Bob Ippolito +%% @copyright 2010 Mochi Media, Inc. + +%% @doc Create temporary files and directories. + +-module(mochitemp). +-export([gettempdir/0]). +-export([mkdtemp/0, mkdtemp/3]). +-export([rmtempdir/1]). +%% -export([mkstemp/4]). +-define(SAFE_CHARS, {$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, + $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, $1, $2, $3, $4, $5, $6, $7, $8, $9, $_}). +-define(TMP_MAX, 10000). + +-include_lib("kernel/include/file.hrl"). + +%% TODO: An ugly wrapper over the mktemp tool with open_port and sadness? +%% We can't implement this race-free in Erlang without the ability +%% to issue O_CREAT|O_EXCL. I suppose we could hack something with +%% mkdtemp, del_dir, open. +%% mkstemp(Suffix, Prefix, Dir, Options) -> +%% ok. + +rmtempdir(Dir) -> + case file:del_dir(Dir) of + {error, eexist} -> + ok = rmtempdirfiles(Dir), + ok = file:del_dir(Dir); + ok -> + ok + end. + +rmtempdirfiles(Dir) -> + {ok, Files} = file:list_dir(Dir), + ok = rmtempdirfiles(Dir, Files). + +rmtempdirfiles(_Dir, []) -> + ok; +rmtempdirfiles(Dir, [Basename | Rest]) -> + Path = filename:join([Dir, Basename]), + case filelib:is_dir(Path) of + true -> + ok = rmtempdir(Path); + false -> + ok = file:delete(Path) + end, + rmtempdirfiles(Dir, Rest). + +mkdtemp() -> + mkdtemp("", "tmp", gettempdir()). + +mkdtemp(Suffix, Prefix, Dir) -> + mkdtemp_n(rngpath_fun(Suffix, Prefix, Dir), ?TMP_MAX). + + + +mkdtemp_n(RngPath, 1) -> + make_dir(RngPath()); +mkdtemp_n(RngPath, N) -> + try make_dir(RngPath()) + catch throw:{error, eexist} -> + mkdtemp_n(RngPath, N - 1) + end. + +make_dir(Path) -> + case file:make_dir(Path) of + ok -> + ok; + E={error, eexist} -> + throw(E) + end, + %% Small window for a race condition here because dir is created 777 + ok = file:write_file_info(Path, #file_info{mode=8#0700}), + Path. + +rngpath_fun(Prefix, Suffix, Dir) -> + fun () -> + filename:join([Dir, Prefix ++ rngchars(6) ++ Suffix]) + end. + +rngchars(0) -> + ""; +rngchars(N) -> + [rngchar() | rngchars(N - 1)]. + +rngchar() -> + rngchar(mochiweb_util:rand_uniform(0, tuple_size(?SAFE_CHARS))). + +rngchar(C) -> + element(1 + C, ?SAFE_CHARS). + +%% @spec gettempdir() -> string() +%% @doc Get a usable temporary directory using the first of these that is a directory: +%% $TMPDIR, $TMP, $TEMP, "/tmp", "/var/tmp", "/usr/tmp", ".". +gettempdir() -> + gettempdir(gettempdir_checks(), fun normalize_dir/1). + +gettempdir_checks() -> + [{fun os:getenv/1, ["TMPDIR", "TMP", "TEMP"]}, + {fun gettempdir_identity/1, ["/tmp", "/var/tmp", "/usr/tmp"]}, + {fun gettempdir_cwd/1, [cwd]}]. + +gettempdir_identity(L) -> + L. + +gettempdir_cwd(cwd) -> + {ok, L} = file:get_cwd(), + L. + +gettempdir([{_F, []} | RestF], Normalize) -> + gettempdir(RestF, Normalize); +gettempdir([{F, [L | RestL]} | RestF], Normalize) -> + case Normalize(F(L)) of + false -> + gettempdir([{F, RestL} | RestF], Normalize); + Dir -> + Dir + end. + +normalize_dir(False) when False =:= false orelse False =:= "" -> + %% Erlang doesn't have an unsetenv, wtf. + false; +normalize_dir(L) -> + Dir = filename:absname(L), + case filelib:is_dir(Dir) of + false -> + false; + true -> + Dir + end. + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +pushenv(L) -> + [{K, os:getenv(K)} || K <- L]. +popenv(L) -> + F = fun ({K, false}) -> + %% Erlang doesn't have an unsetenv, wtf. + os:putenv(K, ""); + ({K, V}) -> + os:putenv(K, V) + end, + lists:foreach(F, L). + +gettempdir_fallback_test() -> + ?assertEqual( + "/", + gettempdir([{fun gettempdir_identity/1, ["/--not-here--/"]}, + {fun gettempdir_identity/1, ["/"]}], + fun normalize_dir/1)), + ?assertEqual( + "/", + %% simulate a true os:getenv unset env + gettempdir([{fun gettempdir_identity/1, [false]}, + {fun gettempdir_identity/1, ["/"]}], + fun normalize_dir/1)), + ok. + +gettempdir_identity_test() -> + ?assertEqual( + "/", + gettempdir([{fun gettempdir_identity/1, ["/"]}], fun normalize_dir/1)), + ok. + +gettempdir_cwd_test() -> + {ok, Cwd} = file:get_cwd(), + ?assertEqual( + normalize_dir(Cwd), + gettempdir([{fun gettempdir_cwd/1, [cwd]}], fun normalize_dir/1)), + ok. + +rngchars_test() -> + ?assertEqual( + "", + rngchars(0)), + ?assertEqual( + 10, + length(rngchars(10))), + ok. + +rngchar_test() -> + ?assertEqual( + $a, + rngchar(0)), + ?assertEqual( + $A, + rngchar(26)), + ?assertEqual( + $_, + rngchar(62)), + ok. + +mkdtemp_n_failonce_test() -> + D = mkdtemp(), + Path = filename:join([D, "testdir"]), + %% Toggle the existence of a dir so that it fails + %% the first time and succeeds the second. + F = fun () -> + case filelib:is_dir(Path) of + true -> + file:del_dir(Path); + false -> + file:make_dir(Path) + end, + Path + end, + try + %% Fails the first time + ?assertThrow( + {error, eexist}, + mkdtemp_n(F, 1)), + %% Reset state + file:del_dir(Path), + %% Succeeds the second time + ?assertEqual( + Path, + mkdtemp_n(F, 2)) + after rmtempdir(D) + end, + ok. + +mkdtemp_n_fail_test() -> + {ok, Cwd} = file:get_cwd(), + ?assertThrow( + {error, eexist}, + mkdtemp_n(fun () -> Cwd end, 1)), + ?assertThrow( + {error, eexist}, + mkdtemp_n(fun () -> Cwd end, 2)), + ok. + +make_dir_fail_test() -> + {ok, Cwd} = file:get_cwd(), + ?assertThrow( + {error, eexist}, + make_dir(Cwd)), + ok. + +mkdtemp_test() -> + D = mkdtemp(), + ?assertEqual( + true, + filelib:is_dir(D)), + ?assertEqual( + ok, + file:del_dir(D)), + ok. + +rmtempdir_test() -> + D1 = mkdtemp(), + ?assertEqual( + true, + filelib:is_dir(D1)), + ?assertEqual( + ok, + rmtempdir(D1)), + D2 = mkdtemp(), + ?assertEqual( + true, + filelib:is_dir(D2)), + ok = file:write_file(filename:join([D2, "foo"]), <<"bytes">>), + D3 = mkdtemp("suffix", "prefix", D2), + ?assertEqual( + true, + filelib:is_dir(D3)), + ok = file:write_file(filename:join([D3, "foo"]), <<"bytes">>), + ?assertEqual( + ok, + rmtempdir(D2)), + ?assertEqual( + {error, enoent}, + file:consult(D3)), + ?assertEqual( + {error, enoent}, + file:consult(D2)), + ok. + +gettempdir_env_test() -> + Env = pushenv(["TMPDIR", "TEMP", "TMP"]), + FalseEnv = [{"TMPDIR", false}, {"TEMP", false}, {"TMP", false}], + try + popenv(FalseEnv), + popenv([{"TMPDIR", "/"}]), + ?assertEqual( + "/", + os:getenv("TMPDIR")), + ?assertEqual( + "/", + gettempdir()), + {ok, Cwd} = file:get_cwd(), + popenv(FalseEnv), + popenv([{"TMP", Cwd}]), + ?assertEqual( + normalize_dir(Cwd), + gettempdir()) + after popenv(Env) + end, + ok. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiutf8.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiutf8.erl new file mode 100644 index 0000000..c9d2751 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiutf8.erl @@ -0,0 +1,317 @@ +%% @copyright 2010 Mochi Media, Inc. +%% @author Bob Ippolito + +%% @doc Algorithm to convert any binary to a valid UTF-8 sequence by ignoring +%% invalid bytes. + +-module(mochiutf8). +-export([valid_utf8_bytes/1, codepoint_to_bytes/1, codepoints_to_bytes/1]). +-export([bytes_to_codepoints/1, bytes_foldl/3, codepoint_foldl/3]). +-export([read_codepoint/1, len/1]). + +%% External API + +%% -type unichar_low() :: 0..16#d7ff. +%% -type unichar_high() :: 16#e000..16#10ffff. +%% -type unichar() :: unichar_low() | unichar_high(). + +%% -spec codepoint_to_bytes(unichar()) -> binary(). +%% @doc Convert a unicode codepoint to UTF-8 bytes. +codepoint_to_bytes(C) when (C >= 16#00 andalso C =< 16#7f) -> + %% U+0000 - U+007F - 7 bits + <>; +codepoint_to_bytes(C) when (C >= 16#080 andalso C =< 16#07FF) -> + %% U+0080 - U+07FF - 11 bits + <<0:5, B1:5, B0:6>> = <>, + <<2#110:3, B1:5, + 2#10:2, B0:6>>; +codepoint_to_bytes(C) when (C >= 16#0800 andalso C =< 16#FFFF) andalso + (C < 16#D800 orelse C > 16#DFFF) -> + %% U+0800 - U+FFFF - 16 bits (excluding UTC-16 surrogate code points) + <> = <>, + <<2#1110:4, B2:4, + 2#10:2, B1:6, + 2#10:2, B0:6>>; +codepoint_to_bytes(C) when (C >= 16#010000 andalso C =< 16#10FFFF) -> + %% U+10000 - U+10FFFF - 21 bits + <<0:3, B3:3, B2:6, B1:6, B0:6>> = <>, + <<2#11110:5, B3:3, + 2#10:2, B2:6, + 2#10:2, B1:6, + 2#10:2, B0:6>>. + +%% -spec codepoints_to_bytes([unichar()]) -> binary(). +%% @doc Convert a list of codepoints to a UTF-8 binary. +codepoints_to_bytes(L) -> + <<<<(codepoint_to_bytes(C))/binary>> || C <- L>>. + +%% -spec read_codepoint(binary()) -> {unichar(), binary(), binary()}. +read_codepoint(Bin = <<2#0:1, C:7, Rest/binary>>) -> + %% U+0000 - U+007F - 7 bits + <> = Bin, + {C, B, Rest}; +read_codepoint(Bin = <<2#110:3, B1:5, + 2#10:2, B0:6, + Rest/binary>>) -> + %% U+0080 - U+07FF - 11 bits + case <> of + <> when C >= 16#80 -> + <> = Bin, + {C, B, Rest} + end; +read_codepoint(Bin = <<2#1110:4, B2:4, + 2#10:2, B1:6, + 2#10:2, B0:6, + Rest/binary>>) -> + %% U+0800 - U+FFFF - 16 bits (excluding UTC-16 surrogate code points) + case <> of + <> when (C >= 16#0800 andalso C =< 16#FFFF) andalso + (C < 16#D800 orelse C > 16#DFFF) -> + <> = Bin, + {C, B, Rest} + end; +read_codepoint(Bin = <<2#11110:5, B3:3, + 2#10:2, B2:6, + 2#10:2, B1:6, + 2#10:2, B0:6, + Rest/binary>>) -> + %% U+10000 - U+10FFFF - 21 bits + case <> of + <> when (C >= 16#010000 andalso C =< 16#10FFFF) -> + <> = Bin, + {C, B, Rest} + end. + +%% -spec codepoint_foldl(fun((unichar(), _) -> _), _, binary()) -> _. +codepoint_foldl(F, Acc, <<>>) when is_function(F, 2) -> + Acc; +codepoint_foldl(F, Acc, Bin) -> + {C, _, Rest} = read_codepoint(Bin), + codepoint_foldl(F, F(C, Acc), Rest). + +%% -spec bytes_foldl(fun((binary(), _) -> _), _, binary()) -> _. +bytes_foldl(F, Acc, <<>>) when is_function(F, 2) -> + Acc; +bytes_foldl(F, Acc, Bin) -> + {_, B, Rest} = read_codepoint(Bin), + bytes_foldl(F, F(B, Acc), Rest). + +%% -spec bytes_to_codepoints(binary()) -> [unichar()]. +bytes_to_codepoints(B) -> + lists:reverse(codepoint_foldl(fun (C, Acc) -> [C | Acc] end, [], B)). + +%% -spec len(binary()) -> non_neg_integer(). +len(<<>>) -> + 0; +len(B) -> + {_, _, Rest} = read_codepoint(B), + 1 + len(Rest). + +%% -spec valid_utf8_bytes(B::binary()) -> binary(). +%% @doc Return only the bytes in B that represent valid UTF-8. Uses +%% the following recursive algorithm: skip one byte if B does not +%% follow UTF-8 syntax (a 1-4 byte encoding of some number), +%% skip sequence of 2-4 bytes if it represents an overlong encoding +%% or bad code point (surrogate U+D800 - U+DFFF or > U+10FFFF). +valid_utf8_bytes(B) when is_binary(B) -> + binary_skip_bytes(B, invalid_utf8_indexes(B)). + +%% Internal API + +%% -spec binary_skip_bytes(binary(), [non_neg_integer()]) -> binary(). +%% @doc Return B, but skipping the 0-based indexes in L. +binary_skip_bytes(B, []) -> + B; +binary_skip_bytes(B, L) -> + binary_skip_bytes(B, L, 0, []). + +%% @private +%% -spec binary_skip_bytes(binary(), [non_neg_integer()], non_neg_integer(), iolist()) -> binary(). +binary_skip_bytes(B, [], _N, Acc) -> + iolist_to_binary(lists:reverse([B | Acc])); +binary_skip_bytes(<<_, RestB/binary>>, [N | RestL], N, Acc) -> + binary_skip_bytes(RestB, RestL, 1 + N, Acc); +binary_skip_bytes(<>, L, N, Acc) -> + binary_skip_bytes(RestB, L, 1 + N, [C | Acc]). + +%% -spec invalid_utf8_indexes(binary()) -> [non_neg_integer()]. +%% @doc Return the 0-based indexes in B that are not valid UTF-8. +invalid_utf8_indexes(B) -> + invalid_utf8_indexes(B, 0, []). + +%% @private. +%% -spec invalid_utf8_indexes(binary(), non_neg_integer(), [non_neg_integer()]) -> [non_neg_integer()]. +invalid_utf8_indexes(<>, N, Acc) when C < 16#80 -> + %% U+0000 - U+007F - 7 bits + invalid_utf8_indexes(Rest, 1 + N, Acc); +invalid_utf8_indexes(<>, N, Acc) + when C1 band 16#E0 =:= 16#C0, + C2 band 16#C0 =:= 16#80 -> + %% U+0080 - U+07FF - 11 bits + case ((C1 band 16#1F) bsl 6) bor (C2 band 16#3F) of + C when C < 16#80 -> + %% Overlong encoding. + invalid_utf8_indexes(Rest, 2 + N, [1 + N, N | Acc]); + _ -> + %% Upper bound U+07FF does not need to be checked + invalid_utf8_indexes(Rest, 2 + N, Acc) + end; +invalid_utf8_indexes(<>, N, Acc) + when C1 band 16#F0 =:= 16#E0, + C2 band 16#C0 =:= 16#80, + C3 band 16#C0 =:= 16#80 -> + %% U+0800 - U+FFFF - 16 bits + case ((((C1 band 16#0F) bsl 6) bor (C2 band 16#3F)) bsl 6) bor + (C3 band 16#3F) of + C when (C < 16#800) orelse (C >= 16#D800 andalso C =< 16#DFFF) -> + %% Overlong encoding or surrogate. + invalid_utf8_indexes(Rest, 3 + N, [2 + N, 1 + N, N | Acc]); + _ -> + %% Upper bound U+FFFF does not need to be checked + invalid_utf8_indexes(Rest, 3 + N, Acc) + end; +invalid_utf8_indexes(<>, N, Acc) + when C1 band 16#F8 =:= 16#F0, + C2 band 16#C0 =:= 16#80, + C3 band 16#C0 =:= 16#80, + C4 band 16#C0 =:= 16#80 -> + %% U+10000 - U+10FFFF - 21 bits + case ((((((C1 band 16#0F) bsl 6) bor (C2 band 16#3F)) bsl 6) bor + (C3 band 16#3F)) bsl 6) bor (C4 band 16#3F) of + C when (C < 16#10000) orelse (C > 16#10FFFF) -> + %% Overlong encoding or invalid code point. + invalid_utf8_indexes(Rest, 4 + N, [3 + N, 2 + N, 1 + N, N | Acc]); + _ -> + invalid_utf8_indexes(Rest, 4 + N, Acc) + end; +invalid_utf8_indexes(<<_, Rest/binary>>, N, Acc) -> + %% Invalid char + invalid_utf8_indexes(Rest, 1 + N, [N | Acc]); +invalid_utf8_indexes(<<>>, _N, Acc) -> + lists:reverse(Acc). + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +binary_skip_bytes_test() -> + ?assertEqual(<<"foo">>, + binary_skip_bytes(<<"foo">>, [])), + ?assertEqual(<<"foobar">>, + binary_skip_bytes(<<"foo bar">>, [3])), + ?assertEqual(<<"foo">>, + binary_skip_bytes(<<"foo bar">>, [3, 4, 5, 6])), + ?assertEqual(<<"oo bar">>, + binary_skip_bytes(<<"foo bar">>, [0])), + ok. + +invalid_utf8_indexes_test() -> + ?assertEqual( + [], + invalid_utf8_indexes(<<"unicode snowman for you: ", 226, 152, 131>>)), + ?assertEqual( + [0], + invalid_utf8_indexes(<<128>>)), + ?assertEqual( + [57,59,60,64,66,67], + invalid_utf8_indexes(<<"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; (", + 167, 65, 170, 186, 73, 83, 80, 166, 87, 186, 217, 41, 41>>)), + ok. + +codepoint_to_bytes_test() -> + %% U+0000 - U+007F - 7 bits + %% U+0080 - U+07FF - 11 bits + %% U+0800 - U+FFFF - 16 bits (excluding UTC-16 surrogate code points) + %% U+10000 - U+10FFFF - 21 bits + ?assertEqual( + <<"a">>, + codepoint_to_bytes($a)), + ?assertEqual( + <<16#c2, 16#80>>, + codepoint_to_bytes(16#80)), + ?assertEqual( + <<16#df, 16#bf>>, + codepoint_to_bytes(16#07ff)), + ?assertEqual( + <<16#ef, 16#bf, 16#bf>>, + codepoint_to_bytes(16#ffff)), + ?assertEqual( + <<16#f4, 16#8f, 16#bf, 16#bf>>, + codepoint_to_bytes(16#10ffff)), + ok. + +bytes_foldl_test() -> + ?assertEqual( + <<"abc">>, + bytes_foldl(fun (B, Acc) -> <> end, <<>>, <<"abc">>)), + ?assertEqual( + <<"abc", 226, 152, 131, 228, 184, 173, 194, 133, 244,143,191,191>>, + bytes_foldl(fun (B, Acc) -> <> end, <<>>, + <<"abc", 226, 152, 131, 228, 184, 173, 194, 133, 244,143,191,191>>)), + ok. + +bytes_to_codepoints_test() -> + ?assertEqual( + "abc" ++ [16#2603, 16#4e2d, 16#85, 16#10ffff], + bytes_to_codepoints(<<"abc", 226, 152, 131, 228, 184, 173, 194, 133, 244,143,191,191>>)), + ok. + +codepoint_foldl_test() -> + ?assertEqual( + "cba", + codepoint_foldl(fun (C, Acc) -> [C | Acc] end, [], <<"abc">>)), + ?assertEqual( + [16#10ffff, 16#85, 16#4e2d, 16#2603 | "cba"], + codepoint_foldl(fun (C, Acc) -> [C | Acc] end, [], + <<"abc", 226, 152, 131, 228, 184, 173, 194, 133, 244,143,191,191>>)), + ok. + +len_test() -> + ?assertEqual( + 29, + len(<<"unicode snowman for you: ", 226, 152, 131, 228, 184, 173, 194, 133, 244, 143, 191, 191>>)), + ok. + +codepoints_to_bytes_test() -> + ?assertEqual( + iolist_to_binary(lists:map(fun codepoint_to_bytes/1, lists:seq(1, 1000))), + codepoints_to_bytes(lists:seq(1, 1000))), + ok. + +valid_utf8_bytes_test() -> + ?assertEqual( + <<"invalid U+11ffff: ">>, + valid_utf8_bytes(<<"invalid U+11ffff: ", 244, 159, 191, 191>>)), + ?assertEqual( + <<"U+10ffff: ", 244, 143, 191, 191>>, + valid_utf8_bytes(<<"U+10ffff: ", 244, 143, 191, 191>>)), + ?assertEqual( + <<"overlong 2-byte encoding (a): ">>, + valid_utf8_bytes(<<"overlong 2-byte encoding (a): ", 2#11000001, 2#10100001>>)), + ?assertEqual( + <<"overlong 2-byte encoding (!): ">>, + valid_utf8_bytes(<<"overlong 2-byte encoding (!): ", 2#11000000, 2#10100001>>)), + ?assertEqual( + <<"mu: ", 194, 181>>, + valid_utf8_bytes(<<"mu: ", 194, 181>>)), + ?assertEqual( + <<"bad coding bytes: ">>, + valid_utf8_bytes(<<"bad coding bytes: ", 2#10011111, 2#10111111, 2#11111111>>)), + ?assertEqual( + <<"low surrogate (unpaired): ">>, + valid_utf8_bytes(<<"low surrogate (unpaired): ", 237, 176, 128>>)), + ?assertEqual( + <<"high surrogate (unpaired): ">>, + valid_utf8_bytes(<<"high surrogate (unpaired): ", 237, 191, 191>>)), + ?assertEqual( + <<"unicode snowman for you: ", 226, 152, 131>>, + valid_utf8_bytes(<<"unicode snowman for you: ", 226, 152, 131>>)), + ?assertEqual( + <<"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; (AISPW))">>, + valid_utf8_bytes(<<"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; (", + 167, 65, 170, 186, 73, 83, 80, 166, 87, 186, 217, 41, 41>>)), + ok. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb.app.src b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb.app.src new file mode 100644 index 0000000..4a6808e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb.app.src @@ -0,0 +1,8 @@ +%% This is generated from src/mochiweb.app.src +{application, mochiweb, + [{description, "MochiMedia Web Server"}, + {vsn, "2.7.0"}, + {modules, []}, + {registered, []}, + {env, []}, + {applications, [kernel, stdlib, inets, xmerl]}]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb.erl new file mode 100644 index 0000000..f597c73 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb.erl @@ -0,0 +1,76 @@ +%% @author Bob Ippolito +%% @copyright 2007 Mochi Media, Inc. + +%% @doc Start and stop the MochiWeb server. + +-module(mochiweb). +-author('bob@mochimedia.com'). + +-export([new_request/1, new_response/1]). +-export([all_loaded/0, all_loaded/1, reload/0]). +-export([ensure_started/1]). + +reload() -> + [c:l(Module) || Module <- all_loaded()]. + +all_loaded() -> + all_loaded(filename:dirname(code:which(?MODULE))). + +all_loaded(Base) when is_atom(Base) -> + []; +all_loaded(Base) -> + FullBase = Base ++ "/", + F = fun ({_Module, Loaded}, Acc) when is_atom(Loaded) -> + Acc; + ({Module, Loaded}, Acc) -> + case lists:prefix(FullBase, Loaded) of + true -> + [Module | Acc]; + false -> + Acc + end + end, + lists:foldl(F, [], code:all_loaded()). + + +%% @spec new_request({Socket, Request, Headers}) -> MochiWebRequest +%% @doc Return a mochiweb_request data structure. +new_request({Socket, {Method, {abs_path, Uri}, Version}, Headers}) -> + mochiweb_request:new(Socket, + Method, + Uri, + Version, + mochiweb_headers:make(Headers)); +% this case probably doesn't "exist". +new_request({Socket, {Method, {absoluteURI, _Protocol, _Host, _Port, Uri}, + Version}, Headers}) -> + mochiweb_request:new(Socket, + Method, + Uri, + Version, + mochiweb_headers:make(Headers)); +%% Request-URI is "*" +%% From http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 +new_request({Socket, {Method, '*'=Uri, Version}, Headers}) -> + mochiweb_request:new(Socket, + Method, + Uri, + Version, + mochiweb_headers:make(Headers)). + +%% @spec new_response({Request, integer(), Headers}) -> MochiWebResponse +%% @doc Return a mochiweb_response data structure. +new_response({Request, Code, Headers}) -> + mochiweb_response:new(Request, + Code, + mochiweb_headers:make(Headers)). + +%% @spec ensure_started(App::atom()) -> ok +%% @doc Start the given App if it has not been started already. +ensure_started(App) -> + case application:start(App) of + ok -> + ok; + {error, {already_started, App}} -> + ok + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_acceptor.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_acceptor.erl new file mode 100644 index 0000000..ebbaf45 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_acceptor.erl @@ -0,0 +1,50 @@ +%% @author Bob Ippolito +%% @copyright 2010 Mochi Media, Inc. + +%% @doc MochiWeb acceptor. + +-module(mochiweb_acceptor). +-author('bob@mochimedia.com'). + +-include("internal.hrl"). + +-export([start_link/3, init/3]). + +start_link(Server, Listen, Loop) -> + proc_lib:spawn_link(?MODULE, init, [Server, Listen, Loop]). + +init(Server, Listen, Loop) -> + T1 = os:timestamp(), + case catch mochiweb_socket:accept(Listen) of + {ok, Socket} -> + gen_server:cast(Server, {accepted, self(), timer:now_diff(os:timestamp(), T1)}), + call_loop(Loop, Socket); + {error, closed} -> + exit(normal); + {error, timeout} -> + init(Server, Listen, Loop); + {error, esslaccept} -> + exit(normal); + Other -> + error_logger:error_report( + [{application, mochiweb}, + "Accept failed error", + lists:flatten(io_lib:format("~p", [Other]))]), + exit({error, accept_failed}) + end. + +call_loop({M, F}, Socket) -> + M:F(Socket); +call_loop({M, F, [A1]}, Socket) -> + M:F(Socket, A1); +call_loop({M, F, A}, Socket) -> + erlang:apply(M, F, [Socket | A]); +call_loop(Loop, Socket) -> + Loop(Socket). + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_base64url.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_base64url.erl new file mode 100644 index 0000000..ab5aaec --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_base64url.erl @@ -0,0 +1,83 @@ +-module(mochiweb_base64url). +-export([encode/1, decode/1]). +%% @doc URL and filename safe base64 variant with no padding, +%% also known as "base64url" per RFC 4648. +%% +%% This differs from base64 in the following ways: +%% '-' is used in place of '+' (62), +%% '_' is used in place of '/' (63), +%% padding is implicit rather than explicit ('='). + +-spec encode(iolist()) -> binary(). +encode(B) when is_binary(B) -> + encode_binary(B); +encode(L) when is_list(L) -> + encode_binary(iolist_to_binary(L)). + +-spec decode(iolist()) -> binary(). +decode(B) when is_binary(B) -> + decode_binary(B); +decode(L) when is_list(L) -> + decode_binary(iolist_to_binary(L)). + +%% Implementation, derived from stdlib base64.erl + +%% One-based decode map. +-define(DECODE_MAP, + {bad,bad,bad,bad,bad,bad,bad,bad,ws,ws,bad,bad,ws,bad,bad, %1-15 + bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, %16-31 + ws,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,62,bad,bad, %32-47 + 52,53,54,55,56,57,58,59,60,61,bad,bad,bad,bad,bad,bad, %48-63 + bad,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14, %64-79 + 15,16,17,18,19,20,21,22,23,24,25,bad,bad,bad,bad,63, %80-95 + bad,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, %96-111 + 41,42,43,44,45,46,47,48,49,50,51,bad,bad,bad,bad,bad, %112-127 + bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, + bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, + bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, + bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, + bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, + bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, + bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, + bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad}). + +encode_binary(Bin) -> + Split = 3*(byte_size(Bin) div 3), + <> = Bin, + Main = << <<(b64e(C)):8>> || <> <= Main0 >>, + case Rest of + <> -> + <

>; + <> -> + <
>; + <<>> -> + Main + end. + +decode_binary(Bin) -> + Main = << <<(b64d(C)):6>> || <> <= Bin, + (C =/= $\t andalso C =/= $\s andalso + C =/= $\r andalso C =/= $\n) >>, + case bit_size(Main) rem 8 of + 0 -> + Main; + N -> + Split = byte_size(Main) - 1, + <> = Main, + Result + end. + +%% accessors + +b64e(X) -> + element(X+1, + {$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, + $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, $1, $2, $3, $4, $5, $6, $7, $8, $9, $-, $_}). + +b64d(X) -> + b64d_ok(element(X, ?DECODE_MAP)). + +b64d_ok(I) when is_integer(I) -> I. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_charref.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_charref.erl new file mode 100644 index 0000000..665d0f9 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_charref.erl @@ -0,0 +1,2183 @@ +%% @author Bob Ippolito +%% @copyright 2007 Mochi Media, Inc. + +%% @doc Converts HTML 5 charrefs and entities to codepoints (or lists of code points). +-module(mochiweb_charref). +-export([charref/1]). + +%% External API. + +%% @doc Convert a decimal charref, hex charref, or html entity to a unicode +%% codepoint, or return undefined on failure. +%% The input should not include an ampersand or semicolon. +%% charref("#38") = 38, charref("#x26") = 38, charref("amp") = 38. +%% -spec charref(binary() | string()) -> integer() | [integer()] | undefined. +charref(B) when is_binary(B) -> + charref(binary_to_list(B)); +charref([$#, C | L]) when C =:= $x orelse C =:= $X -> + try erlang:list_to_integer(L, 16) + catch + error:badarg -> undefined + end; +charref([$# | L]) -> + try list_to_integer(L) + catch + error:badarg -> undefined + end; +charref(L) -> + entity(L). + +%% Internal API. + +%% [2011-10-14] Generated from: +%% http://www.w3.org/TR/html5/named-character-references.html + +entity("AElig") -> 16#000C6; +entity("AMP") -> 16#00026; +entity("Aacute") -> 16#000C1; +entity("Abreve") -> 16#00102; +entity("Acirc") -> 16#000C2; +entity("Acy") -> 16#00410; +entity("Afr") -> 16#1D504; +entity("Agrave") -> 16#000C0; +entity("Alpha") -> 16#00391; +entity("Amacr") -> 16#00100; +entity("And") -> 16#02A53; +entity("Aogon") -> 16#00104; +entity("Aopf") -> 16#1D538; +entity("ApplyFunction") -> 16#02061; +entity("Aring") -> 16#000C5; +entity("Ascr") -> 16#1D49C; +entity("Assign") -> 16#02254; +entity("Atilde") -> 16#000C3; +entity("Auml") -> 16#000C4; +entity("Backslash") -> 16#02216; +entity("Barv") -> 16#02AE7; +entity("Barwed") -> 16#02306; +entity("Bcy") -> 16#00411; +entity("Because") -> 16#02235; +entity("Bernoullis") -> 16#0212C; +entity("Beta") -> 16#00392; +entity("Bfr") -> 16#1D505; +entity("Bopf") -> 16#1D539; +entity("Breve") -> 16#002D8; +entity("Bscr") -> 16#0212C; +entity("Bumpeq") -> 16#0224E; +entity("CHcy") -> 16#00427; +entity("COPY") -> 16#000A9; +entity("Cacute") -> 16#00106; +entity("Cap") -> 16#022D2; +entity("CapitalDifferentialD") -> 16#02145; +entity("Cayleys") -> 16#0212D; +entity("Ccaron") -> 16#0010C; +entity("Ccedil") -> 16#000C7; +entity("Ccirc") -> 16#00108; +entity("Cconint") -> 16#02230; +entity("Cdot") -> 16#0010A; +entity("Cedilla") -> 16#000B8; +entity("CenterDot") -> 16#000B7; +entity("Cfr") -> 16#0212D; +entity("Chi") -> 16#003A7; +entity("CircleDot") -> 16#02299; +entity("CircleMinus") -> 16#02296; +entity("CirclePlus") -> 16#02295; +entity("CircleTimes") -> 16#02297; +entity("ClockwiseContourIntegral") -> 16#02232; +entity("CloseCurlyDoubleQuote") -> 16#0201D; +entity("CloseCurlyQuote") -> 16#02019; +entity("Colon") -> 16#02237; +entity("Colone") -> 16#02A74; +entity("Congruent") -> 16#02261; +entity("Conint") -> 16#0222F; +entity("ContourIntegral") -> 16#0222E; +entity("Copf") -> 16#02102; +entity("Coproduct") -> 16#02210; +entity("CounterClockwiseContourIntegral") -> 16#02233; +entity("Cross") -> 16#02A2F; +entity("Cscr") -> 16#1D49E; +entity("Cup") -> 16#022D3; +entity("CupCap") -> 16#0224D; +entity("DD") -> 16#02145; +entity("DDotrahd") -> 16#02911; +entity("DJcy") -> 16#00402; +entity("DScy") -> 16#00405; +entity("DZcy") -> 16#0040F; +entity("Dagger") -> 16#02021; +entity("Darr") -> 16#021A1; +entity("Dashv") -> 16#02AE4; +entity("Dcaron") -> 16#0010E; +entity("Dcy") -> 16#00414; +entity("Del") -> 16#02207; +entity("Delta") -> 16#00394; +entity("Dfr") -> 16#1D507; +entity("DiacriticalAcute") -> 16#000B4; +entity("DiacriticalDot") -> 16#002D9; +entity("DiacriticalDoubleAcute") -> 16#002DD; +entity("DiacriticalGrave") -> 16#00060; +entity("DiacriticalTilde") -> 16#002DC; +entity("Diamond") -> 16#022C4; +entity("DifferentialD") -> 16#02146; +entity("Dopf") -> 16#1D53B; +entity("Dot") -> 16#000A8; +entity("DotDot") -> 16#020DC; +entity("DotEqual") -> 16#02250; +entity("DoubleContourIntegral") -> 16#0222F; +entity("DoubleDot") -> 16#000A8; +entity("DoubleDownArrow") -> 16#021D3; +entity("DoubleLeftArrow") -> 16#021D0; +entity("DoubleLeftRightArrow") -> 16#021D4; +entity("DoubleLeftTee") -> 16#02AE4; +entity("DoubleLongLeftArrow") -> 16#027F8; +entity("DoubleLongLeftRightArrow") -> 16#027FA; +entity("DoubleLongRightArrow") -> 16#027F9; +entity("DoubleRightArrow") -> 16#021D2; +entity("DoubleRightTee") -> 16#022A8; +entity("DoubleUpArrow") -> 16#021D1; +entity("DoubleUpDownArrow") -> 16#021D5; +entity("DoubleVerticalBar") -> 16#02225; +entity("DownArrow") -> 16#02193; +entity("DownArrowBar") -> 16#02913; +entity("DownArrowUpArrow") -> 16#021F5; +entity("DownBreve") -> 16#00311; +entity("DownLeftRightVector") -> 16#02950; +entity("DownLeftTeeVector") -> 16#0295E; +entity("DownLeftVector") -> 16#021BD; +entity("DownLeftVectorBar") -> 16#02956; +entity("DownRightTeeVector") -> 16#0295F; +entity("DownRightVector") -> 16#021C1; +entity("DownRightVectorBar") -> 16#02957; +entity("DownTee") -> 16#022A4; +entity("DownTeeArrow") -> 16#021A7; +entity("Downarrow") -> 16#021D3; +entity("Dscr") -> 16#1D49F; +entity("Dstrok") -> 16#00110; +entity("ENG") -> 16#0014A; +entity("ETH") -> 16#000D0; +entity("Eacute") -> 16#000C9; +entity("Ecaron") -> 16#0011A; +entity("Ecirc") -> 16#000CA; +entity("Ecy") -> 16#0042D; +entity("Edot") -> 16#00116; +entity("Efr") -> 16#1D508; +entity("Egrave") -> 16#000C8; +entity("Element") -> 16#02208; +entity("Emacr") -> 16#00112; +entity("EmptySmallSquare") -> 16#025FB; +entity("EmptyVerySmallSquare") -> 16#025AB; +entity("Eogon") -> 16#00118; +entity("Eopf") -> 16#1D53C; +entity("Epsilon") -> 16#00395; +entity("Equal") -> 16#02A75; +entity("EqualTilde") -> 16#02242; +entity("Equilibrium") -> 16#021CC; +entity("Escr") -> 16#02130; +entity("Esim") -> 16#02A73; +entity("Eta") -> 16#00397; +entity("Euml") -> 16#000CB; +entity("Exists") -> 16#02203; +entity("ExponentialE") -> 16#02147; +entity("Fcy") -> 16#00424; +entity("Ffr") -> 16#1D509; +entity("FilledSmallSquare") -> 16#025FC; +entity("FilledVerySmallSquare") -> 16#025AA; +entity("Fopf") -> 16#1D53D; +entity("ForAll") -> 16#02200; +entity("Fouriertrf") -> 16#02131; +entity("Fscr") -> 16#02131; +entity("GJcy") -> 16#00403; +entity("GT") -> 16#0003E; +entity("Gamma") -> 16#00393; +entity("Gammad") -> 16#003DC; +entity("Gbreve") -> 16#0011E; +entity("Gcedil") -> 16#00122; +entity("Gcirc") -> 16#0011C; +entity("Gcy") -> 16#00413; +entity("Gdot") -> 16#00120; +entity("Gfr") -> 16#1D50A; +entity("Gg") -> 16#022D9; +entity("Gopf") -> 16#1D53E; +entity("GreaterEqual") -> 16#02265; +entity("GreaterEqualLess") -> 16#022DB; +entity("GreaterFullEqual") -> 16#02267; +entity("GreaterGreater") -> 16#02AA2; +entity("GreaterLess") -> 16#02277; +entity("GreaterSlantEqual") -> 16#02A7E; +entity("GreaterTilde") -> 16#02273; +entity("Gscr") -> 16#1D4A2; +entity("Gt") -> 16#0226B; +entity("HARDcy") -> 16#0042A; +entity("Hacek") -> 16#002C7; +entity("Hat") -> 16#0005E; +entity("Hcirc") -> 16#00124; +entity("Hfr") -> 16#0210C; +entity("HilbertSpace") -> 16#0210B; +entity("Hopf") -> 16#0210D; +entity("HorizontalLine") -> 16#02500; +entity("Hscr") -> 16#0210B; +entity("Hstrok") -> 16#00126; +entity("HumpDownHump") -> 16#0224E; +entity("HumpEqual") -> 16#0224F; +entity("IEcy") -> 16#00415; +entity("IJlig") -> 16#00132; +entity("IOcy") -> 16#00401; +entity("Iacute") -> 16#000CD; +entity("Icirc") -> 16#000CE; +entity("Icy") -> 16#00418; +entity("Idot") -> 16#00130; +entity("Ifr") -> 16#02111; +entity("Igrave") -> 16#000CC; +entity("Im") -> 16#02111; +entity("Imacr") -> 16#0012A; +entity("ImaginaryI") -> 16#02148; +entity("Implies") -> 16#021D2; +entity("Int") -> 16#0222C; +entity("Integral") -> 16#0222B; +entity("Intersection") -> 16#022C2; +entity("InvisibleComma") -> 16#02063; +entity("InvisibleTimes") -> 16#02062; +entity("Iogon") -> 16#0012E; +entity("Iopf") -> 16#1D540; +entity("Iota") -> 16#00399; +entity("Iscr") -> 16#02110; +entity("Itilde") -> 16#00128; +entity("Iukcy") -> 16#00406; +entity("Iuml") -> 16#000CF; +entity("Jcirc") -> 16#00134; +entity("Jcy") -> 16#00419; +entity("Jfr") -> 16#1D50D; +entity("Jopf") -> 16#1D541; +entity("Jscr") -> 16#1D4A5; +entity("Jsercy") -> 16#00408; +entity("Jukcy") -> 16#00404; +entity("KHcy") -> 16#00425; +entity("KJcy") -> 16#0040C; +entity("Kappa") -> 16#0039A; +entity("Kcedil") -> 16#00136; +entity("Kcy") -> 16#0041A; +entity("Kfr") -> 16#1D50E; +entity("Kopf") -> 16#1D542; +entity("Kscr") -> 16#1D4A6; +entity("LJcy") -> 16#00409; +entity("LT") -> 16#0003C; +entity("Lacute") -> 16#00139; +entity("Lambda") -> 16#0039B; +entity("Lang") -> 16#027EA; +entity("Laplacetrf") -> 16#02112; +entity("Larr") -> 16#0219E; +entity("Lcaron") -> 16#0013D; +entity("Lcedil") -> 16#0013B; +entity("Lcy") -> 16#0041B; +entity("LeftAngleBracket") -> 16#027E8; +entity("LeftArrow") -> 16#02190; +entity("LeftArrowBar") -> 16#021E4; +entity("LeftArrowRightArrow") -> 16#021C6; +entity("LeftCeiling") -> 16#02308; +entity("LeftDoubleBracket") -> 16#027E6; +entity("LeftDownTeeVector") -> 16#02961; +entity("LeftDownVector") -> 16#021C3; +entity("LeftDownVectorBar") -> 16#02959; +entity("LeftFloor") -> 16#0230A; +entity("LeftRightArrow") -> 16#02194; +entity("LeftRightVector") -> 16#0294E; +entity("LeftTee") -> 16#022A3; +entity("LeftTeeArrow") -> 16#021A4; +entity("LeftTeeVector") -> 16#0295A; +entity("LeftTriangle") -> 16#022B2; +entity("LeftTriangleBar") -> 16#029CF; +entity("LeftTriangleEqual") -> 16#022B4; +entity("LeftUpDownVector") -> 16#02951; +entity("LeftUpTeeVector") -> 16#02960; +entity("LeftUpVector") -> 16#021BF; +entity("LeftUpVectorBar") -> 16#02958; +entity("LeftVector") -> 16#021BC; +entity("LeftVectorBar") -> 16#02952; +entity("Leftarrow") -> 16#021D0; +entity("Leftrightarrow") -> 16#021D4; +entity("LessEqualGreater") -> 16#022DA; +entity("LessFullEqual") -> 16#02266; +entity("LessGreater") -> 16#02276; +entity("LessLess") -> 16#02AA1; +entity("LessSlantEqual") -> 16#02A7D; +entity("LessTilde") -> 16#02272; +entity("Lfr") -> 16#1D50F; +entity("Ll") -> 16#022D8; +entity("Lleftarrow") -> 16#021DA; +entity("Lmidot") -> 16#0013F; +entity("LongLeftArrow") -> 16#027F5; +entity("LongLeftRightArrow") -> 16#027F7; +entity("LongRightArrow") -> 16#027F6; +entity("Longleftarrow") -> 16#027F8; +entity("Longleftrightarrow") -> 16#027FA; +entity("Longrightarrow") -> 16#027F9; +entity("Lopf") -> 16#1D543; +entity("LowerLeftArrow") -> 16#02199; +entity("LowerRightArrow") -> 16#02198; +entity("Lscr") -> 16#02112; +entity("Lsh") -> 16#021B0; +entity("Lstrok") -> 16#00141; +entity("Lt") -> 16#0226A; +entity("Map") -> 16#02905; +entity("Mcy") -> 16#0041C; +entity("MediumSpace") -> 16#0205F; +entity("Mellintrf") -> 16#02133; +entity("Mfr") -> 16#1D510; +entity("MinusPlus") -> 16#02213; +entity("Mopf") -> 16#1D544; +entity("Mscr") -> 16#02133; +entity("Mu") -> 16#0039C; +entity("NJcy") -> 16#0040A; +entity("Nacute") -> 16#00143; +entity("Ncaron") -> 16#00147; +entity("Ncedil") -> 16#00145; +entity("Ncy") -> 16#0041D; +entity("NegativeMediumSpace") -> 16#0200B; +entity("NegativeThickSpace") -> 16#0200B; +entity("NegativeThinSpace") -> 16#0200B; +entity("NegativeVeryThinSpace") -> 16#0200B; +entity("NestedGreaterGreater") -> 16#0226B; +entity("NestedLessLess") -> 16#0226A; +entity("NewLine") -> 16#0000A; +entity("Nfr") -> 16#1D511; +entity("NoBreak") -> 16#02060; +entity("NonBreakingSpace") -> 16#000A0; +entity("Nopf") -> 16#02115; +entity("Not") -> 16#02AEC; +entity("NotCongruent") -> 16#02262; +entity("NotCupCap") -> 16#0226D; +entity("NotDoubleVerticalBar") -> 16#02226; +entity("NotElement") -> 16#02209; +entity("NotEqual") -> 16#02260; +entity("NotEqualTilde") -> [16#02242, 16#00338]; +entity("NotExists") -> 16#02204; +entity("NotGreater") -> 16#0226F; +entity("NotGreaterEqual") -> 16#02271; +entity("NotGreaterFullEqual") -> [16#02267, 16#00338]; +entity("NotGreaterGreater") -> [16#0226B, 16#00338]; +entity("NotGreaterLess") -> 16#02279; +entity("NotGreaterSlantEqual") -> [16#02A7E, 16#00338]; +entity("NotGreaterTilde") -> 16#02275; +entity("NotHumpDownHump") -> [16#0224E, 16#00338]; +entity("NotHumpEqual") -> [16#0224F, 16#00338]; +entity("NotLeftTriangle") -> 16#022EA; +entity("NotLeftTriangleBar") -> [16#029CF, 16#00338]; +entity("NotLeftTriangleEqual") -> 16#022EC; +entity("NotLess") -> 16#0226E; +entity("NotLessEqual") -> 16#02270; +entity("NotLessGreater") -> 16#02278; +entity("NotLessLess") -> [16#0226A, 16#00338]; +entity("NotLessSlantEqual") -> [16#02A7D, 16#00338]; +entity("NotLessTilde") -> 16#02274; +entity("NotNestedGreaterGreater") -> [16#02AA2, 16#00338]; +entity("NotNestedLessLess") -> [16#02AA1, 16#00338]; +entity("NotPrecedes") -> 16#02280; +entity("NotPrecedesEqual") -> [16#02AAF, 16#00338]; +entity("NotPrecedesSlantEqual") -> 16#022E0; +entity("NotReverseElement") -> 16#0220C; +entity("NotRightTriangle") -> 16#022EB; +entity("NotRightTriangleBar") -> [16#029D0, 16#00338]; +entity("NotRightTriangleEqual") -> 16#022ED; +entity("NotSquareSubset") -> [16#0228F, 16#00338]; +entity("NotSquareSubsetEqual") -> 16#022E2; +entity("NotSquareSuperset") -> [16#02290, 16#00338]; +entity("NotSquareSupersetEqual") -> 16#022E3; +entity("NotSubset") -> [16#02282, 16#020D2]; +entity("NotSubsetEqual") -> 16#02288; +entity("NotSucceeds") -> 16#02281; +entity("NotSucceedsEqual") -> [16#02AB0, 16#00338]; +entity("NotSucceedsSlantEqual") -> 16#022E1; +entity("NotSucceedsTilde") -> [16#0227F, 16#00338]; +entity("NotSuperset") -> [16#02283, 16#020D2]; +entity("NotSupersetEqual") -> 16#02289; +entity("NotTilde") -> 16#02241; +entity("NotTildeEqual") -> 16#02244; +entity("NotTildeFullEqual") -> 16#02247; +entity("NotTildeTilde") -> 16#02249; +entity("NotVerticalBar") -> 16#02224; +entity("Nscr") -> 16#1D4A9; +entity("Ntilde") -> 16#000D1; +entity("Nu") -> 16#0039D; +entity("OElig") -> 16#00152; +entity("Oacute") -> 16#000D3; +entity("Ocirc") -> 16#000D4; +entity("Ocy") -> 16#0041E; +entity("Odblac") -> 16#00150; +entity("Ofr") -> 16#1D512; +entity("Ograve") -> 16#000D2; +entity("Omacr") -> 16#0014C; +entity("Omega") -> 16#003A9; +entity("Omicron") -> 16#0039F; +entity("Oopf") -> 16#1D546; +entity("OpenCurlyDoubleQuote") -> 16#0201C; +entity("OpenCurlyQuote") -> 16#02018; +entity("Or") -> 16#02A54; +entity("Oscr") -> 16#1D4AA; +entity("Oslash") -> 16#000D8; +entity("Otilde") -> 16#000D5; +entity("Otimes") -> 16#02A37; +entity("Ouml") -> 16#000D6; +entity("OverBar") -> 16#0203E; +entity("OverBrace") -> 16#023DE; +entity("OverBracket") -> 16#023B4; +entity("OverParenthesis") -> 16#023DC; +entity("PartialD") -> 16#02202; +entity("Pcy") -> 16#0041F; +entity("Pfr") -> 16#1D513; +entity("Phi") -> 16#003A6; +entity("Pi") -> 16#003A0; +entity("PlusMinus") -> 16#000B1; +entity("Poincareplane") -> 16#0210C; +entity("Popf") -> 16#02119; +entity("Pr") -> 16#02ABB; +entity("Precedes") -> 16#0227A; +entity("PrecedesEqual") -> 16#02AAF; +entity("PrecedesSlantEqual") -> 16#0227C; +entity("PrecedesTilde") -> 16#0227E; +entity("Prime") -> 16#02033; +entity("Product") -> 16#0220F; +entity("Proportion") -> 16#02237; +entity("Proportional") -> 16#0221D; +entity("Pscr") -> 16#1D4AB; +entity("Psi") -> 16#003A8; +entity("QUOT") -> 16#00022; +entity("Qfr") -> 16#1D514; +entity("Qopf") -> 16#0211A; +entity("Qscr") -> 16#1D4AC; +entity("RBarr") -> 16#02910; +entity("REG") -> 16#000AE; +entity("Racute") -> 16#00154; +entity("Rang") -> 16#027EB; +entity("Rarr") -> 16#021A0; +entity("Rarrtl") -> 16#02916; +entity("Rcaron") -> 16#00158; +entity("Rcedil") -> 16#00156; +entity("Rcy") -> 16#00420; +entity("Re") -> 16#0211C; +entity("ReverseElement") -> 16#0220B; +entity("ReverseEquilibrium") -> 16#021CB; +entity("ReverseUpEquilibrium") -> 16#0296F; +entity("Rfr") -> 16#0211C; +entity("Rho") -> 16#003A1; +entity("RightAngleBracket") -> 16#027E9; +entity("RightArrow") -> 16#02192; +entity("RightArrowBar") -> 16#021E5; +entity("RightArrowLeftArrow") -> 16#021C4; +entity("RightCeiling") -> 16#02309; +entity("RightDoubleBracket") -> 16#027E7; +entity("RightDownTeeVector") -> 16#0295D; +entity("RightDownVector") -> 16#021C2; +entity("RightDownVectorBar") -> 16#02955; +entity("RightFloor") -> 16#0230B; +entity("RightTee") -> 16#022A2; +entity("RightTeeArrow") -> 16#021A6; +entity("RightTeeVector") -> 16#0295B; +entity("RightTriangle") -> 16#022B3; +entity("RightTriangleBar") -> 16#029D0; +entity("RightTriangleEqual") -> 16#022B5; +entity("RightUpDownVector") -> 16#0294F; +entity("RightUpTeeVector") -> 16#0295C; +entity("RightUpVector") -> 16#021BE; +entity("RightUpVectorBar") -> 16#02954; +entity("RightVector") -> 16#021C0; +entity("RightVectorBar") -> 16#02953; +entity("Rightarrow") -> 16#021D2; +entity("Ropf") -> 16#0211D; +entity("RoundImplies") -> 16#02970; +entity("Rrightarrow") -> 16#021DB; +entity("Rscr") -> 16#0211B; +entity("Rsh") -> 16#021B1; +entity("RuleDelayed") -> 16#029F4; +entity("SHCHcy") -> 16#00429; +entity("SHcy") -> 16#00428; +entity("SOFTcy") -> 16#0042C; +entity("Sacute") -> 16#0015A; +entity("Sc") -> 16#02ABC; +entity("Scaron") -> 16#00160; +entity("Scedil") -> 16#0015E; +entity("Scirc") -> 16#0015C; +entity("Scy") -> 16#00421; +entity("Sfr") -> 16#1D516; +entity("ShortDownArrow") -> 16#02193; +entity("ShortLeftArrow") -> 16#02190; +entity("ShortRightArrow") -> 16#02192; +entity("ShortUpArrow") -> 16#02191; +entity("Sigma") -> 16#003A3; +entity("SmallCircle") -> 16#02218; +entity("Sopf") -> 16#1D54A; +entity("Sqrt") -> 16#0221A; +entity("Square") -> 16#025A1; +entity("SquareIntersection") -> 16#02293; +entity("SquareSubset") -> 16#0228F; +entity("SquareSubsetEqual") -> 16#02291; +entity("SquareSuperset") -> 16#02290; +entity("SquareSupersetEqual") -> 16#02292; +entity("SquareUnion") -> 16#02294; +entity("Sscr") -> 16#1D4AE; +entity("Star") -> 16#022C6; +entity("Sub") -> 16#022D0; +entity("Subset") -> 16#022D0; +entity("SubsetEqual") -> 16#02286; +entity("Succeeds") -> 16#0227B; +entity("SucceedsEqual") -> 16#02AB0; +entity("SucceedsSlantEqual") -> 16#0227D; +entity("SucceedsTilde") -> 16#0227F; +entity("SuchThat") -> 16#0220B; +entity("Sum") -> 16#02211; +entity("Sup") -> 16#022D1; +entity("Superset") -> 16#02283; +entity("SupersetEqual") -> 16#02287; +entity("Supset") -> 16#022D1; +entity("THORN") -> 16#000DE; +entity("TRADE") -> 16#02122; +entity("TSHcy") -> 16#0040B; +entity("TScy") -> 16#00426; +entity("Tab") -> 16#00009; +entity("Tau") -> 16#003A4; +entity("Tcaron") -> 16#00164; +entity("Tcedil") -> 16#00162; +entity("Tcy") -> 16#00422; +entity("Tfr") -> 16#1D517; +entity("Therefore") -> 16#02234; +entity("Theta") -> 16#00398; +entity("ThickSpace") -> [16#0205F, 16#0200A]; +entity("ThinSpace") -> 16#02009; +entity("Tilde") -> 16#0223C; +entity("TildeEqual") -> 16#02243; +entity("TildeFullEqual") -> 16#02245; +entity("TildeTilde") -> 16#02248; +entity("Topf") -> 16#1D54B; +entity("TripleDot") -> 16#020DB; +entity("Tscr") -> 16#1D4AF; +entity("Tstrok") -> 16#00166; +entity("Uacute") -> 16#000DA; +entity("Uarr") -> 16#0219F; +entity("Uarrocir") -> 16#02949; +entity("Ubrcy") -> 16#0040E; +entity("Ubreve") -> 16#0016C; +entity("Ucirc") -> 16#000DB; +entity("Ucy") -> 16#00423; +entity("Udblac") -> 16#00170; +entity("Ufr") -> 16#1D518; +entity("Ugrave") -> 16#000D9; +entity("Umacr") -> 16#0016A; +entity("UnderBar") -> 16#0005F; +entity("UnderBrace") -> 16#023DF; +entity("UnderBracket") -> 16#023B5; +entity("UnderParenthesis") -> 16#023DD; +entity("Union") -> 16#022C3; +entity("UnionPlus") -> 16#0228E; +entity("Uogon") -> 16#00172; +entity("Uopf") -> 16#1D54C; +entity("UpArrow") -> 16#02191; +entity("UpArrowBar") -> 16#02912; +entity("UpArrowDownArrow") -> 16#021C5; +entity("UpDownArrow") -> 16#02195; +entity("UpEquilibrium") -> 16#0296E; +entity("UpTee") -> 16#022A5; +entity("UpTeeArrow") -> 16#021A5; +entity("Uparrow") -> 16#021D1; +entity("Updownarrow") -> 16#021D5; +entity("UpperLeftArrow") -> 16#02196; +entity("UpperRightArrow") -> 16#02197; +entity("Upsi") -> 16#003D2; +entity("Upsilon") -> 16#003A5; +entity("Uring") -> 16#0016E; +entity("Uscr") -> 16#1D4B0; +entity("Utilde") -> 16#00168; +entity("Uuml") -> 16#000DC; +entity("VDash") -> 16#022AB; +entity("Vbar") -> 16#02AEB; +entity("Vcy") -> 16#00412; +entity("Vdash") -> 16#022A9; +entity("Vdashl") -> 16#02AE6; +entity("Vee") -> 16#022C1; +entity("Verbar") -> 16#02016; +entity("Vert") -> 16#02016; +entity("VerticalBar") -> 16#02223; +entity("VerticalLine") -> 16#0007C; +entity("VerticalSeparator") -> 16#02758; +entity("VerticalTilde") -> 16#02240; +entity("VeryThinSpace") -> 16#0200A; +entity("Vfr") -> 16#1D519; +entity("Vopf") -> 16#1D54D; +entity("Vscr") -> 16#1D4B1; +entity("Vvdash") -> 16#022AA; +entity("Wcirc") -> 16#00174; +entity("Wedge") -> 16#022C0; +entity("Wfr") -> 16#1D51A; +entity("Wopf") -> 16#1D54E; +entity("Wscr") -> 16#1D4B2; +entity("Xfr") -> 16#1D51B; +entity("Xi") -> 16#0039E; +entity("Xopf") -> 16#1D54F; +entity("Xscr") -> 16#1D4B3; +entity("YAcy") -> 16#0042F; +entity("YIcy") -> 16#00407; +entity("YUcy") -> 16#0042E; +entity("Yacute") -> 16#000DD; +entity("Ycirc") -> 16#00176; +entity("Ycy") -> 16#0042B; +entity("Yfr") -> 16#1D51C; +entity("Yopf") -> 16#1D550; +entity("Yscr") -> 16#1D4B4; +entity("Yuml") -> 16#00178; +entity("ZHcy") -> 16#00416; +entity("Zacute") -> 16#00179; +entity("Zcaron") -> 16#0017D; +entity("Zcy") -> 16#00417; +entity("Zdot") -> 16#0017B; +entity("ZeroWidthSpace") -> 16#0200B; +entity("Zeta") -> 16#00396; +entity("Zfr") -> 16#02128; +entity("Zopf") -> 16#02124; +entity("Zscr") -> 16#1D4B5; +entity("aacute") -> 16#000E1; +entity("abreve") -> 16#00103; +entity("ac") -> 16#0223E; +entity("acE") -> [16#0223E, 16#00333]; +entity("acd") -> 16#0223F; +entity("acirc") -> 16#000E2; +entity("acute") -> 16#000B4; +entity("acy") -> 16#00430; +entity("aelig") -> 16#000E6; +entity("af") -> 16#02061; +entity("afr") -> 16#1D51E; +entity("agrave") -> 16#000E0; +entity("alefsym") -> 16#02135; +entity("aleph") -> 16#02135; +entity("alpha") -> 16#003B1; +entity("amacr") -> 16#00101; +entity("amalg") -> 16#02A3F; +entity("amp") -> 16#00026; +entity("and") -> 16#02227; +entity("andand") -> 16#02A55; +entity("andd") -> 16#02A5C; +entity("andslope") -> 16#02A58; +entity("andv") -> 16#02A5A; +entity("ang") -> 16#02220; +entity("ange") -> 16#029A4; +entity("angle") -> 16#02220; +entity("angmsd") -> 16#02221; +entity("angmsdaa") -> 16#029A8; +entity("angmsdab") -> 16#029A9; +entity("angmsdac") -> 16#029AA; +entity("angmsdad") -> 16#029AB; +entity("angmsdae") -> 16#029AC; +entity("angmsdaf") -> 16#029AD; +entity("angmsdag") -> 16#029AE; +entity("angmsdah") -> 16#029AF; +entity("angrt") -> 16#0221F; +entity("angrtvb") -> 16#022BE; +entity("angrtvbd") -> 16#0299D; +entity("angsph") -> 16#02222; +entity("angst") -> 16#000C5; +entity("angzarr") -> 16#0237C; +entity("aogon") -> 16#00105; +entity("aopf") -> 16#1D552; +entity("ap") -> 16#02248; +entity("apE") -> 16#02A70; +entity("apacir") -> 16#02A6F; +entity("ape") -> 16#0224A; +entity("apid") -> 16#0224B; +entity("apos") -> 16#00027; +entity("approx") -> 16#02248; +entity("approxeq") -> 16#0224A; +entity("aring") -> 16#000E5; +entity("ascr") -> 16#1D4B6; +entity("ast") -> 16#0002A; +entity("asymp") -> 16#02248; +entity("asympeq") -> 16#0224D; +entity("atilde") -> 16#000E3; +entity("auml") -> 16#000E4; +entity("awconint") -> 16#02233; +entity("awint") -> 16#02A11; +entity("bNot") -> 16#02AED; +entity("backcong") -> 16#0224C; +entity("backepsilon") -> 16#003F6; +entity("backprime") -> 16#02035; +entity("backsim") -> 16#0223D; +entity("backsimeq") -> 16#022CD; +entity("barvee") -> 16#022BD; +entity("barwed") -> 16#02305; +entity("barwedge") -> 16#02305; +entity("bbrk") -> 16#023B5; +entity("bbrktbrk") -> 16#023B6; +entity("bcong") -> 16#0224C; +entity("bcy") -> 16#00431; +entity("bdquo") -> 16#0201E; +entity("becaus") -> 16#02235; +entity("because") -> 16#02235; +entity("bemptyv") -> 16#029B0; +entity("bepsi") -> 16#003F6; +entity("bernou") -> 16#0212C; +entity("beta") -> 16#003B2; +entity("beth") -> 16#02136; +entity("between") -> 16#0226C; +entity("bfr") -> 16#1D51F; +entity("bigcap") -> 16#022C2; +entity("bigcirc") -> 16#025EF; +entity("bigcup") -> 16#022C3; +entity("bigodot") -> 16#02A00; +entity("bigoplus") -> 16#02A01; +entity("bigotimes") -> 16#02A02; +entity("bigsqcup") -> 16#02A06; +entity("bigstar") -> 16#02605; +entity("bigtriangledown") -> 16#025BD; +entity("bigtriangleup") -> 16#025B3; +entity("biguplus") -> 16#02A04; +entity("bigvee") -> 16#022C1; +entity("bigwedge") -> 16#022C0; +entity("bkarow") -> 16#0290D; +entity("blacklozenge") -> 16#029EB; +entity("blacksquare") -> 16#025AA; +entity("blacktriangle") -> 16#025B4; +entity("blacktriangledown") -> 16#025BE; +entity("blacktriangleleft") -> 16#025C2; +entity("blacktriangleright") -> 16#025B8; +entity("blank") -> 16#02423; +entity("blk12") -> 16#02592; +entity("blk14") -> 16#02591; +entity("blk34") -> 16#02593; +entity("block") -> 16#02588; +entity("bne") -> [16#0003D, 16#020E5]; +entity("bnequiv") -> [16#02261, 16#020E5]; +entity("bnot") -> 16#02310; +entity("bopf") -> 16#1D553; +entity("bot") -> 16#022A5; +entity("bottom") -> 16#022A5; +entity("bowtie") -> 16#022C8; +entity("boxDL") -> 16#02557; +entity("boxDR") -> 16#02554; +entity("boxDl") -> 16#02556; +entity("boxDr") -> 16#02553; +entity("boxH") -> 16#02550; +entity("boxHD") -> 16#02566; +entity("boxHU") -> 16#02569; +entity("boxHd") -> 16#02564; +entity("boxHu") -> 16#02567; +entity("boxUL") -> 16#0255D; +entity("boxUR") -> 16#0255A; +entity("boxUl") -> 16#0255C; +entity("boxUr") -> 16#02559; +entity("boxV") -> 16#02551; +entity("boxVH") -> 16#0256C; +entity("boxVL") -> 16#02563; +entity("boxVR") -> 16#02560; +entity("boxVh") -> 16#0256B; +entity("boxVl") -> 16#02562; +entity("boxVr") -> 16#0255F; +entity("boxbox") -> 16#029C9; +entity("boxdL") -> 16#02555; +entity("boxdR") -> 16#02552; +entity("boxdl") -> 16#02510; +entity("boxdr") -> 16#0250C; +entity("boxh") -> 16#02500; +entity("boxhD") -> 16#02565; +entity("boxhU") -> 16#02568; +entity("boxhd") -> 16#0252C; +entity("boxhu") -> 16#02534; +entity("boxminus") -> 16#0229F; +entity("boxplus") -> 16#0229E; +entity("boxtimes") -> 16#022A0; +entity("boxuL") -> 16#0255B; +entity("boxuR") -> 16#02558; +entity("boxul") -> 16#02518; +entity("boxur") -> 16#02514; +entity("boxv") -> 16#02502; +entity("boxvH") -> 16#0256A; +entity("boxvL") -> 16#02561; +entity("boxvR") -> 16#0255E; +entity("boxvh") -> 16#0253C; +entity("boxvl") -> 16#02524; +entity("boxvr") -> 16#0251C; +entity("bprime") -> 16#02035; +entity("breve") -> 16#002D8; +entity("brvbar") -> 16#000A6; +entity("bscr") -> 16#1D4B7; +entity("bsemi") -> 16#0204F; +entity("bsim") -> 16#0223D; +entity("bsime") -> 16#022CD; +entity("bsol") -> 16#0005C; +entity("bsolb") -> 16#029C5; +entity("bsolhsub") -> 16#027C8; +entity("bull") -> 16#02022; +entity("bullet") -> 16#02022; +entity("bump") -> 16#0224E; +entity("bumpE") -> 16#02AAE; +entity("bumpe") -> 16#0224F; +entity("bumpeq") -> 16#0224F; +entity("cacute") -> 16#00107; +entity("cap") -> 16#02229; +entity("capand") -> 16#02A44; +entity("capbrcup") -> 16#02A49; +entity("capcap") -> 16#02A4B; +entity("capcup") -> 16#02A47; +entity("capdot") -> 16#02A40; +entity("caps") -> [16#02229, 16#0FE00]; +entity("caret") -> 16#02041; +entity("caron") -> 16#002C7; +entity("ccaps") -> 16#02A4D; +entity("ccaron") -> 16#0010D; +entity("ccedil") -> 16#000E7; +entity("ccirc") -> 16#00109; +entity("ccups") -> 16#02A4C; +entity("ccupssm") -> 16#02A50; +entity("cdot") -> 16#0010B; +entity("cedil") -> 16#000B8; +entity("cemptyv") -> 16#029B2; +entity("cent") -> 16#000A2; +entity("centerdot") -> 16#000B7; +entity("cfr") -> 16#1D520; +entity("chcy") -> 16#00447; +entity("check") -> 16#02713; +entity("checkmark") -> 16#02713; +entity("chi") -> 16#003C7; +entity("cir") -> 16#025CB; +entity("cirE") -> 16#029C3; +entity("circ") -> 16#002C6; +entity("circeq") -> 16#02257; +entity("circlearrowleft") -> 16#021BA; +entity("circlearrowright") -> 16#021BB; +entity("circledR") -> 16#000AE; +entity("circledS") -> 16#024C8; +entity("circledast") -> 16#0229B; +entity("circledcirc") -> 16#0229A; +entity("circleddash") -> 16#0229D; +entity("cire") -> 16#02257; +entity("cirfnint") -> 16#02A10; +entity("cirmid") -> 16#02AEF; +entity("cirscir") -> 16#029C2; +entity("clubs") -> 16#02663; +entity("clubsuit") -> 16#02663; +entity("colon") -> 16#0003A; +entity("colone") -> 16#02254; +entity("coloneq") -> 16#02254; +entity("comma") -> 16#0002C; +entity("commat") -> 16#00040; +entity("comp") -> 16#02201; +entity("compfn") -> 16#02218; +entity("complement") -> 16#02201; +entity("complexes") -> 16#02102; +entity("cong") -> 16#02245; +entity("congdot") -> 16#02A6D; +entity("conint") -> 16#0222E; +entity("copf") -> 16#1D554; +entity("coprod") -> 16#02210; +entity("copy") -> 16#000A9; +entity("copysr") -> 16#02117; +entity("crarr") -> 16#021B5; +entity("cross") -> 16#02717; +entity("cscr") -> 16#1D4B8; +entity("csub") -> 16#02ACF; +entity("csube") -> 16#02AD1; +entity("csup") -> 16#02AD0; +entity("csupe") -> 16#02AD2; +entity("ctdot") -> 16#022EF; +entity("cudarrl") -> 16#02938; +entity("cudarrr") -> 16#02935; +entity("cuepr") -> 16#022DE; +entity("cuesc") -> 16#022DF; +entity("cularr") -> 16#021B6; +entity("cularrp") -> 16#0293D; +entity("cup") -> 16#0222A; +entity("cupbrcap") -> 16#02A48; +entity("cupcap") -> 16#02A46; +entity("cupcup") -> 16#02A4A; +entity("cupdot") -> 16#0228D; +entity("cupor") -> 16#02A45; +entity("cups") -> [16#0222A, 16#0FE00]; +entity("curarr") -> 16#021B7; +entity("curarrm") -> 16#0293C; +entity("curlyeqprec") -> 16#022DE; +entity("curlyeqsucc") -> 16#022DF; +entity("curlyvee") -> 16#022CE; +entity("curlywedge") -> 16#022CF; +entity("curren") -> 16#000A4; +entity("curvearrowleft") -> 16#021B6; +entity("curvearrowright") -> 16#021B7; +entity("cuvee") -> 16#022CE; +entity("cuwed") -> 16#022CF; +entity("cwconint") -> 16#02232; +entity("cwint") -> 16#02231; +entity("cylcty") -> 16#0232D; +entity("dArr") -> 16#021D3; +entity("dHar") -> 16#02965; +entity("dagger") -> 16#02020; +entity("daleth") -> 16#02138; +entity("darr") -> 16#02193; +entity("dash") -> 16#02010; +entity("dashv") -> 16#022A3; +entity("dbkarow") -> 16#0290F; +entity("dblac") -> 16#002DD; +entity("dcaron") -> 16#0010F; +entity("dcy") -> 16#00434; +entity("dd") -> 16#02146; +entity("ddagger") -> 16#02021; +entity("ddarr") -> 16#021CA; +entity("ddotseq") -> 16#02A77; +entity("deg") -> 16#000B0; +entity("delta") -> 16#003B4; +entity("demptyv") -> 16#029B1; +entity("dfisht") -> 16#0297F; +entity("dfr") -> 16#1D521; +entity("dharl") -> 16#021C3; +entity("dharr") -> 16#021C2; +entity("diam") -> 16#022C4; +entity("diamond") -> 16#022C4; +entity("diamondsuit") -> 16#02666; +entity("diams") -> 16#02666; +entity("die") -> 16#000A8; +entity("digamma") -> 16#003DD; +entity("disin") -> 16#022F2; +entity("div") -> 16#000F7; +entity("divide") -> 16#000F7; +entity("divideontimes") -> 16#022C7; +entity("divonx") -> 16#022C7; +entity("djcy") -> 16#00452; +entity("dlcorn") -> 16#0231E; +entity("dlcrop") -> 16#0230D; +entity("dollar") -> 16#00024; +entity("dopf") -> 16#1D555; +entity("dot") -> 16#002D9; +entity("doteq") -> 16#02250; +entity("doteqdot") -> 16#02251; +entity("dotminus") -> 16#02238; +entity("dotplus") -> 16#02214; +entity("dotsquare") -> 16#022A1; +entity("doublebarwedge") -> 16#02306; +entity("downarrow") -> 16#02193; +entity("downdownarrows") -> 16#021CA; +entity("downharpoonleft") -> 16#021C3; +entity("downharpoonright") -> 16#021C2; +entity("drbkarow") -> 16#02910; +entity("drcorn") -> 16#0231F; +entity("drcrop") -> 16#0230C; +entity("dscr") -> 16#1D4B9; +entity("dscy") -> 16#00455; +entity("dsol") -> 16#029F6; +entity("dstrok") -> 16#00111; +entity("dtdot") -> 16#022F1; +entity("dtri") -> 16#025BF; +entity("dtrif") -> 16#025BE; +entity("duarr") -> 16#021F5; +entity("duhar") -> 16#0296F; +entity("dwangle") -> 16#029A6; +entity("dzcy") -> 16#0045F; +entity("dzigrarr") -> 16#027FF; +entity("eDDot") -> 16#02A77; +entity("eDot") -> 16#02251; +entity("eacute") -> 16#000E9; +entity("easter") -> 16#02A6E; +entity("ecaron") -> 16#0011B; +entity("ecir") -> 16#02256; +entity("ecirc") -> 16#000EA; +entity("ecolon") -> 16#02255; +entity("ecy") -> 16#0044D; +entity("edot") -> 16#00117; +entity("ee") -> 16#02147; +entity("efDot") -> 16#02252; +entity("efr") -> 16#1D522; +entity("eg") -> 16#02A9A; +entity("egrave") -> 16#000E8; +entity("egs") -> 16#02A96; +entity("egsdot") -> 16#02A98; +entity("el") -> 16#02A99; +entity("elinters") -> 16#023E7; +entity("ell") -> 16#02113; +entity("els") -> 16#02A95; +entity("elsdot") -> 16#02A97; +entity("emacr") -> 16#00113; +entity("empty") -> 16#02205; +entity("emptyset") -> 16#02205; +entity("emptyv") -> 16#02205; +entity("emsp") -> 16#02003; +entity("emsp13") -> 16#02004; +entity("emsp14") -> 16#02005; +entity("eng") -> 16#0014B; +entity("ensp") -> 16#02002; +entity("eogon") -> 16#00119; +entity("eopf") -> 16#1D556; +entity("epar") -> 16#022D5; +entity("eparsl") -> 16#029E3; +entity("eplus") -> 16#02A71; +entity("epsi") -> 16#003B5; +entity("epsilon") -> 16#003B5; +entity("epsiv") -> 16#003F5; +entity("eqcirc") -> 16#02256; +entity("eqcolon") -> 16#02255; +entity("eqsim") -> 16#02242; +entity("eqslantgtr") -> 16#02A96; +entity("eqslantless") -> 16#02A95; +entity("equals") -> 16#0003D; +entity("equest") -> 16#0225F; +entity("equiv") -> 16#02261; +entity("equivDD") -> 16#02A78; +entity("eqvparsl") -> 16#029E5; +entity("erDot") -> 16#02253; +entity("erarr") -> 16#02971; +entity("escr") -> 16#0212F; +entity("esdot") -> 16#02250; +entity("esim") -> 16#02242; +entity("eta") -> 16#003B7; +entity("eth") -> 16#000F0; +entity("euml") -> 16#000EB; +entity("euro") -> 16#020AC; +entity("excl") -> 16#00021; +entity("exist") -> 16#02203; +entity("expectation") -> 16#02130; +entity("exponentiale") -> 16#02147; +entity("fallingdotseq") -> 16#02252; +entity("fcy") -> 16#00444; +entity("female") -> 16#02640; +entity("ffilig") -> 16#0FB03; +entity("fflig") -> 16#0FB00; +entity("ffllig") -> 16#0FB04; +entity("ffr") -> 16#1D523; +entity("filig") -> 16#0FB01; +entity("fjlig") -> [16#00066, 16#0006A]; +entity("flat") -> 16#0266D; +entity("fllig") -> 16#0FB02; +entity("fltns") -> 16#025B1; +entity("fnof") -> 16#00192; +entity("fopf") -> 16#1D557; +entity("forall") -> 16#02200; +entity("fork") -> 16#022D4; +entity("forkv") -> 16#02AD9; +entity("fpartint") -> 16#02A0D; +entity("frac12") -> 16#000BD; +entity("frac13") -> 16#02153; +entity("frac14") -> 16#000BC; +entity("frac15") -> 16#02155; +entity("frac16") -> 16#02159; +entity("frac18") -> 16#0215B; +entity("frac23") -> 16#02154; +entity("frac25") -> 16#02156; +entity("frac34") -> 16#000BE; +entity("frac35") -> 16#02157; +entity("frac38") -> 16#0215C; +entity("frac45") -> 16#02158; +entity("frac56") -> 16#0215A; +entity("frac58") -> 16#0215D; +entity("frac78") -> 16#0215E; +entity("frasl") -> 16#02044; +entity("frown") -> 16#02322; +entity("fscr") -> 16#1D4BB; +entity("gE") -> 16#02267; +entity("gEl") -> 16#02A8C; +entity("gacute") -> 16#001F5; +entity("gamma") -> 16#003B3; +entity("gammad") -> 16#003DD; +entity("gap") -> 16#02A86; +entity("gbreve") -> 16#0011F; +entity("gcirc") -> 16#0011D; +entity("gcy") -> 16#00433; +entity("gdot") -> 16#00121; +entity("ge") -> 16#02265; +entity("gel") -> 16#022DB; +entity("geq") -> 16#02265; +entity("geqq") -> 16#02267; +entity("geqslant") -> 16#02A7E; +entity("ges") -> 16#02A7E; +entity("gescc") -> 16#02AA9; +entity("gesdot") -> 16#02A80; +entity("gesdoto") -> 16#02A82; +entity("gesdotol") -> 16#02A84; +entity("gesl") -> [16#022DB, 16#0FE00]; +entity("gesles") -> 16#02A94; +entity("gfr") -> 16#1D524; +entity("gg") -> 16#0226B; +entity("ggg") -> 16#022D9; +entity("gimel") -> 16#02137; +entity("gjcy") -> 16#00453; +entity("gl") -> 16#02277; +entity("glE") -> 16#02A92; +entity("gla") -> 16#02AA5; +entity("glj") -> 16#02AA4; +entity("gnE") -> 16#02269; +entity("gnap") -> 16#02A8A; +entity("gnapprox") -> 16#02A8A; +entity("gne") -> 16#02A88; +entity("gneq") -> 16#02A88; +entity("gneqq") -> 16#02269; +entity("gnsim") -> 16#022E7; +entity("gopf") -> 16#1D558; +entity("grave") -> 16#00060; +entity("gscr") -> 16#0210A; +entity("gsim") -> 16#02273; +entity("gsime") -> 16#02A8E; +entity("gsiml") -> 16#02A90; +entity("gt") -> 16#0003E; +entity("gtcc") -> 16#02AA7; +entity("gtcir") -> 16#02A7A; +entity("gtdot") -> 16#022D7; +entity("gtlPar") -> 16#02995; +entity("gtquest") -> 16#02A7C; +entity("gtrapprox") -> 16#02A86; +entity("gtrarr") -> 16#02978; +entity("gtrdot") -> 16#022D7; +entity("gtreqless") -> 16#022DB; +entity("gtreqqless") -> 16#02A8C; +entity("gtrless") -> 16#02277; +entity("gtrsim") -> 16#02273; +entity("gvertneqq") -> [16#02269, 16#0FE00]; +entity("gvnE") -> [16#02269, 16#0FE00]; +entity("hArr") -> 16#021D4; +entity("hairsp") -> 16#0200A; +entity("half") -> 16#000BD; +entity("hamilt") -> 16#0210B; +entity("hardcy") -> 16#0044A; +entity("harr") -> 16#02194; +entity("harrcir") -> 16#02948; +entity("harrw") -> 16#021AD; +entity("hbar") -> 16#0210F; +entity("hcirc") -> 16#00125; +entity("hearts") -> 16#02665; +entity("heartsuit") -> 16#02665; +entity("hellip") -> 16#02026; +entity("hercon") -> 16#022B9; +entity("hfr") -> 16#1D525; +entity("hksearow") -> 16#02925; +entity("hkswarow") -> 16#02926; +entity("hoarr") -> 16#021FF; +entity("homtht") -> 16#0223B; +entity("hookleftarrow") -> 16#021A9; +entity("hookrightarrow") -> 16#021AA; +entity("hopf") -> 16#1D559; +entity("horbar") -> 16#02015; +entity("hscr") -> 16#1D4BD; +entity("hslash") -> 16#0210F; +entity("hstrok") -> 16#00127; +entity("hybull") -> 16#02043; +entity("hyphen") -> 16#02010; +entity("iacute") -> 16#000ED; +entity("ic") -> 16#02063; +entity("icirc") -> 16#000EE; +entity("icy") -> 16#00438; +entity("iecy") -> 16#00435; +entity("iexcl") -> 16#000A1; +entity("iff") -> 16#021D4; +entity("ifr") -> 16#1D526; +entity("igrave") -> 16#000EC; +entity("ii") -> 16#02148; +entity("iiiint") -> 16#02A0C; +entity("iiint") -> 16#0222D; +entity("iinfin") -> 16#029DC; +entity("iiota") -> 16#02129; +entity("ijlig") -> 16#00133; +entity("imacr") -> 16#0012B; +entity("image") -> 16#02111; +entity("imagline") -> 16#02110; +entity("imagpart") -> 16#02111; +entity("imath") -> 16#00131; +entity("imof") -> 16#022B7; +entity("imped") -> 16#001B5; +entity("in") -> 16#02208; +entity("incare") -> 16#02105; +entity("infin") -> 16#0221E; +entity("infintie") -> 16#029DD; +entity("inodot") -> 16#00131; +entity("int") -> 16#0222B; +entity("intcal") -> 16#022BA; +entity("integers") -> 16#02124; +entity("intercal") -> 16#022BA; +entity("intlarhk") -> 16#02A17; +entity("intprod") -> 16#02A3C; +entity("iocy") -> 16#00451; +entity("iogon") -> 16#0012F; +entity("iopf") -> 16#1D55A; +entity("iota") -> 16#003B9; +entity("iprod") -> 16#02A3C; +entity("iquest") -> 16#000BF; +entity("iscr") -> 16#1D4BE; +entity("isin") -> 16#02208; +entity("isinE") -> 16#022F9; +entity("isindot") -> 16#022F5; +entity("isins") -> 16#022F4; +entity("isinsv") -> 16#022F3; +entity("isinv") -> 16#02208; +entity("it") -> 16#02062; +entity("itilde") -> 16#00129; +entity("iukcy") -> 16#00456; +entity("iuml") -> 16#000EF; +entity("jcirc") -> 16#00135; +entity("jcy") -> 16#00439; +entity("jfr") -> 16#1D527; +entity("jmath") -> 16#00237; +entity("jopf") -> 16#1D55B; +entity("jscr") -> 16#1D4BF; +entity("jsercy") -> 16#00458; +entity("jukcy") -> 16#00454; +entity("kappa") -> 16#003BA; +entity("kappav") -> 16#003F0; +entity("kcedil") -> 16#00137; +entity("kcy") -> 16#0043A; +entity("kfr") -> 16#1D528; +entity("kgreen") -> 16#00138; +entity("khcy") -> 16#00445; +entity("kjcy") -> 16#0045C; +entity("kopf") -> 16#1D55C; +entity("kscr") -> 16#1D4C0; +entity("lAarr") -> 16#021DA; +entity("lArr") -> 16#021D0; +entity("lAtail") -> 16#0291B; +entity("lBarr") -> 16#0290E; +entity("lE") -> 16#02266; +entity("lEg") -> 16#02A8B; +entity("lHar") -> 16#02962; +entity("lacute") -> 16#0013A; +entity("laemptyv") -> 16#029B4; +entity("lagran") -> 16#02112; +entity("lambda") -> 16#003BB; +entity("lang") -> 16#027E8; +entity("langd") -> 16#02991; +entity("langle") -> 16#027E8; +entity("lap") -> 16#02A85; +entity("laquo") -> 16#000AB; +entity("larr") -> 16#02190; +entity("larrb") -> 16#021E4; +entity("larrbfs") -> 16#0291F; +entity("larrfs") -> 16#0291D; +entity("larrhk") -> 16#021A9; +entity("larrlp") -> 16#021AB; +entity("larrpl") -> 16#02939; +entity("larrsim") -> 16#02973; +entity("larrtl") -> 16#021A2; +entity("lat") -> 16#02AAB; +entity("latail") -> 16#02919; +entity("late") -> 16#02AAD; +entity("lates") -> [16#02AAD, 16#0FE00]; +entity("lbarr") -> 16#0290C; +entity("lbbrk") -> 16#02772; +entity("lbrace") -> 16#0007B; +entity("lbrack") -> 16#0005B; +entity("lbrke") -> 16#0298B; +entity("lbrksld") -> 16#0298F; +entity("lbrkslu") -> 16#0298D; +entity("lcaron") -> 16#0013E; +entity("lcedil") -> 16#0013C; +entity("lceil") -> 16#02308; +entity("lcub") -> 16#0007B; +entity("lcy") -> 16#0043B; +entity("ldca") -> 16#02936; +entity("ldquo") -> 16#0201C; +entity("ldquor") -> 16#0201E; +entity("ldrdhar") -> 16#02967; +entity("ldrushar") -> 16#0294B; +entity("ldsh") -> 16#021B2; +entity("le") -> 16#02264; +entity("leftarrow") -> 16#02190; +entity("leftarrowtail") -> 16#021A2; +entity("leftharpoondown") -> 16#021BD; +entity("leftharpoonup") -> 16#021BC; +entity("leftleftarrows") -> 16#021C7; +entity("leftrightarrow") -> 16#02194; +entity("leftrightarrows") -> 16#021C6; +entity("leftrightharpoons") -> 16#021CB; +entity("leftrightsquigarrow") -> 16#021AD; +entity("leftthreetimes") -> 16#022CB; +entity("leg") -> 16#022DA; +entity("leq") -> 16#02264; +entity("leqq") -> 16#02266; +entity("leqslant") -> 16#02A7D; +entity("les") -> 16#02A7D; +entity("lescc") -> 16#02AA8; +entity("lesdot") -> 16#02A7F; +entity("lesdoto") -> 16#02A81; +entity("lesdotor") -> 16#02A83; +entity("lesg") -> [16#022DA, 16#0FE00]; +entity("lesges") -> 16#02A93; +entity("lessapprox") -> 16#02A85; +entity("lessdot") -> 16#022D6; +entity("lesseqgtr") -> 16#022DA; +entity("lesseqqgtr") -> 16#02A8B; +entity("lessgtr") -> 16#02276; +entity("lesssim") -> 16#02272; +entity("lfisht") -> 16#0297C; +entity("lfloor") -> 16#0230A; +entity("lfr") -> 16#1D529; +entity("lg") -> 16#02276; +entity("lgE") -> 16#02A91; +entity("lhard") -> 16#021BD; +entity("lharu") -> 16#021BC; +entity("lharul") -> 16#0296A; +entity("lhblk") -> 16#02584; +entity("ljcy") -> 16#00459; +entity("ll") -> 16#0226A; +entity("llarr") -> 16#021C7; +entity("llcorner") -> 16#0231E; +entity("llhard") -> 16#0296B; +entity("lltri") -> 16#025FA; +entity("lmidot") -> 16#00140; +entity("lmoust") -> 16#023B0; +entity("lmoustache") -> 16#023B0; +entity("lnE") -> 16#02268; +entity("lnap") -> 16#02A89; +entity("lnapprox") -> 16#02A89; +entity("lne") -> 16#02A87; +entity("lneq") -> 16#02A87; +entity("lneqq") -> 16#02268; +entity("lnsim") -> 16#022E6; +entity("loang") -> 16#027EC; +entity("loarr") -> 16#021FD; +entity("lobrk") -> 16#027E6; +entity("longleftarrow") -> 16#027F5; +entity("longleftrightarrow") -> 16#027F7; +entity("longmapsto") -> 16#027FC; +entity("longrightarrow") -> 16#027F6; +entity("looparrowleft") -> 16#021AB; +entity("looparrowright") -> 16#021AC; +entity("lopar") -> 16#02985; +entity("lopf") -> 16#1D55D; +entity("loplus") -> 16#02A2D; +entity("lotimes") -> 16#02A34; +entity("lowast") -> 16#02217; +entity("lowbar") -> 16#0005F; +entity("loz") -> 16#025CA; +entity("lozenge") -> 16#025CA; +entity("lozf") -> 16#029EB; +entity("lpar") -> 16#00028; +entity("lparlt") -> 16#02993; +entity("lrarr") -> 16#021C6; +entity("lrcorner") -> 16#0231F; +entity("lrhar") -> 16#021CB; +entity("lrhard") -> 16#0296D; +entity("lrm") -> 16#0200E; +entity("lrtri") -> 16#022BF; +entity("lsaquo") -> 16#02039; +entity("lscr") -> 16#1D4C1; +entity("lsh") -> 16#021B0; +entity("lsim") -> 16#02272; +entity("lsime") -> 16#02A8D; +entity("lsimg") -> 16#02A8F; +entity("lsqb") -> 16#0005B; +entity("lsquo") -> 16#02018; +entity("lsquor") -> 16#0201A; +entity("lstrok") -> 16#00142; +entity("lt") -> 16#0003C; +entity("ltcc") -> 16#02AA6; +entity("ltcir") -> 16#02A79; +entity("ltdot") -> 16#022D6; +entity("lthree") -> 16#022CB; +entity("ltimes") -> 16#022C9; +entity("ltlarr") -> 16#02976; +entity("ltquest") -> 16#02A7B; +entity("ltrPar") -> 16#02996; +entity("ltri") -> 16#025C3; +entity("ltrie") -> 16#022B4; +entity("ltrif") -> 16#025C2; +entity("lurdshar") -> 16#0294A; +entity("luruhar") -> 16#02966; +entity("lvertneqq") -> [16#02268, 16#0FE00]; +entity("lvnE") -> [16#02268, 16#0FE00]; +entity("mDDot") -> 16#0223A; +entity("macr") -> 16#000AF; +entity("male") -> 16#02642; +entity("malt") -> 16#02720; +entity("maltese") -> 16#02720; +entity("map") -> 16#021A6; +entity("mapsto") -> 16#021A6; +entity("mapstodown") -> 16#021A7; +entity("mapstoleft") -> 16#021A4; +entity("mapstoup") -> 16#021A5; +entity("marker") -> 16#025AE; +entity("mcomma") -> 16#02A29; +entity("mcy") -> 16#0043C; +entity("mdash") -> 16#02014; +entity("measuredangle") -> 16#02221; +entity("mfr") -> 16#1D52A; +entity("mho") -> 16#02127; +entity("micro") -> 16#000B5; +entity("mid") -> 16#02223; +entity("midast") -> 16#0002A; +entity("midcir") -> 16#02AF0; +entity("middot") -> 16#000B7; +entity("minus") -> 16#02212; +entity("minusb") -> 16#0229F; +entity("minusd") -> 16#02238; +entity("minusdu") -> 16#02A2A; +entity("mlcp") -> 16#02ADB; +entity("mldr") -> 16#02026; +entity("mnplus") -> 16#02213; +entity("models") -> 16#022A7; +entity("mopf") -> 16#1D55E; +entity("mp") -> 16#02213; +entity("mscr") -> 16#1D4C2; +entity("mstpos") -> 16#0223E; +entity("mu") -> 16#003BC; +entity("multimap") -> 16#022B8; +entity("mumap") -> 16#022B8; +entity("nGg") -> [16#022D9, 16#00338]; +entity("nGt") -> [16#0226B, 16#020D2]; +entity("nGtv") -> [16#0226B, 16#00338]; +entity("nLeftarrow") -> 16#021CD; +entity("nLeftrightarrow") -> 16#021CE; +entity("nLl") -> [16#022D8, 16#00338]; +entity("nLt") -> [16#0226A, 16#020D2]; +entity("nLtv") -> [16#0226A, 16#00338]; +entity("nRightarrow") -> 16#021CF; +entity("nVDash") -> 16#022AF; +entity("nVdash") -> 16#022AE; +entity("nabla") -> 16#02207; +entity("nacute") -> 16#00144; +entity("nang") -> [16#02220, 16#020D2]; +entity("nap") -> 16#02249; +entity("napE") -> [16#02A70, 16#00338]; +entity("napid") -> [16#0224B, 16#00338]; +entity("napos") -> 16#00149; +entity("napprox") -> 16#02249; +entity("natur") -> 16#0266E; +entity("natural") -> 16#0266E; +entity("naturals") -> 16#02115; +entity("nbsp") -> 16#000A0; +entity("nbump") -> [16#0224E, 16#00338]; +entity("nbumpe") -> [16#0224F, 16#00338]; +entity("ncap") -> 16#02A43; +entity("ncaron") -> 16#00148; +entity("ncedil") -> 16#00146; +entity("ncong") -> 16#02247; +entity("ncongdot") -> [16#02A6D, 16#00338]; +entity("ncup") -> 16#02A42; +entity("ncy") -> 16#0043D; +entity("ndash") -> 16#02013; +entity("ne") -> 16#02260; +entity("neArr") -> 16#021D7; +entity("nearhk") -> 16#02924; +entity("nearr") -> 16#02197; +entity("nearrow") -> 16#02197; +entity("nedot") -> [16#02250, 16#00338]; +entity("nequiv") -> 16#02262; +entity("nesear") -> 16#02928; +entity("nesim") -> [16#02242, 16#00338]; +entity("nexist") -> 16#02204; +entity("nexists") -> 16#02204; +entity("nfr") -> 16#1D52B; +entity("ngE") -> [16#02267, 16#00338]; +entity("nge") -> 16#02271; +entity("ngeq") -> 16#02271; +entity("ngeqq") -> [16#02267, 16#00338]; +entity("ngeqslant") -> [16#02A7E, 16#00338]; +entity("nges") -> [16#02A7E, 16#00338]; +entity("ngsim") -> 16#02275; +entity("ngt") -> 16#0226F; +entity("ngtr") -> 16#0226F; +entity("nhArr") -> 16#021CE; +entity("nharr") -> 16#021AE; +entity("nhpar") -> 16#02AF2; +entity("ni") -> 16#0220B; +entity("nis") -> 16#022FC; +entity("nisd") -> 16#022FA; +entity("niv") -> 16#0220B; +entity("njcy") -> 16#0045A; +entity("nlArr") -> 16#021CD; +entity("nlE") -> [16#02266, 16#00338]; +entity("nlarr") -> 16#0219A; +entity("nldr") -> 16#02025; +entity("nle") -> 16#02270; +entity("nleftarrow") -> 16#0219A; +entity("nleftrightarrow") -> 16#021AE; +entity("nleq") -> 16#02270; +entity("nleqq") -> [16#02266, 16#00338]; +entity("nleqslant") -> [16#02A7D, 16#00338]; +entity("nles") -> [16#02A7D, 16#00338]; +entity("nless") -> 16#0226E; +entity("nlsim") -> 16#02274; +entity("nlt") -> 16#0226E; +entity("nltri") -> 16#022EA; +entity("nltrie") -> 16#022EC; +entity("nmid") -> 16#02224; +entity("nopf") -> 16#1D55F; +entity("not") -> 16#000AC; +entity("notin") -> 16#02209; +entity("notinE") -> [16#022F9, 16#00338]; +entity("notindot") -> [16#022F5, 16#00338]; +entity("notinva") -> 16#02209; +entity("notinvb") -> 16#022F7; +entity("notinvc") -> 16#022F6; +entity("notni") -> 16#0220C; +entity("notniva") -> 16#0220C; +entity("notnivb") -> 16#022FE; +entity("notnivc") -> 16#022FD; +entity("npar") -> 16#02226; +entity("nparallel") -> 16#02226; +entity("nparsl") -> [16#02AFD, 16#020E5]; +entity("npart") -> [16#02202, 16#00338]; +entity("npolint") -> 16#02A14; +entity("npr") -> 16#02280; +entity("nprcue") -> 16#022E0; +entity("npre") -> [16#02AAF, 16#00338]; +entity("nprec") -> 16#02280; +entity("npreceq") -> [16#02AAF, 16#00338]; +entity("nrArr") -> 16#021CF; +entity("nrarr") -> 16#0219B; +entity("nrarrc") -> [16#02933, 16#00338]; +entity("nrarrw") -> [16#0219D, 16#00338]; +entity("nrightarrow") -> 16#0219B; +entity("nrtri") -> 16#022EB; +entity("nrtrie") -> 16#022ED; +entity("nsc") -> 16#02281; +entity("nsccue") -> 16#022E1; +entity("nsce") -> [16#02AB0, 16#00338]; +entity("nscr") -> 16#1D4C3; +entity("nshortmid") -> 16#02224; +entity("nshortparallel") -> 16#02226; +entity("nsim") -> 16#02241; +entity("nsime") -> 16#02244; +entity("nsimeq") -> 16#02244; +entity("nsmid") -> 16#02224; +entity("nspar") -> 16#02226; +entity("nsqsube") -> 16#022E2; +entity("nsqsupe") -> 16#022E3; +entity("nsub") -> 16#02284; +entity("nsubE") -> [16#02AC5, 16#00338]; +entity("nsube") -> 16#02288; +entity("nsubset") -> [16#02282, 16#020D2]; +entity("nsubseteq") -> 16#02288; +entity("nsubseteqq") -> [16#02AC5, 16#00338]; +entity("nsucc") -> 16#02281; +entity("nsucceq") -> [16#02AB0, 16#00338]; +entity("nsup") -> 16#02285; +entity("nsupE") -> [16#02AC6, 16#00338]; +entity("nsupe") -> 16#02289; +entity("nsupset") -> [16#02283, 16#020D2]; +entity("nsupseteq") -> 16#02289; +entity("nsupseteqq") -> [16#02AC6, 16#00338]; +entity("ntgl") -> 16#02279; +entity("ntilde") -> 16#000F1; +entity("ntlg") -> 16#02278; +entity("ntriangleleft") -> 16#022EA; +entity("ntrianglelefteq") -> 16#022EC; +entity("ntriangleright") -> 16#022EB; +entity("ntrianglerighteq") -> 16#022ED; +entity("nu") -> 16#003BD; +entity("num") -> 16#00023; +entity("numero") -> 16#02116; +entity("numsp") -> 16#02007; +entity("nvDash") -> 16#022AD; +entity("nvHarr") -> 16#02904; +entity("nvap") -> [16#0224D, 16#020D2]; +entity("nvdash") -> 16#022AC; +entity("nvge") -> [16#02265, 16#020D2]; +entity("nvgt") -> [16#0003E, 16#020D2]; +entity("nvinfin") -> 16#029DE; +entity("nvlArr") -> 16#02902; +entity("nvle") -> [16#02264, 16#020D2]; +entity("nvlt") -> [16#0003C, 16#020D2]; +entity("nvltrie") -> [16#022B4, 16#020D2]; +entity("nvrArr") -> 16#02903; +entity("nvrtrie") -> [16#022B5, 16#020D2]; +entity("nvsim") -> [16#0223C, 16#020D2]; +entity("nwArr") -> 16#021D6; +entity("nwarhk") -> 16#02923; +entity("nwarr") -> 16#02196; +entity("nwarrow") -> 16#02196; +entity("nwnear") -> 16#02927; +entity("oS") -> 16#024C8; +entity("oacute") -> 16#000F3; +entity("oast") -> 16#0229B; +entity("ocir") -> 16#0229A; +entity("ocirc") -> 16#000F4; +entity("ocy") -> 16#0043E; +entity("odash") -> 16#0229D; +entity("odblac") -> 16#00151; +entity("odiv") -> 16#02A38; +entity("odot") -> 16#02299; +entity("odsold") -> 16#029BC; +entity("oelig") -> 16#00153; +entity("ofcir") -> 16#029BF; +entity("ofr") -> 16#1D52C; +entity("ogon") -> 16#002DB; +entity("ograve") -> 16#000F2; +entity("ogt") -> 16#029C1; +entity("ohbar") -> 16#029B5; +entity("ohm") -> 16#003A9; +entity("oint") -> 16#0222E; +entity("olarr") -> 16#021BA; +entity("olcir") -> 16#029BE; +entity("olcross") -> 16#029BB; +entity("oline") -> 16#0203E; +entity("olt") -> 16#029C0; +entity("omacr") -> 16#0014D; +entity("omega") -> 16#003C9; +entity("omicron") -> 16#003BF; +entity("omid") -> 16#029B6; +entity("ominus") -> 16#02296; +entity("oopf") -> 16#1D560; +entity("opar") -> 16#029B7; +entity("operp") -> 16#029B9; +entity("oplus") -> 16#02295; +entity("or") -> 16#02228; +entity("orarr") -> 16#021BB; +entity("ord") -> 16#02A5D; +entity("order") -> 16#02134; +entity("orderof") -> 16#02134; +entity("ordf") -> 16#000AA; +entity("ordm") -> 16#000BA; +entity("origof") -> 16#022B6; +entity("oror") -> 16#02A56; +entity("orslope") -> 16#02A57; +entity("orv") -> 16#02A5B; +entity("oscr") -> 16#02134; +entity("oslash") -> 16#000F8; +entity("osol") -> 16#02298; +entity("otilde") -> 16#000F5; +entity("otimes") -> 16#02297; +entity("otimesas") -> 16#02A36; +entity("ouml") -> 16#000F6; +entity("ovbar") -> 16#0233D; +entity("par") -> 16#02225; +entity("para") -> 16#000B6; +entity("parallel") -> 16#02225; +entity("parsim") -> 16#02AF3; +entity("parsl") -> 16#02AFD; +entity("part") -> 16#02202; +entity("pcy") -> 16#0043F; +entity("percnt") -> 16#00025; +entity("period") -> 16#0002E; +entity("permil") -> 16#02030; +entity("perp") -> 16#022A5; +entity("pertenk") -> 16#02031; +entity("pfr") -> 16#1D52D; +entity("phi") -> 16#003C6; +entity("phiv") -> 16#003D5; +entity("phmmat") -> 16#02133; +entity("phone") -> 16#0260E; +entity("pi") -> 16#003C0; +entity("pitchfork") -> 16#022D4; +entity("piv") -> 16#003D6; +entity("planck") -> 16#0210F; +entity("planckh") -> 16#0210E; +entity("plankv") -> 16#0210F; +entity("plus") -> 16#0002B; +entity("plusacir") -> 16#02A23; +entity("plusb") -> 16#0229E; +entity("pluscir") -> 16#02A22; +entity("plusdo") -> 16#02214; +entity("plusdu") -> 16#02A25; +entity("pluse") -> 16#02A72; +entity("plusmn") -> 16#000B1; +entity("plussim") -> 16#02A26; +entity("plustwo") -> 16#02A27; +entity("pm") -> 16#000B1; +entity("pointint") -> 16#02A15; +entity("popf") -> 16#1D561; +entity("pound") -> 16#000A3; +entity("pr") -> 16#0227A; +entity("prE") -> 16#02AB3; +entity("prap") -> 16#02AB7; +entity("prcue") -> 16#0227C; +entity("pre") -> 16#02AAF; +entity("prec") -> 16#0227A; +entity("precapprox") -> 16#02AB7; +entity("preccurlyeq") -> 16#0227C; +entity("preceq") -> 16#02AAF; +entity("precnapprox") -> 16#02AB9; +entity("precneqq") -> 16#02AB5; +entity("precnsim") -> 16#022E8; +entity("precsim") -> 16#0227E; +entity("prime") -> 16#02032; +entity("primes") -> 16#02119; +entity("prnE") -> 16#02AB5; +entity("prnap") -> 16#02AB9; +entity("prnsim") -> 16#022E8; +entity("prod") -> 16#0220F; +entity("profalar") -> 16#0232E; +entity("profline") -> 16#02312; +entity("profsurf") -> 16#02313; +entity("prop") -> 16#0221D; +entity("propto") -> 16#0221D; +entity("prsim") -> 16#0227E; +entity("prurel") -> 16#022B0; +entity("pscr") -> 16#1D4C5; +entity("psi") -> 16#003C8; +entity("puncsp") -> 16#02008; +entity("qfr") -> 16#1D52E; +entity("qint") -> 16#02A0C; +entity("qopf") -> 16#1D562; +entity("qprime") -> 16#02057; +entity("qscr") -> 16#1D4C6; +entity("quaternions") -> 16#0210D; +entity("quatint") -> 16#02A16; +entity("quest") -> 16#0003F; +entity("questeq") -> 16#0225F; +entity("quot") -> 16#00022; +entity("rAarr") -> 16#021DB; +entity("rArr") -> 16#021D2; +entity("rAtail") -> 16#0291C; +entity("rBarr") -> 16#0290F; +entity("rHar") -> 16#02964; +entity("race") -> [16#0223D, 16#00331]; +entity("racute") -> 16#00155; +entity("radic") -> 16#0221A; +entity("raemptyv") -> 16#029B3; +entity("rang") -> 16#027E9; +entity("rangd") -> 16#02992; +entity("range") -> 16#029A5; +entity("rangle") -> 16#027E9; +entity("raquo") -> 16#000BB; +entity("rarr") -> 16#02192; +entity("rarrap") -> 16#02975; +entity("rarrb") -> 16#021E5; +entity("rarrbfs") -> 16#02920; +entity("rarrc") -> 16#02933; +entity("rarrfs") -> 16#0291E; +entity("rarrhk") -> 16#021AA; +entity("rarrlp") -> 16#021AC; +entity("rarrpl") -> 16#02945; +entity("rarrsim") -> 16#02974; +entity("rarrtl") -> 16#021A3; +entity("rarrw") -> 16#0219D; +entity("ratail") -> 16#0291A; +entity("ratio") -> 16#02236; +entity("rationals") -> 16#0211A; +entity("rbarr") -> 16#0290D; +entity("rbbrk") -> 16#02773; +entity("rbrace") -> 16#0007D; +entity("rbrack") -> 16#0005D; +entity("rbrke") -> 16#0298C; +entity("rbrksld") -> 16#0298E; +entity("rbrkslu") -> 16#02990; +entity("rcaron") -> 16#00159; +entity("rcedil") -> 16#00157; +entity("rceil") -> 16#02309; +entity("rcub") -> 16#0007D; +entity("rcy") -> 16#00440; +entity("rdca") -> 16#02937; +entity("rdldhar") -> 16#02969; +entity("rdquo") -> 16#0201D; +entity("rdquor") -> 16#0201D; +entity("rdsh") -> 16#021B3; +entity("real") -> 16#0211C; +entity("realine") -> 16#0211B; +entity("realpart") -> 16#0211C; +entity("reals") -> 16#0211D; +entity("rect") -> 16#025AD; +entity("reg") -> 16#000AE; +entity("rfisht") -> 16#0297D; +entity("rfloor") -> 16#0230B; +entity("rfr") -> 16#1D52F; +entity("rhard") -> 16#021C1; +entity("rharu") -> 16#021C0; +entity("rharul") -> 16#0296C; +entity("rho") -> 16#003C1; +entity("rhov") -> 16#003F1; +entity("rightarrow") -> 16#02192; +entity("rightarrowtail") -> 16#021A3; +entity("rightharpoondown") -> 16#021C1; +entity("rightharpoonup") -> 16#021C0; +entity("rightleftarrows") -> 16#021C4; +entity("rightleftharpoons") -> 16#021CC; +entity("rightrightarrows") -> 16#021C9; +entity("rightsquigarrow") -> 16#0219D; +entity("rightthreetimes") -> 16#022CC; +entity("ring") -> 16#002DA; +entity("risingdotseq") -> 16#02253; +entity("rlarr") -> 16#021C4; +entity("rlhar") -> 16#021CC; +entity("rlm") -> 16#0200F; +entity("rmoust") -> 16#023B1; +entity("rmoustache") -> 16#023B1; +entity("rnmid") -> 16#02AEE; +entity("roang") -> 16#027ED; +entity("roarr") -> 16#021FE; +entity("robrk") -> 16#027E7; +entity("ropar") -> 16#02986; +entity("ropf") -> 16#1D563; +entity("roplus") -> 16#02A2E; +entity("rotimes") -> 16#02A35; +entity("rpar") -> 16#00029; +entity("rpargt") -> 16#02994; +entity("rppolint") -> 16#02A12; +entity("rrarr") -> 16#021C9; +entity("rsaquo") -> 16#0203A; +entity("rscr") -> 16#1D4C7; +entity("rsh") -> 16#021B1; +entity("rsqb") -> 16#0005D; +entity("rsquo") -> 16#02019; +entity("rsquor") -> 16#02019; +entity("rthree") -> 16#022CC; +entity("rtimes") -> 16#022CA; +entity("rtri") -> 16#025B9; +entity("rtrie") -> 16#022B5; +entity("rtrif") -> 16#025B8; +entity("rtriltri") -> 16#029CE; +entity("ruluhar") -> 16#02968; +entity("rx") -> 16#0211E; +entity("sacute") -> 16#0015B; +entity("sbquo") -> 16#0201A; +entity("sc") -> 16#0227B; +entity("scE") -> 16#02AB4; +entity("scap") -> 16#02AB8; +entity("scaron") -> 16#00161; +entity("sccue") -> 16#0227D; +entity("sce") -> 16#02AB0; +entity("scedil") -> 16#0015F; +entity("scirc") -> 16#0015D; +entity("scnE") -> 16#02AB6; +entity("scnap") -> 16#02ABA; +entity("scnsim") -> 16#022E9; +entity("scpolint") -> 16#02A13; +entity("scsim") -> 16#0227F; +entity("scy") -> 16#00441; +entity("sdot") -> 16#022C5; +entity("sdotb") -> 16#022A1; +entity("sdote") -> 16#02A66; +entity("seArr") -> 16#021D8; +entity("searhk") -> 16#02925; +entity("searr") -> 16#02198; +entity("searrow") -> 16#02198; +entity("sect") -> 16#000A7; +entity("semi") -> 16#0003B; +entity("seswar") -> 16#02929; +entity("setminus") -> 16#02216; +entity("setmn") -> 16#02216; +entity("sext") -> 16#02736; +entity("sfr") -> 16#1D530; +entity("sfrown") -> 16#02322; +entity("sharp") -> 16#0266F; +entity("shchcy") -> 16#00449; +entity("shcy") -> 16#00448; +entity("shortmid") -> 16#02223; +entity("shortparallel") -> 16#02225; +entity("shy") -> 16#000AD; +entity("sigma") -> 16#003C3; +entity("sigmaf") -> 16#003C2; +entity("sigmav") -> 16#003C2; +entity("sim") -> 16#0223C; +entity("simdot") -> 16#02A6A; +entity("sime") -> 16#02243; +entity("simeq") -> 16#02243; +entity("simg") -> 16#02A9E; +entity("simgE") -> 16#02AA0; +entity("siml") -> 16#02A9D; +entity("simlE") -> 16#02A9F; +entity("simne") -> 16#02246; +entity("simplus") -> 16#02A24; +entity("simrarr") -> 16#02972; +entity("slarr") -> 16#02190; +entity("smallsetminus") -> 16#02216; +entity("smashp") -> 16#02A33; +entity("smeparsl") -> 16#029E4; +entity("smid") -> 16#02223; +entity("smile") -> 16#02323; +entity("smt") -> 16#02AAA; +entity("smte") -> 16#02AAC; +entity("smtes") -> [16#02AAC, 16#0FE00]; +entity("softcy") -> 16#0044C; +entity("sol") -> 16#0002F; +entity("solb") -> 16#029C4; +entity("solbar") -> 16#0233F; +entity("sopf") -> 16#1D564; +entity("spades") -> 16#02660; +entity("spadesuit") -> 16#02660; +entity("spar") -> 16#02225; +entity("sqcap") -> 16#02293; +entity("sqcaps") -> [16#02293, 16#0FE00]; +entity("sqcup") -> 16#02294; +entity("sqcups") -> [16#02294, 16#0FE00]; +entity("sqsub") -> 16#0228F; +entity("sqsube") -> 16#02291; +entity("sqsubset") -> 16#0228F; +entity("sqsubseteq") -> 16#02291; +entity("sqsup") -> 16#02290; +entity("sqsupe") -> 16#02292; +entity("sqsupset") -> 16#02290; +entity("sqsupseteq") -> 16#02292; +entity("squ") -> 16#025A1; +entity("square") -> 16#025A1; +entity("squarf") -> 16#025AA; +entity("squf") -> 16#025AA; +entity("srarr") -> 16#02192; +entity("sscr") -> 16#1D4C8; +entity("ssetmn") -> 16#02216; +entity("ssmile") -> 16#02323; +entity("sstarf") -> 16#022C6; +entity("star") -> 16#02606; +entity("starf") -> 16#02605; +entity("straightepsilon") -> 16#003F5; +entity("straightphi") -> 16#003D5; +entity("strns") -> 16#000AF; +entity("sub") -> 16#02282; +entity("subE") -> 16#02AC5; +entity("subdot") -> 16#02ABD; +entity("sube") -> 16#02286; +entity("subedot") -> 16#02AC3; +entity("submult") -> 16#02AC1; +entity("subnE") -> 16#02ACB; +entity("subne") -> 16#0228A; +entity("subplus") -> 16#02ABF; +entity("subrarr") -> 16#02979; +entity("subset") -> 16#02282; +entity("subseteq") -> 16#02286; +entity("subseteqq") -> 16#02AC5; +entity("subsetneq") -> 16#0228A; +entity("subsetneqq") -> 16#02ACB; +entity("subsim") -> 16#02AC7; +entity("subsub") -> 16#02AD5; +entity("subsup") -> 16#02AD3; +entity("succ") -> 16#0227B; +entity("succapprox") -> 16#02AB8; +entity("succcurlyeq") -> 16#0227D; +entity("succeq") -> 16#02AB0; +entity("succnapprox") -> 16#02ABA; +entity("succneqq") -> 16#02AB6; +entity("succnsim") -> 16#022E9; +entity("succsim") -> 16#0227F; +entity("sum") -> 16#02211; +entity("sung") -> 16#0266A; +entity("sup") -> 16#02283; +entity("sup1") -> 16#000B9; +entity("sup2") -> 16#000B2; +entity("sup3") -> 16#000B3; +entity("supE") -> 16#02AC6; +entity("supdot") -> 16#02ABE; +entity("supdsub") -> 16#02AD8; +entity("supe") -> 16#02287; +entity("supedot") -> 16#02AC4; +entity("suphsol") -> 16#027C9; +entity("suphsub") -> 16#02AD7; +entity("suplarr") -> 16#0297B; +entity("supmult") -> 16#02AC2; +entity("supnE") -> 16#02ACC; +entity("supne") -> 16#0228B; +entity("supplus") -> 16#02AC0; +entity("supset") -> 16#02283; +entity("supseteq") -> 16#02287; +entity("supseteqq") -> 16#02AC6; +entity("supsetneq") -> 16#0228B; +entity("supsetneqq") -> 16#02ACC; +entity("supsim") -> 16#02AC8; +entity("supsub") -> 16#02AD4; +entity("supsup") -> 16#02AD6; +entity("swArr") -> 16#021D9; +entity("swarhk") -> 16#02926; +entity("swarr") -> 16#02199; +entity("swarrow") -> 16#02199; +entity("swnwar") -> 16#0292A; +entity("szlig") -> 16#000DF; +entity("target") -> 16#02316; +entity("tau") -> 16#003C4; +entity("tbrk") -> 16#023B4; +entity("tcaron") -> 16#00165; +entity("tcedil") -> 16#00163; +entity("tcy") -> 16#00442; +entity("tdot") -> 16#020DB; +entity("telrec") -> 16#02315; +entity("tfr") -> 16#1D531; +entity("there4") -> 16#02234; +entity("therefore") -> 16#02234; +entity("theta") -> 16#003B8; +entity("thetasym") -> 16#003D1; +entity("thetav") -> 16#003D1; +entity("thickapprox") -> 16#02248; +entity("thicksim") -> 16#0223C; +entity("thinsp") -> 16#02009; +entity("thkap") -> 16#02248; +entity("thksim") -> 16#0223C; +entity("thorn") -> 16#000FE; +entity("tilde") -> 16#002DC; +entity("times") -> 16#000D7; +entity("timesb") -> 16#022A0; +entity("timesbar") -> 16#02A31; +entity("timesd") -> 16#02A30; +entity("tint") -> 16#0222D; +entity("toea") -> 16#02928; +entity("top") -> 16#022A4; +entity("topbot") -> 16#02336; +entity("topcir") -> 16#02AF1; +entity("topf") -> 16#1D565; +entity("topfork") -> 16#02ADA; +entity("tosa") -> 16#02929; +entity("tprime") -> 16#02034; +entity("trade") -> 16#02122; +entity("triangle") -> 16#025B5; +entity("triangledown") -> 16#025BF; +entity("triangleleft") -> 16#025C3; +entity("trianglelefteq") -> 16#022B4; +entity("triangleq") -> 16#0225C; +entity("triangleright") -> 16#025B9; +entity("trianglerighteq") -> 16#022B5; +entity("tridot") -> 16#025EC; +entity("trie") -> 16#0225C; +entity("triminus") -> 16#02A3A; +entity("triplus") -> 16#02A39; +entity("trisb") -> 16#029CD; +entity("tritime") -> 16#02A3B; +entity("trpezium") -> 16#023E2; +entity("tscr") -> 16#1D4C9; +entity("tscy") -> 16#00446; +entity("tshcy") -> 16#0045B; +entity("tstrok") -> 16#00167; +entity("twixt") -> 16#0226C; +entity("twoheadleftarrow") -> 16#0219E; +entity("twoheadrightarrow") -> 16#021A0; +entity("uArr") -> 16#021D1; +entity("uHar") -> 16#02963; +entity("uacute") -> 16#000FA; +entity("uarr") -> 16#02191; +entity("ubrcy") -> 16#0045E; +entity("ubreve") -> 16#0016D; +entity("ucirc") -> 16#000FB; +entity("ucy") -> 16#00443; +entity("udarr") -> 16#021C5; +entity("udblac") -> 16#00171; +entity("udhar") -> 16#0296E; +entity("ufisht") -> 16#0297E; +entity("ufr") -> 16#1D532; +entity("ugrave") -> 16#000F9; +entity("uharl") -> 16#021BF; +entity("uharr") -> 16#021BE; +entity("uhblk") -> 16#02580; +entity("ulcorn") -> 16#0231C; +entity("ulcorner") -> 16#0231C; +entity("ulcrop") -> 16#0230F; +entity("ultri") -> 16#025F8; +entity("umacr") -> 16#0016B; +entity("uml") -> 16#000A8; +entity("uogon") -> 16#00173; +entity("uopf") -> 16#1D566; +entity("uparrow") -> 16#02191; +entity("updownarrow") -> 16#02195; +entity("upharpoonleft") -> 16#021BF; +entity("upharpoonright") -> 16#021BE; +entity("uplus") -> 16#0228E; +entity("upsi") -> 16#003C5; +entity("upsih") -> 16#003D2; +entity("upsilon") -> 16#003C5; +entity("upuparrows") -> 16#021C8; +entity("urcorn") -> 16#0231D; +entity("urcorner") -> 16#0231D; +entity("urcrop") -> 16#0230E; +entity("uring") -> 16#0016F; +entity("urtri") -> 16#025F9; +entity("uscr") -> 16#1D4CA; +entity("utdot") -> 16#022F0; +entity("utilde") -> 16#00169; +entity("utri") -> 16#025B5; +entity("utrif") -> 16#025B4; +entity("uuarr") -> 16#021C8; +entity("uuml") -> 16#000FC; +entity("uwangle") -> 16#029A7; +entity("vArr") -> 16#021D5; +entity("vBar") -> 16#02AE8; +entity("vBarv") -> 16#02AE9; +entity("vDash") -> 16#022A8; +entity("vangrt") -> 16#0299C; +entity("varepsilon") -> 16#003F5; +entity("varkappa") -> 16#003F0; +entity("varnothing") -> 16#02205; +entity("varphi") -> 16#003D5; +entity("varpi") -> 16#003D6; +entity("varpropto") -> 16#0221D; +entity("varr") -> 16#02195; +entity("varrho") -> 16#003F1; +entity("varsigma") -> 16#003C2; +entity("varsubsetneq") -> [16#0228A, 16#0FE00]; +entity("varsubsetneqq") -> [16#02ACB, 16#0FE00]; +entity("varsupsetneq") -> [16#0228B, 16#0FE00]; +entity("varsupsetneqq") -> [16#02ACC, 16#0FE00]; +entity("vartheta") -> 16#003D1; +entity("vartriangleleft") -> 16#022B2; +entity("vartriangleright") -> 16#022B3; +entity("vcy") -> 16#00432; +entity("vdash") -> 16#022A2; +entity("vee") -> 16#02228; +entity("veebar") -> 16#022BB; +entity("veeeq") -> 16#0225A; +entity("vellip") -> 16#022EE; +entity("verbar") -> 16#0007C; +entity("vert") -> 16#0007C; +entity("vfr") -> 16#1D533; +entity("vltri") -> 16#022B2; +entity("vnsub") -> [16#02282, 16#020D2]; +entity("vnsup") -> [16#02283, 16#020D2]; +entity("vopf") -> 16#1D567; +entity("vprop") -> 16#0221D; +entity("vrtri") -> 16#022B3; +entity("vscr") -> 16#1D4CB; +entity("vsubnE") -> [16#02ACB, 16#0FE00]; +entity("vsubne") -> [16#0228A, 16#0FE00]; +entity("vsupnE") -> [16#02ACC, 16#0FE00]; +entity("vsupne") -> [16#0228B, 16#0FE00]; +entity("vzigzag") -> 16#0299A; +entity("wcirc") -> 16#00175; +entity("wedbar") -> 16#02A5F; +entity("wedge") -> 16#02227; +entity("wedgeq") -> 16#02259; +entity("weierp") -> 16#02118; +entity("wfr") -> 16#1D534; +entity("wopf") -> 16#1D568; +entity("wp") -> 16#02118; +entity("wr") -> 16#02240; +entity("wreath") -> 16#02240; +entity("wscr") -> 16#1D4CC; +entity("xcap") -> 16#022C2; +entity("xcirc") -> 16#025EF; +entity("xcup") -> 16#022C3; +entity("xdtri") -> 16#025BD; +entity("xfr") -> 16#1D535; +entity("xhArr") -> 16#027FA; +entity("xharr") -> 16#027F7; +entity("xi") -> 16#003BE; +entity("xlArr") -> 16#027F8; +entity("xlarr") -> 16#027F5; +entity("xmap") -> 16#027FC; +entity("xnis") -> 16#022FB; +entity("xodot") -> 16#02A00; +entity("xopf") -> 16#1D569; +entity("xoplus") -> 16#02A01; +entity("xotime") -> 16#02A02; +entity("xrArr") -> 16#027F9; +entity("xrarr") -> 16#027F6; +entity("xscr") -> 16#1D4CD; +entity("xsqcup") -> 16#02A06; +entity("xuplus") -> 16#02A04; +entity("xutri") -> 16#025B3; +entity("xvee") -> 16#022C1; +entity("xwedge") -> 16#022C0; +entity("yacute") -> 16#000FD; +entity("yacy") -> 16#0044F; +entity("ycirc") -> 16#00177; +entity("ycy") -> 16#0044B; +entity("yen") -> 16#000A5; +entity("yfr") -> 16#1D536; +entity("yicy") -> 16#00457; +entity("yopf") -> 16#1D56A; +entity("yscr") -> 16#1D4CE; +entity("yucy") -> 16#0044E; +entity("yuml") -> 16#000FF; +entity("zacute") -> 16#0017A; +entity("zcaron") -> 16#0017E; +entity("zcy") -> 16#00437; +entity("zdot") -> 16#0017C; +entity("zeetrf") -> 16#02128; +entity("zeta") -> 16#003B6; +entity("zfr") -> 16#1D537; +entity("zhcy") -> 16#00436; +entity("zigrarr") -> 16#021DD; +entity("zopf") -> 16#1D56B; +entity("zscr") -> 16#1D4CF; +entity("zwj") -> 16#0200D; +entity("zwnj") -> 16#0200C; +entity(_) -> undefined. + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +exhaustive_entity_test() -> + T = mochiweb_cover:clause_lookup_table(?MODULE, entity), + [?assertEqual(V, entity(K)) || {K, V} <- T]. + +charref_test() -> + 1234 = charref("#1234"), + 255 = charref("#xfF"), + 255 = charref(<<"#XFf">>), + 38 = charref("amp"), + 38 = charref(<<"amp">>), + undefined = charref("not_an_entity"), + undefined = charref("#not_an_entity"), + undefined = charref("#xnot_an_entity"), + ok. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_cookies.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_cookies.erl new file mode 100644 index 0000000..1cc4e91 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_cookies.erl @@ -0,0 +1,331 @@ +%% @author Emad El-Haraty +%% @copyright 2007 Mochi Media, Inc. + +%% @doc HTTP Cookie parsing and generating (RFC 2109, RFC 2965). + +-module(mochiweb_cookies). +-export([parse_cookie/1, cookie/3, cookie/2]). + +-define(QUOTE, $\"). + +-define(IS_WHITESPACE(C), + (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)). + +%% RFC 2616 separators (called tspecials in RFC 2068) +-define(IS_SEPARATOR(C), + (C < 32 orelse + C =:= $\s orelse C =:= $\t orelse + C =:= $( orelse C =:= $) orelse C =:= $< orelse C =:= $> orelse + C =:= $@ orelse C =:= $, orelse C =:= $; orelse C =:= $: orelse + C =:= $\\ orelse C =:= $\" orelse C =:= $/ orelse + C =:= $[ orelse C =:= $] orelse C =:= $? orelse C =:= $= orelse + C =:= ${ orelse C =:= $})). + +%% @type proplist() = [{Key::string(), Value::string()}]. +%% @type header() = {Name::string(), Value::string()}. +%% @type int_seconds() = integer(). + +%% @spec cookie(Key::string(), Value::string()) -> header() +%% @doc Short-hand for cookie(Key, Value, []). +cookie(Key, Value) -> + cookie(Key, Value, []). + +%% @spec cookie(Key::string(), Value::string(), Options::[Option]) -> header() +%% where Option = {max_age, int_seconds()} | {local_time, {date(), time()}} +%% | {domain, string()} | {path, string()} +%% | {secure, true | false} | {http_only, true | false} +%% +%% @doc Generate a Set-Cookie header field tuple. +cookie(Key, Value, Options) -> + Cookie = [any_to_list(Key), "=", quote(Value), "; Version=1"], + %% Set-Cookie: + %% Comment, Domain, Max-Age, Path, Secure, Version + %% Set-Cookie2: + %% Comment, CommentURL, Discard, Domain, Max-Age, Path, Port, Secure, + %% Version + ExpiresPart = + case proplists:get_value(max_age, Options) of + undefined -> + ""; + RawAge -> + When = case proplists:get_value(local_time, Options) of + undefined -> + calendar:local_time(); + LocalTime -> + LocalTime + end, + Age = case RawAge < 0 of + true -> + 0; + false -> + RawAge + end, + ["; Expires=", age_to_cookie_date(Age, When), + "; Max-Age=", quote(Age)] + end, + SecurePart = + case proplists:get_value(secure, Options) of + true -> + "; Secure"; + _ -> + "" + end, + DomainPart = + case proplists:get_value(domain, Options) of + undefined -> + ""; + Domain -> + ["; Domain=", quote(Domain)] + end, + PathPart = + case proplists:get_value(path, Options) of + undefined -> + ""; + Path -> + ["; Path=", quote(Path)] + end, + HttpOnlyPart = + case proplists:get_value(http_only, Options) of + true -> + "; HttpOnly"; + _ -> + "" + end, + CookieParts = [Cookie, ExpiresPart, SecurePart, DomainPart, PathPart, HttpOnlyPart], + {"Set-Cookie", lists:flatten(CookieParts)}. + + +%% Every major browser incorrectly handles quoted strings in a +%% different and (worse) incompatible manner. Instead of wasting time +%% writing redundant code for each browser, we restrict cookies to +%% only contain characters that browsers handle compatibly. +%% +%% By replacing the definition of quote with this, we generate +%% RFC-compliant cookies: +%% +%% quote(V) -> +%% Fun = fun(?QUOTE, Acc) -> [$\\, ?QUOTE | Acc]; +%% (Ch, Acc) -> [Ch | Acc] +%% end, +%% [?QUOTE | lists:foldr(Fun, [?QUOTE], V)]. + +%% Convert to a string and raise an error if quoting is required. +quote(V0) -> + V = any_to_list(V0), + lists:all(fun(Ch) -> Ch =:= $/ orelse not ?IS_SEPARATOR(Ch) end, V) + orelse erlang:error({cookie_quoting_required, V}), + V. + + +%% Return a date in the form of: Wdy, DD-Mon-YYYY HH:MM:SS GMT +%% See also: rfc2109: 10.1.2 +rfc2109_cookie_expires_date(LocalTime) -> + {{YYYY,MM,DD},{Hour,Min,Sec}} = + case calendar:local_time_to_universal_time_dst(LocalTime) of + [] -> + {Date, {Hour1, Min1, Sec1}} = LocalTime, + LocalTime2 = {Date, {Hour1 + 1, Min1, Sec1}}, + case calendar:local_time_to_universal_time_dst(LocalTime2) of + [Gmt] -> Gmt; + [_,Gmt] -> Gmt + end; + [Gmt] -> Gmt; + [_,Gmt] -> Gmt + end, + DayNumber = calendar:day_of_the_week({YYYY,MM,DD}), + lists:flatten( + io_lib:format("~s, ~2.2.0w-~3.s-~4.4.0w ~2.2.0w:~2.2.0w:~2.2.0w GMT", + [httpd_util:day(DayNumber),DD,httpd_util:month(MM),YYYY,Hour,Min,Sec])). + +add_seconds(Secs, LocalTime) -> + Greg = calendar:datetime_to_gregorian_seconds(LocalTime), + calendar:gregorian_seconds_to_datetime(Greg + Secs). + +age_to_cookie_date(Age, LocalTime) -> + rfc2109_cookie_expires_date(add_seconds(Age, LocalTime)). + +%% @spec parse_cookie(string()) -> [{K::string(), V::string()}] +%% @doc Parse the contents of a Cookie header field, ignoring cookie +%% attributes, and return a simple property list. +parse_cookie("") -> + []; +parse_cookie(Cookie) -> + parse_cookie(Cookie, []). + +%% Internal API + +parse_cookie([], Acc) -> + lists:reverse(Acc); +parse_cookie(String, Acc) -> + {{Token, Value}, Rest} = read_pair(String), + Acc1 = case Token of + "" -> + Acc; + "$" ++ _ -> + Acc; + _ -> + [{Token, Value} | Acc] + end, + parse_cookie(Rest, Acc1). + +read_pair(String) -> + {Token, Rest} = read_token(skip_whitespace(String)), + {Value, Rest1} = read_value(skip_whitespace(Rest)), + {{Token, Value}, skip_past_separator(Rest1)}. + +read_value([$= | Value]) -> + Value1 = skip_whitespace(Value), + case Value1 of + [?QUOTE | _] -> + read_quoted(Value1); + _ -> + read_token(Value1) + end; +read_value(String) -> + {"", String}. + +read_quoted([?QUOTE | String]) -> + read_quoted(String, []). + +read_quoted([], Acc) -> + {lists:reverse(Acc), []}; +read_quoted([?QUOTE | Rest], Acc) -> + {lists:reverse(Acc), Rest}; +read_quoted([$\\, Any | Rest], Acc) -> + read_quoted(Rest, [Any | Acc]); +read_quoted([C | Rest], Acc) -> + read_quoted(Rest, [C | Acc]). + +skip_whitespace(String) -> + F = fun (C) -> ?IS_WHITESPACE(C) end, + lists:dropwhile(F, String). + +read_token(String) -> + F = fun (C) -> not ?IS_SEPARATOR(C) end, + lists:splitwith(F, String). + +skip_past_separator([]) -> + []; +skip_past_separator([$; | Rest]) -> + Rest; +skip_past_separator([$, | Rest]) -> + Rest; +skip_past_separator([_ | Rest]) -> + skip_past_separator(Rest). + +any_to_list(V) when is_list(V) -> + V; +any_to_list(V) when is_atom(V) -> + atom_to_list(V); +any_to_list(V) when is_binary(V) -> + binary_to_list(V); +any_to_list(V) when is_integer(V) -> + integer_to_list(V). + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +quote_test() -> + %% ?assertError eunit macro is not compatible with coverage module + try quote(":wq") + catch error:{cookie_quoting_required, ":wq"} -> ok + end, + ?assertEqual( + "foo", + quote(foo)), + ok. + +parse_cookie_test() -> + %% RFC example + C1 = "$Version=\"1\"; Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\"; + Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\"; + Shipping=\"FedEx\"; $Path=\"/acme\"", + ?assertEqual( + [{"Customer","WILE_E_COYOTE"}, + {"Part_Number","Rocket_Launcher_0001"}, + {"Shipping","FedEx"}], + parse_cookie(C1)), + %% Potential edge cases + ?assertEqual( + [{"foo", "x"}], + parse_cookie("foo=\"\\x\"")), + ?assertEqual( + [], + parse_cookie("=")), + ?assertEqual( + [{"foo", ""}, {"bar", ""}], + parse_cookie(" foo ; bar ")), + ?assertEqual( + [{"foo", ""}, {"bar", ""}], + parse_cookie("foo=;bar=")), + ?assertEqual( + [{"foo", "\";"}, {"bar", ""}], + parse_cookie("foo = \"\\\";\";bar ")), + ?assertEqual( + [{"foo", "\";bar"}], + parse_cookie("foo=\"\\\";bar")), + ?assertEqual( + [], + parse_cookie([])), + ?assertEqual( + [{"foo", "bar"}, {"baz", "wibble"}], + parse_cookie("foo=bar , baz=wibble ")), + ok. + +domain_test() -> + ?assertEqual( + {"Set-Cookie", + "Customer=WILE_E_COYOTE; " + "Version=1; " + "Domain=acme.com; " + "HttpOnly"}, + cookie("Customer", "WILE_E_COYOTE", + [{http_only, true}, {domain, "acme.com"}])), + ok. + +local_time_test() -> + {"Set-Cookie", S} = cookie("Customer", "WILE_E_COYOTE", + [{max_age, 111}, {secure, true}]), + ?assertMatch( + ["Customer=WILE_E_COYOTE", + " Version=1", + " Expires=" ++ _, + " Max-Age=111", + " Secure"], + string:tokens(S, ";")), + ok. + +cookie_test() -> + C1 = {"Set-Cookie", + "Customer=WILE_E_COYOTE; " + "Version=1; " + "Path=/acme"}, + C1 = cookie("Customer", "WILE_E_COYOTE", [{path, "/acme"}]), + C1 = cookie("Customer", "WILE_E_COYOTE", + [{path, "/acme"}, {badoption, "negatory"}]), + C1 = cookie('Customer', 'WILE_E_COYOTE', [{path, '/acme'}]), + C1 = cookie(<<"Customer">>, <<"WILE_E_COYOTE">>, [{path, <<"/acme">>}]), + + {"Set-Cookie","=NoKey; Version=1"} = cookie("", "NoKey", []), + {"Set-Cookie","=NoKey; Version=1"} = cookie("", "NoKey"), + LocalTime = calendar:universal_time_to_local_time({{2007, 5, 15}, {13, 45, 33}}), + C2 = {"Set-Cookie", + "Customer=WILE_E_COYOTE; " + "Version=1; " + "Expires=Tue, 15-May-2007 13:45:33 GMT; " + "Max-Age=0"}, + C2 = cookie("Customer", "WILE_E_COYOTE", + [{max_age, -111}, {local_time, LocalTime}]), + C3 = {"Set-Cookie", + "Customer=WILE_E_COYOTE; " + "Version=1; " + "Expires=Wed, 16-May-2007 13:45:50 GMT; " + "Max-Age=86417"}, + C3 = cookie("Customer", "WILE_E_COYOTE", + [{max_age, 86417}, {local_time, LocalTime}]), + ok. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_cover.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_cover.erl new file mode 100644 index 0000000..aa075d5 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_cover.erl @@ -0,0 +1,75 @@ +%% @author Bob Ippolito +%% @copyright 2010 Mochi Media, Inc. + +%% @doc Workarounds for various cover deficiencies. +-module(mochiweb_cover). +-export([get_beam/1, get_abstract_code/1, + get_clauses/2, clause_lookup_table/1]). +-export([clause_lookup_table/2]). + +%% Internal + +get_beam(Module) -> + {Module, Beam, _Path} = code:get_object_code(Module), + Beam. + +get_abstract_code(Beam) -> + {ok, {_Module, + [{abstract_code, + {raw_abstract_v1, L}}]}} = beam_lib:chunks(Beam, [abstract_code]), + L. + +get_clauses(Function, Code) -> + [L] = [Clauses || {function, _, FName, _, Clauses} + <- Code, FName =:= Function], + L. + +clause_lookup_table(Module, Function) -> + clause_lookup_table( + get_clauses(Function, + get_abstract_code(get_beam(Module)))). + +clause_lookup_table(Clauses) -> + lists:foldr(fun clause_fold/2, [], Clauses). + +clause_fold({clause, _, + [InTerm], + _Guards=[], + [OutTerm]}, + Acc) -> + try [{erl_parse:normalise(InTerm), erl_parse:normalise(OutTerm)} | Acc] + catch error:_ -> Acc + end; +clause_fold(_, Acc) -> + Acc. + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +foo_table(a) -> b; +foo_table("a") -> <<"b">>; +foo_table(123) -> {4, 3, 2}; +foo_table([list]) -> []; +foo_table([list1, list2]) -> [list1, list2, list3]; +foo_table(ignored) -> some, code, ignored; +foo_table(Var) -> Var. + +foo_table_test() -> + T = clause_lookup_table(?MODULE, foo_table), + [?assertEqual(V, foo_table(K)) || {K, V} <- T]. + +clause_lookup_table_test() -> + ?assertEqual(b, foo_table(a)), + ?assertEqual(ignored, foo_table(ignored)), + ?assertEqual('Var', foo_table('Var')), + ?assertEqual( + [{a, b}, + {"a", <<"b">>}, + {123, {4, 3, 2}}, + {[list], []}, + {[list1, list2], [list1, list2, list3]}], + clause_lookup_table(?MODULE, foo_table)). + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_echo.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_echo.erl new file mode 100644 index 0000000..e145840 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_echo.erl @@ -0,0 +1,41 @@ +%% @author Bob Ippolito +%% @copyright 2007 Mochi Media, Inc. + +%% @doc Simple and stupid echo server to demo mochiweb_socket_server. + +-module(mochiweb_echo). +-author('bob@mochimedia.com'). +-export([start/0, stop/0, loop/1]). + +stop() -> + mochiweb_socket_server:stop(?MODULE). + +start() -> + mochiweb_socket_server:start([{link, false} | options()]). + +options() -> + [{name, ?MODULE}, + {port, 6789}, + {ip, "127.0.0.1"}, + {max, 1}, + {loop, {?MODULE, loop}}]. + +loop(Socket) -> + case mochiweb_socket:recv(Socket, 0, 30000) of + {ok, Data} -> + case mochiweb_socket:send(Socket, Data) of + ok -> + loop(Socket); + _ -> + exit(normal) + end; + _Other -> + exit(normal) + end. + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_headers.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_headers.erl new file mode 100644 index 0000000..b49cf9e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_headers.erl @@ -0,0 +1,420 @@ +%% @author Bob Ippolito +%% @copyright 2007 Mochi Media, Inc. + +%% @doc Case preserving (but case insensitive) HTTP Header dictionary. + +-module(mochiweb_headers). +-author('bob@mochimedia.com'). +-export([empty/0, from_list/1, insert/3, enter/3, get_value/2, lookup/2]). +-export([delete_any/2, get_primary_value/2, get_combined_value/2]). +-export([default/3, enter_from_list/2, default_from_list/2]). +-export([to_list/1, make/1]). +-export([from_binary/1]). + +%% @type headers(). +%% @type key() = atom() | binary() | string(). +%% @type value() = atom() | binary() | string() | integer(). + +%% @spec empty() -> headers() +%% @doc Create an empty headers structure. +empty() -> + gb_trees:empty(). + +%% @spec make(headers() | [{key(), value()}]) -> headers() +%% @doc Construct a headers() from the given list. +make(L) when is_list(L) -> + from_list(L); +%% assume a non-list is already mochiweb_headers. +make(T) -> + T. + +%% @spec from_binary(iolist()) -> headers() +%% @doc Transforms a raw HTTP header into a mochiweb headers structure. +%% +%% The given raw HTTP header can be one of the following: +%% +%% 1) A string or a binary representing a full HTTP header ending with +%% double CRLF. +%% Examples: +%% ``` +%% "Content-Length: 47\r\nContent-Type: text/plain\r\n\r\n" +%% <<"Content-Length: 47\r\nContent-Type: text/plain\r\n\r\n">>''' +%% +%% 2) A list of binaries or strings where each element represents a raw +%% HTTP header line ending with a single CRLF. +%% Examples: +%% ``` +%% [<<"Content-Length: 47\r\n">>, <<"Content-Type: text/plain\r\n">>] +%% ["Content-Length: 47\r\n", "Content-Type: text/plain\r\n"] +%% ["Content-Length: 47\r\n", <<"Content-Type: text/plain\r\n">>]''' +%% +from_binary(RawHttpHeader) when is_binary(RawHttpHeader) -> + from_binary(RawHttpHeader, []); +from_binary(RawHttpHeaderList) -> + from_binary(list_to_binary([RawHttpHeaderList, "\r\n"])). + +from_binary(RawHttpHeader, Acc) -> + case erlang:decode_packet(httph, RawHttpHeader, []) of + {ok, {http_header, _, H, _, V}, Rest} -> + from_binary(Rest, [{H, V} | Acc]); + _ -> + make(Acc) + end. + +%% @spec from_list([{key(), value()}]) -> headers() +%% @doc Construct a headers() from the given list. +from_list(List) -> + lists:foldl(fun ({K, V}, T) -> insert(K, V, T) end, empty(), List). + +%% @spec enter_from_list([{key(), value()}], headers()) -> headers() +%% @doc Insert pairs into the headers, replace any values for existing keys. +enter_from_list(List, T) -> + lists:foldl(fun ({K, V}, T1) -> enter(K, V, T1) end, T, List). + +%% @spec default_from_list([{key(), value()}], headers()) -> headers() +%% @doc Insert pairs into the headers for keys that do not already exist. +default_from_list(List, T) -> + lists:foldl(fun ({K, V}, T1) -> default(K, V, T1) end, T, List). + +%% @spec to_list(headers()) -> [{key(), string()}] +%% @doc Return the contents of the headers. The keys will be the exact key +%% that was first inserted (e.g. may be an atom or binary, case is +%% preserved). +to_list(T) -> + F = fun ({K, {array, L}}, Acc) -> + L1 = lists:reverse(L), + lists:foldl(fun (V, Acc1) -> [{K, V} | Acc1] end, Acc, L1); + (Pair, Acc) -> + [Pair | Acc] + end, + lists:reverse(lists:foldl(F, [], gb_trees:values(T))). + +%% @spec get_value(key(), headers()) -> string() | undefined +%% @doc Return the value of the given header using a case insensitive search. +%% undefined will be returned for keys that are not present. +get_value(K, T) -> + case lookup(K, T) of + {value, {_, V}} -> + expand(V); + none -> + undefined + end. + +%% @spec get_primary_value(key(), headers()) -> string() | undefined +%% @doc Return the value of the given header up to the first semicolon using +%% a case insensitive search. undefined will be returned for keys +%% that are not present. +get_primary_value(K, T) -> + case get_value(K, T) of + undefined -> + undefined; + V -> + lists:takewhile(fun (C) -> C =/= $; end, V) + end. + +%% @spec get_combined_value(key(), headers()) -> string() | undefined +%% @doc Return the value from the given header using a case insensitive search. +%% If the value of the header is a comma-separated list where holds values +%% are all identical, the identical value will be returned. +%% undefined will be returned for keys that are not present or the +%% values in the list are not the same. +%% +%% NOTE: The process isn't designed for a general purpose. If you need +%% to access all values in the combined header, please refer to +%% '''tokenize_header_value/1'''. +%% +%% Section 4.2 of the RFC 2616 (HTTP 1.1) describes multiple message-header +%% fields with the same field-name may be present in a message if and only +%% if the entire field-value for that header field is defined as a +%% comma-separated list [i.e., #(values)]. +get_combined_value(K, T) -> + case get_value(K, T) of + undefined -> + undefined; + V -> + case sets:to_list(sets:from_list(tokenize_header_value(V))) of + [Val] -> + Val; + _ -> + undefined + end + end. + +%% @spec lookup(key(), headers()) -> {value, {key(), string()}} | none +%% @doc Return the case preserved key and value for the given header using +%% a case insensitive search. none will be returned for keys that are +%% not present. +lookup(K, T) -> + case gb_trees:lookup(normalize(K), T) of + {value, {K0, V}} -> + {value, {K0, expand(V)}}; + none -> + none + end. + +%% @spec default(key(), value(), headers()) -> headers() +%% @doc Insert the pair into the headers if it does not already exist. +default(K, V, T) -> + K1 = normalize(K), + V1 = any_to_list(V), + try gb_trees:insert(K1, {K, V1}, T) + catch + error:{key_exists, _} -> + T + end. + +%% @spec enter(key(), value(), headers()) -> headers() +%% @doc Insert the pair into the headers, replacing any pre-existing key. +enter(K, V, T) -> + K1 = normalize(K), + V1 = any_to_list(V), + gb_trees:enter(K1, {K, V1}, T). + +%% @spec insert(key(), value(), headers()) -> headers() +%% @doc Insert the pair into the headers, merging with any pre-existing key. +%% A merge is done with Value = V0 ++ ", " ++ V1. +insert(K, V, T) -> + K1 = normalize(K), + V1 = any_to_list(V), + try gb_trees:insert(K1, {K, V1}, T) + catch + error:{key_exists, _} -> + {K0, V0} = gb_trees:get(K1, T), + V2 = merge(K1, V1, V0), + gb_trees:update(K1, {K0, V2}, T) + end. + +%% @spec delete_any(key(), headers()) -> headers() +%% @doc Delete the header corresponding to key if it is present. +delete_any(K, T) -> + K1 = normalize(K), + gb_trees:delete_any(K1, T). + +%% Internal API + +tokenize_header_value(undefined) -> + undefined; +tokenize_header_value(V) -> + reversed_tokens(trim_and_reverse(V, false), [], []). + +trim_and_reverse([S | Rest], Reversed) when S=:=$ ; S=:=$\n; S=:=$\t -> + trim_and_reverse(Rest, Reversed); +trim_and_reverse(V, false) -> + trim_and_reverse(lists:reverse(V), true); +trim_and_reverse(V, true) -> + V. + +reversed_tokens([], [], Acc) -> + Acc; +reversed_tokens([], Token, Acc) -> + [Token | Acc]; +reversed_tokens("\"" ++ Rest, [], Acc) -> + case extract_quoted_string(Rest, []) of + {String, NewRest} -> + reversed_tokens(NewRest, [], [String | Acc]); + undefined -> + undefined + end; +reversed_tokens("\"" ++ _Rest, _Token, _Acc) -> + undefined; +reversed_tokens([C | Rest], [], Acc) when C=:=$ ;C=:=$\n;C=:=$\t;C=:=$, -> + reversed_tokens(Rest, [], Acc); +reversed_tokens([C | Rest], Token, Acc) when C=:=$ ;C=:=$\n;C=:=$\t;C=:=$, -> + reversed_tokens(Rest, [], [Token | Acc]); +reversed_tokens([C | Rest], Token, Acc) -> + reversed_tokens(Rest, [C | Token], Acc); +reversed_tokens(_, _, _) -> + undefeined. + +extract_quoted_string([], _Acc) -> + undefined; +extract_quoted_string("\"\\" ++ Rest, Acc) -> + extract_quoted_string(Rest, "\"" ++ Acc); +extract_quoted_string("\"" ++ Rest, Acc) -> + {Acc, Rest}; +extract_quoted_string([C | Rest], Acc) -> + extract_quoted_string(Rest, [C | Acc]). + +expand({array, L}) -> + mochiweb_util:join(lists:reverse(L), ", "); +expand(V) -> + V. + +merge("set-cookie", V1, {array, L}) -> + {array, [V1 | L]}; +merge("set-cookie", V1, V0) -> + {array, [V1, V0]}; +merge(_, V1, V0) -> + V0 ++ ", " ++ V1. + +normalize(K) when is_list(K) -> + string:to_lower(K); +normalize(K) when is_atom(K) -> + normalize(atom_to_list(K)); +normalize(K) when is_binary(K) -> + normalize(binary_to_list(K)). + +any_to_list(V) when is_list(V) -> + V; +any_to_list(V) when is_atom(V) -> + atom_to_list(V); +any_to_list(V) when is_binary(V) -> + binary_to_list(V); +any_to_list(V) when is_integer(V) -> + integer_to_list(V). + +%% +%% Tests. +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +make_test() -> + Identity = make([{hdr, foo}]), + ?assertEqual( + Identity, + make(Identity)). + +enter_from_list_test() -> + H = make([{hdr, foo}]), + ?assertEqual( + [{baz, "wibble"}, {hdr, "foo"}], + to_list(enter_from_list([{baz, wibble}], H))), + ?assertEqual( + [{hdr, "bar"}], + to_list(enter_from_list([{hdr, bar}], H))), + ok. + +default_from_list_test() -> + H = make([{hdr, foo}]), + ?assertEqual( + [{baz, "wibble"}, {hdr, "foo"}], + to_list(default_from_list([{baz, wibble}], H))), + ?assertEqual( + [{hdr, "foo"}], + to_list(default_from_list([{hdr, bar}], H))), + ok. + +get_primary_value_test() -> + H = make([{hdr, foo}, {baz, <<"wibble;taco">>}]), + ?assertEqual( + "foo", + get_primary_value(hdr, H)), + ?assertEqual( + undefined, + get_primary_value(bar, H)), + ?assertEqual( + "wibble", + get_primary_value(<<"baz">>, H)), + ok. + +get_combined_value_test() -> + H = make([{hdr, foo}, {baz, <<"wibble,taco">>}, {content_length, "123, 123"}, + {test, " 123, 123, 123 , 123,123 "}, + {test2, "456, 123, 123 , 123"}, + {test3, "123"}, {test4, " 123, "}]), + ?assertEqual( + "foo", + get_combined_value(hdr, H)), + ?assertEqual( + undefined, + get_combined_value(bar, H)), + ?assertEqual( + undefined, + get_combined_value(<<"baz">>, H)), + ?assertEqual( + "123", + get_combined_value(<<"content_length">>, H)), + ?assertEqual( + "123", + get_combined_value(<<"test">>, H)), + ?assertEqual( + undefined, + get_combined_value(<<"test2">>, H)), + ?assertEqual( + "123", + get_combined_value(<<"test3">>, H)), + ?assertEqual( + "123", + get_combined_value(<<"test4">>, H)), + ok. + +set_cookie_test() -> + H = make([{"set-cookie", foo}, {"set-cookie", bar}, {"set-cookie", baz}]), + ?assertEqual( + [{"set-cookie", "foo"}, {"set-cookie", "bar"}, {"set-cookie", "baz"}], + to_list(H)), + ok. + +headers_test() -> + H = ?MODULE:make([{hdr, foo}, {"Hdr", "bar"}, {'Hdr', 2}]), + [{hdr, "foo, bar, 2"}] = ?MODULE:to_list(H), + H1 = ?MODULE:insert(taco, grande, H), + [{hdr, "foo, bar, 2"}, {taco, "grande"}] = ?MODULE:to_list(H1), + H2 = ?MODULE:make([{"Set-Cookie", "foo"}]), + [{"Set-Cookie", "foo"}] = ?MODULE:to_list(H2), + H3 = ?MODULE:insert("Set-Cookie", "bar", H2), + [{"Set-Cookie", "foo"}, {"Set-Cookie", "bar"}] = ?MODULE:to_list(H3), + "foo, bar" = ?MODULE:get_value("set-cookie", H3), + {value, {"Set-Cookie", "foo, bar"}} = ?MODULE:lookup("set-cookie", H3), + undefined = ?MODULE:get_value("shibby", H3), + none = ?MODULE:lookup("shibby", H3), + H4 = ?MODULE:insert("content-type", + "application/x-www-form-urlencoded; charset=utf8", + H3), + "application/x-www-form-urlencoded" = ?MODULE:get_primary_value( + "content-type", H4), + H4 = ?MODULE:delete_any("nonexistent-header", H4), + H3 = ?MODULE:delete_any("content-type", H4), + HB = <<"Content-Length: 47\r\nContent-Type: text/plain\r\n\r\n">>, + H_HB = ?MODULE:from_binary(HB), + H_HB = ?MODULE:from_binary(binary_to_list(HB)), + "47" = ?MODULE:get_value("Content-Length", H_HB), + "text/plain" = ?MODULE:get_value("Content-Type", H_HB), + L_H_HB = ?MODULE:to_list(H_HB), + 2 = length(L_H_HB), + true = lists:member({'Content-Length', "47"}, L_H_HB), + true = lists:member({'Content-Type', "text/plain"}, L_H_HB), + HL = [ <<"Content-Length: 47\r\n">>, <<"Content-Type: text/plain\r\n">> ], + HL2 = [ "Content-Length: 47\r\n", <<"Content-Type: text/plain\r\n">> ], + HL3 = [ <<"Content-Length: 47\r\n">>, "Content-Type: text/plain\r\n" ], + H_HL = ?MODULE:from_binary(HL), + H_HL = ?MODULE:from_binary(HL2), + H_HL = ?MODULE:from_binary(HL3), + "47" = ?MODULE:get_value("Content-Length", H_HL), + "text/plain" = ?MODULE:get_value("Content-Type", H_HL), + L_H_HL = ?MODULE:to_list(H_HL), + 2 = length(L_H_HL), + true = lists:member({'Content-Length', "47"}, L_H_HL), + true = lists:member({'Content-Type', "text/plain"}, L_H_HL), + [] = ?MODULE:to_list(?MODULE:from_binary(<<>>)), + [] = ?MODULE:to_list(?MODULE:from_binary(<<"">>)), + [] = ?MODULE:to_list(?MODULE:from_binary(<<"\r\n">>)), + [] = ?MODULE:to_list(?MODULE:from_binary(<<"\r\n\r\n">>)), + [] = ?MODULE:to_list(?MODULE:from_binary("")), + [] = ?MODULE:to_list(?MODULE:from_binary([<<>>])), + [] = ?MODULE:to_list(?MODULE:from_binary([<<"">>])), + [] = ?MODULE:to_list(?MODULE:from_binary([<<"\r\n">>])), + [] = ?MODULE:to_list(?MODULE:from_binary([<<"\r\n\r\n">>])), + ok. + +tokenize_header_value_test() -> + ?assertEqual(["a quote in a \"quote\"."], + tokenize_header_value("\"a quote in a \\\"quote\\\".\"")), + ?assertEqual(["abc"], tokenize_header_value("abc")), + ?assertEqual(["abc", "def"], tokenize_header_value("abc def")), + ?assertEqual(["abc", "def"], tokenize_header_value("abc , def")), + ?assertEqual(["abc", "def"], tokenize_header_value(",abc ,, def,,")), + ?assertEqual(["abc def"], tokenize_header_value("\"abc def\" ")), + ?assertEqual(["abc, def"], tokenize_header_value("\"abc, def\"")), + ?assertEqual(["\\a\\$"], tokenize_header_value("\"\\a\\$\"")), + ?assertEqual(["abc def", "foo, bar", "12345", ""], + tokenize_header_value("\"abc def\" \"foo, bar\" , 12345, \"\"")), + ?assertEqual(undefined, + tokenize_header_value(undefined)), + ?assertEqual(undefined, + tokenize_header_value("umatched quote\"")), + ?assertEqual(undefined, + tokenize_header_value("\"unmatched quote")). + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_html.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_html.erl new file mode 100644 index 0000000..3732924 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_html.erl @@ -0,0 +1,774 @@ +%% @author Bob Ippolito +%% @copyright 2007 Mochi Media, Inc. + +%% @doc Loosely tokenizes and generates parse trees for HTML 4. +-module(mochiweb_html). +-export([tokens/1, parse/1, parse_tokens/1, to_tokens/1, escape/1, + escape_attr/1, to_html/1]). +-compile([export_all]). +-ifdef(TEST). +-export([destack/1, destack/2, is_singleton/1]). +-endif. + +%% This is a macro to placate syntax highlighters.. +-define(QUOTE, $\"). %% $\" +-define(SQUOTE, $\'). %% $\' +-define(ADV_COL(S, N), + S#decoder{column=N+S#decoder.column, + offset=N+S#decoder.offset}). +-define(INC_COL(S), + S#decoder{column=1+S#decoder.column, + offset=1+S#decoder.offset}). +-define(INC_LINE(S), + S#decoder{column=1, + line=1+S#decoder.line, + offset=1+S#decoder.offset}). +-define(INC_CHAR(S, C), + case C of + $\n -> + S#decoder{column=1, + line=1+S#decoder.line, + offset=1+S#decoder.offset}; + _ -> + S#decoder{column=1+S#decoder.column, + offset=1+S#decoder.offset} + end). + +-define(IS_WHITESPACE(C), + (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)). +-define(IS_LITERAL_SAFE(C), + ((C >= $A andalso C =< $Z) orelse (C >= $a andalso C =< $z) + orelse (C >= $0 andalso C =< $9))). +-define(PROBABLE_CLOSE(C), + (C =:= $> orelse ?IS_WHITESPACE(C))). + +-record(decoder, {line=1, + column=1, + offset=0}). + +%% @type html_node() = {string(), [html_attr()], [html_node() | string()]} +%% @type html_attr() = {string(), string()} +%% @type html_token() = html_data() | start_tag() | end_tag() | inline_html() | html_comment() | html_doctype() +%% @type html_data() = {data, string(), Whitespace::boolean()} +%% @type start_tag() = {start_tag, Name, [html_attr()], Singleton::boolean()} +%% @type end_tag() = {end_tag, Name} +%% @type html_comment() = {comment, Comment} +%% @type html_doctype() = {doctype, [Doctype]} +%% @type inline_html() = {'=', iolist()} + +%% External API. + +%% @spec parse(string() | binary()) -> html_node() +%% @doc tokenize and then transform the token stream into a HTML tree. +parse(Input) -> + parse_tokens(tokens(Input)). + +%% @spec parse_tokens([html_token()]) -> html_node() +%% @doc Transform the output of tokens(Doc) into a HTML tree. +parse_tokens(Tokens) when is_list(Tokens) -> + %% Skip over doctype, processing instructions + [{start_tag, Tag, Attrs, false} | Rest] = find_document(Tokens, normal), + {Tree, _} = tree(Rest, [norm({Tag, Attrs})]), + Tree. + +find_document(Tokens=[{start_tag, _Tag, _Attrs, false} | _Rest], Mode) -> + maybe_add_html_tag(Tokens, Mode); +find_document([{doctype, [<<"html">>]} | Rest], _Mode) -> + find_document(Rest, html5); +find_document([_T | Rest], Mode) -> + find_document(Rest, Mode); +find_document([], _Mode) -> + []. + +maybe_add_html_tag(Tokens=[{start_tag, Tag, _Attrs, false} | _], html5) + when Tag =/= <<"html">> -> + [{start_tag, <<"html">>, [], false} | Tokens]; +maybe_add_html_tag(Tokens, _Mode) -> + Tokens. + +%% @spec tokens(StringOrBinary) -> [html_token()] +%% @doc Transform the input UTF-8 HTML into a token stream. +tokens(Input) -> + tokens(iolist_to_binary(Input), #decoder{}, []). + +%% @spec to_tokens(html_node()) -> [html_token()] +%% @doc Convert a html_node() tree to a list of tokens. +to_tokens({Tag0}) -> + to_tokens({Tag0, [], []}); +to_tokens(T={'=', _}) -> + [T]; +to_tokens(T={doctype, _}) -> + [T]; +to_tokens(T={comment, _}) -> + [T]; +to_tokens({Tag0, Acc}) -> + %% This is only allowed in sub-tags: {p, [{"class", "foo"}]} + to_tokens({Tag0, [], Acc}); +to_tokens({Tag0, Attrs, Acc}) -> + Tag = to_tag(Tag0), + case is_singleton(Tag) of + true -> + to_tokens([], [{start_tag, Tag, Attrs, true}]); + false -> + to_tokens([{Tag, Acc}], [{start_tag, Tag, Attrs, false}]) + end. + +%% @spec to_html([html_token()] | html_node()) -> iolist() +%% @doc Convert a list of html_token() to a HTML document. +to_html(Node) when is_tuple(Node) -> + to_html(to_tokens(Node)); +to_html(Tokens) when is_list(Tokens) -> + to_html(Tokens, []). + +%% @spec escape(string() | atom() | binary()) -> binary() +%% @doc Escape a string such that it's safe for HTML (amp; lt; gt;). +escape(B) when is_binary(B) -> + escape(binary_to_list(B), []); +escape(A) when is_atom(A) -> + escape(atom_to_list(A), []); +escape(S) when is_list(S) -> + escape(S, []). + +%% @spec escape_attr(string() | binary() | atom() | integer() | float()) -> binary() +%% @doc Escape a string such that it's safe for HTML attrs +%% (amp; lt; gt; quot;). +escape_attr(B) when is_binary(B) -> + escape_attr(binary_to_list(B), []); +escape_attr(A) when is_atom(A) -> + escape_attr(atom_to_list(A), []); +escape_attr(S) when is_list(S) -> + escape_attr(S, []); +escape_attr(I) when is_integer(I) -> + escape_attr(integer_to_list(I), []); +escape_attr(F) when is_float(F) -> + escape_attr(mochinum:digits(F), []). + +to_html([], Acc) -> + lists:reverse(Acc); +to_html([{'=', Content} | Rest], Acc) -> + to_html(Rest, [Content | Acc]); +to_html([{pi, Bin} | Rest], Acc) -> + Open = [<<">, + Bin, + <<"?>">>], + to_html(Rest, [Open | Acc]); +to_html([{pi, Tag, Attrs} | Rest], Acc) -> + Open = [<<">, + Tag, + attrs_to_html(Attrs, []), + <<"?>">>], + to_html(Rest, [Open | Acc]); +to_html([{comment, Comment} | Rest], Acc) -> + to_html(Rest, [[<<"">>] | Acc]); +to_html([{doctype, Parts} | Rest], Acc) -> + Inside = doctype_to_html(Parts, Acc), + to_html(Rest, [[<<">, Inside, <<">">>] | Acc]); +to_html([{data, Data, _Whitespace} | Rest], Acc) -> + to_html(Rest, [escape(Data) | Acc]); +to_html([{start_tag, Tag, Attrs, Singleton} | Rest], Acc) -> + Open = [<<"<">>, + Tag, + attrs_to_html(Attrs, []), + case Singleton of + true -> <<" />">>; + false -> <<">">> + end], + to_html(Rest, [Open | Acc]); +to_html([{end_tag, Tag} | Rest], Acc) -> + to_html(Rest, [[<<">, Tag, <<">">>] | Acc]). + +doctype_to_html([], Acc) -> + lists:reverse(Acc); +doctype_to_html([Word | Rest], Acc) -> + case lists:all(fun (C) -> ?IS_LITERAL_SAFE(C) end, + binary_to_list(iolist_to_binary(Word))) of + true -> + doctype_to_html(Rest, [[<<" ">>, Word] | Acc]); + false -> + doctype_to_html(Rest, [[<<" \"">>, escape_attr(Word), ?QUOTE] | Acc]) + end. + +attrs_to_html([], Acc) -> + lists:reverse(Acc); +attrs_to_html([{K, V} | Rest], Acc) -> + attrs_to_html(Rest, + [[<<" ">>, escape(K), <<"=\"">>, + escape_attr(V), <<"\"">>] | Acc]). + +escape([], Acc) -> + list_to_binary(lists:reverse(Acc)); +escape("<" ++ Rest, Acc) -> + escape(Rest, lists:reverse("<", Acc)); +escape(">" ++ Rest, Acc) -> + escape(Rest, lists:reverse(">", Acc)); +escape("&" ++ Rest, Acc) -> + escape(Rest, lists:reverse("&", Acc)); +escape([C | Rest], Acc) -> + escape(Rest, [C | Acc]). + +escape_attr([], Acc) -> + list_to_binary(lists:reverse(Acc)); +escape_attr("<" ++ Rest, Acc) -> + escape_attr(Rest, lists:reverse("<", Acc)); +escape_attr(">" ++ Rest, Acc) -> + escape_attr(Rest, lists:reverse(">", Acc)); +escape_attr("&" ++ Rest, Acc) -> + escape_attr(Rest, lists:reverse("&", Acc)); +escape_attr([?QUOTE | Rest], Acc) -> + escape_attr(Rest, lists:reverse(""", Acc)); +escape_attr([C | Rest], Acc) -> + escape_attr(Rest, [C | Acc]). + +to_tag(A) when is_atom(A) -> + norm(atom_to_list(A)); +to_tag(L) -> + norm(L). + +to_tokens([], Acc) -> + lists:reverse(Acc); +to_tokens([{Tag, []} | Rest], Acc) -> + to_tokens(Rest, [{end_tag, to_tag(Tag)} | Acc]); +to_tokens([{Tag0, [{T0} | R1]} | Rest], Acc) -> + %% Allow {br} + to_tokens([{Tag0, [{T0, [], []} | R1]} | Rest], Acc); +to_tokens([{Tag0, [T0={'=', _C0} | R1]} | Rest], Acc) -> + %% Allow {'=', iolist()} + to_tokens([{Tag0, R1} | Rest], [T0 | Acc]); +to_tokens([{Tag0, [T0={comment, _C0} | R1]} | Rest], Acc) -> + %% Allow {comment, iolist()} + to_tokens([{Tag0, R1} | Rest], [T0 | Acc]); +to_tokens([{Tag0, [T0={pi, _S0} | R1]} | Rest], Acc) -> + %% Allow {pi, binary()} + to_tokens([{Tag0, R1} | Rest], [T0 | Acc]); +to_tokens([{Tag0, [T0={pi, _S0, _A0} | R1]} | Rest], Acc) -> + %% Allow {pi, binary(), list()} + to_tokens([{Tag0, R1} | Rest], [T0 | Acc]); +to_tokens([{Tag0, [{T0, A0=[{_, _} | _]} | R1]} | Rest], Acc) -> + %% Allow {p, [{"class", "foo"}]} + to_tokens([{Tag0, [{T0, A0, []} | R1]} | Rest], Acc); +to_tokens([{Tag0, [{T0, C0} | R1]} | Rest], Acc) -> + %% Allow {p, "content"} and {p, <<"content">>} + to_tokens([{Tag0, [{T0, [], C0} | R1]} | Rest], Acc); +to_tokens([{Tag0, [{T0, A1, C0} | R1]} | Rest], Acc) when is_binary(C0) -> + %% Allow {"p", [{"class", "foo"}], <<"content">>} + to_tokens([{Tag0, [{T0, A1, binary_to_list(C0)} | R1]} | Rest], Acc); +to_tokens([{Tag0, [{T0, A1, C0=[C | _]} | R1]} | Rest], Acc) + when is_integer(C) -> + %% Allow {"p", [{"class", "foo"}], "content"} + to_tokens([{Tag0, [{T0, A1, [C0]} | R1]} | Rest], Acc); +to_tokens([{Tag0, [{T0, A1, C1} | R1]} | Rest], Acc) -> + %% Native {"p", [{"class", "foo"}], ["content"]} + Tag = to_tag(Tag0), + T1 = to_tag(T0), + case is_singleton(norm(T1)) of + true -> + to_tokens([{Tag, R1} | Rest], [{start_tag, T1, A1, true} | Acc]); + false -> + to_tokens([{T1, C1}, {Tag, R1} | Rest], + [{start_tag, T1, A1, false} | Acc]) + end; +to_tokens([{Tag0, [L | R1]} | Rest], Acc) when is_list(L) -> + %% List text + Tag = to_tag(Tag0), + to_tokens([{Tag, R1} | Rest], [{data, iolist_to_binary(L), false} | Acc]); +to_tokens([{Tag0, [B | R1]} | Rest], Acc) when is_binary(B) -> + %% Binary text + Tag = to_tag(Tag0), + to_tokens([{Tag, R1} | Rest], [{data, B, false} | Acc]). + +tokens(B, S=#decoder{offset=O}, Acc) -> + case B of + <<_:O/binary>> -> + lists:reverse(Acc); + _ -> + {Tag, S1} = tokenize(B, S), + case parse_flag(Tag) of + script -> + {Tag2, S2} = tokenize_script(B, S1), + tokens(B, S2, [Tag2, Tag | Acc]); + textarea -> + {Tag2, S2} = tokenize_textarea(B, S1), + tokens(B, S2, [Tag2, Tag | Acc]); + none -> + tokens(B, S1, [Tag | Acc]) + end + end. + +parse_flag({start_tag, B, _, false}) -> + case string:to_lower(binary_to_list(B)) of + "script" -> + script; + "textarea" -> + textarea; + _ -> + none + end; +parse_flag(_) -> + none. + +tokenize(B, S=#decoder{offset=O}) -> + case B of + <<_:O/binary, "", _/binary>> -> + Len = O - Start, + <<_:Start/binary, Raw:Len/binary, _/binary>> = Bin, + {{comment, Raw}, ?ADV_COL(S, 3)}; + <<_:O/binary, C, _/binary>> -> + tokenize_comment(Bin, ?INC_CHAR(S, C), Start); + <<_:Start/binary, Raw/binary>> -> + {{comment, Raw}, S} + end. + +tokenize_script(Bin, S=#decoder{offset=O}) -> + tokenize_script(Bin, S, O). + +tokenize_script(Bin, S=#decoder{offset=O}, Start) -> + case Bin of + %% Just a look-ahead, we want the end_tag separately + <<_:O/binary, $<, $/, SS, CC, RR, II, PP, TT, ZZ, _/binary>> + when (SS =:= $s orelse SS =:= $S) andalso + (CC =:= $c orelse CC =:= $C) andalso + (RR =:= $r orelse RR =:= $R) andalso + (II =:= $i orelse II =:= $I) andalso + (PP =:= $p orelse PP =:= $P) andalso + (TT=:= $t orelse TT =:= $T) andalso + ?PROBABLE_CLOSE(ZZ) -> + Len = O - Start, + <<_:Start/binary, Raw:Len/binary, _/binary>> = Bin, + {{data, Raw, false}, S}; + <<_:O/binary, C, _/binary>> -> + tokenize_script(Bin, ?INC_CHAR(S, C), Start); + <<_:Start/binary, Raw/binary>> -> + {{data, Raw, false}, S} + end. + +tokenize_textarea(Bin, S=#decoder{offset=O}) -> + tokenize_textarea(Bin, S, O). + +tokenize_textarea(Bin, S=#decoder{offset=O}, Start) -> + case Bin of + %% Just a look-ahead, we want the end_tag separately + <<_:O/binary, $<, $/, TT, EE, XX, TT2, AA, RR, EE2, AA2, ZZ, _/binary>> + when (TT =:= $t orelse TT =:= $T) andalso + (EE =:= $e orelse EE =:= $E) andalso + (XX =:= $x orelse XX =:= $X) andalso + (TT2 =:= $t orelse TT2 =:= $T) andalso + (AA =:= $a orelse AA =:= $A) andalso + (RR =:= $r orelse RR =:= $R) andalso + (EE2 =:= $e orelse EE2 =:= $E) andalso + (AA2 =:= $a orelse AA2 =:= $A) andalso + ?PROBABLE_CLOSE(ZZ) -> + Len = O - Start, + <<_:Start/binary, Raw:Len/binary, _/binary>> = Bin, + {{data, Raw, false}, S}; + <<_:O/binary, C, _/binary>> -> + tokenize_textarea(Bin, ?INC_CHAR(S, C), Start); + <<_:Start/binary, Raw/binary>> -> + {{data, Raw, false}, S} + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_http.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_http.erl new file mode 100644 index 0000000..ae6410f --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_http.erl @@ -0,0 +1,268 @@ +%% @author Bob Ippolito +%% @copyright 2007 Mochi Media, Inc. + +%% @doc HTTP server. + +-module(mochiweb_http). +-author('bob@mochimedia.com'). +-export([start/1, start_link/1, stop/0, stop/1]). +-export([loop/2]). +-export([after_response/2, reentry/1]). +-export([parse_range_request/1, range_skip_length/2]). + +-define(REQUEST_RECV_TIMEOUT, 300000). %% timeout waiting for request line +-define(HEADERS_RECV_TIMEOUT, 30000). %% timeout waiting for headers + +-define(MAX_HEADERS, 1000). +-define(DEFAULTS, [{name, ?MODULE}, + {port, 8888}]). + +parse_options(Options) -> + {loop, HttpLoop} = proplists:lookup(loop, Options), + Loop = {?MODULE, loop, [HttpLoop]}, + Options1 = [{loop, Loop} | proplists:delete(loop, Options)], + mochilists:set_defaults(?DEFAULTS, Options1). + +stop() -> + mochiweb_socket_server:stop(?MODULE). + +stop(Name) -> + mochiweb_socket_server:stop(Name). + +%% @spec start(Options) -> ServerRet +%% Options = [option()] +%% Option = {name, atom()} | {ip, string() | tuple()} | {backlog, integer()} +%% | {nodelay, boolean()} | {acceptor_pool_size, integer()} +%% | {ssl, boolean()} | {profile_fun, undefined | (Props) -> ok} +%% | {link, false} +%% @doc Start a mochiweb server. +%% profile_fun is used to profile accept timing. +%% After each accept, if defined, profile_fun is called with a proplist of a subset of the mochiweb_socket_server state and timing information. +%% The proplist is as follows: [{name, Name}, {port, Port}, {active_sockets, ActiveSockets}, {timing, Timing}]. +%% @end +start(Options) -> + mochiweb_socket_server:start(parse_options(Options)). + +start_link(Options) -> + mochiweb_socket_server:start_link(parse_options(Options)). + +loop(Socket, Body) -> + ok = mochiweb_socket:setopts(Socket, [{packet, http}]), + request(Socket, Body). + +-ifdef(gen_tcp_r15b_workaround). +-define(R15B_GEN_TCP_FIX, {tcp_error,_,emsgsize} -> + % R15B02 returns this then closes the socket, so close and exit + mochiweb_socket:close(Socket), + exit(normal); + ). +-else. +-define(R15B_GEN_TCP_FIX,). +-endif. + +request(Socket, Body) -> + ok = mochiweb_socket:setopts(Socket, [{active, once}]), + receive + {Protocol, _, {http_request, Method, Path, Version}} when Protocol == http orelse Protocol == ssl -> + ok = mochiweb_socket:setopts(Socket, [{packet, httph}]), + headers(Socket, {Method, Path, Version}, [], Body, 0); + {Protocol, _, {http_error, "\r\n"}} when Protocol == http orelse Protocol == ssl -> + request(Socket, Body); + {Protocol, _, {http_error, "\n"}} when Protocol == http orelse Protocol == ssl -> + request(Socket, Body); + {tcp_closed, _} -> + mochiweb_socket:close(Socket), + exit(normal); + {ssl_closed, _} -> + mochiweb_socket:close(Socket), + exit(normal); + ?R15B_GEN_TCP_FIX + _Other -> + handle_invalid_request(Socket) + after ?REQUEST_RECV_TIMEOUT -> + mochiweb_socket:close(Socket), + exit(normal) + end. + +reentry(Body) -> + fun (Req) -> + ?MODULE:after_response(Body, Req) + end. + +headers(Socket, Request, Headers, _Body, ?MAX_HEADERS) -> + %% Too many headers sent, bad request. + ok = mochiweb_socket:setopts(Socket, [{packet, raw}]), + handle_invalid_request(Socket, Request, Headers); +headers(Socket, Request, Headers, Body, HeaderCount) -> + ok = mochiweb_socket:setopts(Socket, [{active, once}]), + receive + {Protocol, _, http_eoh} when Protocol == http orelse Protocol == ssl -> + Req = new_request(Socket, Request, Headers), + call_body(Body, Req), + ?MODULE:after_response(Body, Req); + {Protocol, _, {http_header, _, Name, _, Value}} when Protocol == http orelse Protocol == ssl -> + headers(Socket, Request, [{Name, Value} | Headers], Body, + 1 + HeaderCount); + {tcp_closed, _} -> + mochiweb_socket:close(Socket), + exit(normal); + ?R15B_GEN_TCP_FIX + _Other -> + handle_invalid_request(Socket, Request, Headers) + after ?HEADERS_RECV_TIMEOUT -> + mochiweb_socket:close(Socket), + exit(normal) + end. + +call_body({M, F, A}, Req) -> + erlang:apply(M, F, [Req | A]); +call_body({M, F}, Req) -> + M:F(Req); +call_body(Body, Req) -> + Body(Req). + +%% -spec handle_invalid_request(term()) -> no_return(). +handle_invalid_request(Socket) -> + handle_invalid_request(Socket, {'GET', {abs_path, "/"}, {0,9}}, []), + exit(normal). + +%% -spec handle_invalid_request(term(), term(), term()) -> no_return(). +handle_invalid_request(Socket, Request, RevHeaders) -> + Req = new_request(Socket, Request, RevHeaders), + Req:respond({400, [], []}), + mochiweb_socket:close(Socket), + exit(normal). + +new_request(Socket, Request, RevHeaders) -> + ok = mochiweb_socket:setopts(Socket, [{packet, raw}]), + mochiweb:new_request({Socket, Request, lists:reverse(RevHeaders)}). + +after_response(Body, Req) -> + Socket = Req:get(socket), + case Req:should_close() of + true -> + mochiweb_socket:close(Socket), + exit(normal); + false -> + Req:cleanup(), + erlang:garbage_collect(), + ?MODULE:loop(Socket, Body) + end. + +parse_range_request("bytes=0-") -> + undefined; +parse_range_request(RawRange) when is_list(RawRange) -> + try + "bytes=" ++ RangeString = RawRange, + Ranges = string:tokens(RangeString, ","), + lists:map(fun ("-" ++ V) -> + {none, list_to_integer(V)}; + (R) -> + case string:tokens(R, "-") of + [S1, S2] -> + {list_to_integer(S1), list_to_integer(S2)}; + [S] -> + {list_to_integer(S), none} + end + end, + Ranges) + catch + _:_ -> + fail + end. + +range_skip_length(Spec, Size) -> + case Spec of + {none, R} when R =< Size, R >= 0 -> + {Size - R, R}; + {none, _OutOfRange} -> + {0, Size}; + {R, none} when R >= 0, R < Size -> + {R, Size - R}; + {_OutOfRange, none} -> + invalid_range; + {Start, End} when 0 =< Start, Start =< End, End < Size -> + {Start, End - Start + 1}; + {Start, End} when 0 =< Start, Start =< End, End >= Size -> + {Start, Size - Start}; + {_OutOfRange, _End} -> + invalid_range + end. + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +range_test() -> + %% valid, single ranges + ?assertEqual([{20, 30}], parse_range_request("bytes=20-30")), + ?assertEqual([{20, none}], parse_range_request("bytes=20-")), + ?assertEqual([{none, 20}], parse_range_request("bytes=-20")), + + %% trivial single range + ?assertEqual(undefined, parse_range_request("bytes=0-")), + + %% invalid, single ranges + ?assertEqual(fail, parse_range_request("")), + ?assertEqual(fail, parse_range_request("garbage")), + ?assertEqual(fail, parse_range_request("bytes=-20-30")), + + %% valid, multiple range + ?assertEqual( + [{20, 30}, {50, 100}, {110, 200}], + parse_range_request("bytes=20-30,50-100,110-200")), + ?assertEqual( + [{20, none}, {50, 100}, {none, 200}], + parse_range_request("bytes=20-,50-100,-200")), + + %% no ranges + ?assertEqual([], parse_range_request("bytes=")), + ok. + +range_skip_length_test() -> + Body = <<"012345678901234567890123456789012345678901234567890123456789">>, + BodySize = byte_size(Body), %% 60 + BodySize = 60, + + %% these values assume BodySize =:= 60 + ?assertEqual({1,9}, range_skip_length({1,9}, BodySize)), %% 1-9 + ?assertEqual({10,10}, range_skip_length({10,19}, BodySize)), %% 10-19 + ?assertEqual({40, 20}, range_skip_length({none, 20}, BodySize)), %% -20 + ?assertEqual({30, 30}, range_skip_length({30, none}, BodySize)), %% 30- + + %% valid edge cases for range_skip_length + ?assertEqual({BodySize, 0}, range_skip_length({none, 0}, BodySize)), + ?assertEqual({0, BodySize}, range_skip_length({none, BodySize}, BodySize)), + ?assertEqual({0, BodySize}, range_skip_length({0, none}, BodySize)), + BodySizeLess1 = BodySize - 1, + ?assertEqual({BodySizeLess1, 1}, + range_skip_length({BodySize - 1, none}, BodySize)), + ?assertEqual({BodySizeLess1, 1}, + range_skip_length({BodySize - 1, BodySize+5}, BodySize)), + ?assertEqual({BodySizeLess1, 1}, + range_skip_length({BodySize - 1, BodySize}, BodySize)), + + %% out of range, return whole thing + ?assertEqual({0, BodySize}, + range_skip_length({none, BodySize + 1}, BodySize)), + ?assertEqual({0, BodySize}, + range_skip_length({none, -1}, BodySize)), + ?assertEqual({0, BodySize}, + range_skip_length({0, BodySize + 1}, BodySize)), + + %% invalid ranges + ?assertEqual(invalid_range, + range_skip_length({-1, 30}, BodySize)), + ?assertEqual(invalid_range, + range_skip_length({-1, BodySize + 1}, BodySize)), + ?assertEqual(invalid_range, + range_skip_length({BodySize, 40}, BodySize)), + ?assertEqual(invalid_range, + range_skip_length({-1, none}, BodySize)), + ?assertEqual(invalid_range, + range_skip_length({BodySize, none}, BodySize)), + ok. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_io.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_io.erl new file mode 100644 index 0000000..8454b43 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_io.erl @@ -0,0 +1,43 @@ +%% @author Bob Ippolito +%% @copyright 2007 Mochi Media, Inc. + +%% @doc Utilities for dealing with IO devices (open files). + +-module(mochiweb_io). +-author('bob@mochimedia.com'). + +-export([iodevice_stream/3, iodevice_stream/2]). +-export([iodevice_foldl/4, iodevice_foldl/3]). +-export([iodevice_size/1]). +-define(READ_SIZE, 8192). + +iodevice_foldl(F, Acc, IoDevice) -> + iodevice_foldl(F, Acc, IoDevice, ?READ_SIZE). + +iodevice_foldl(F, Acc, IoDevice, BufferSize) -> + case file:read(IoDevice, BufferSize) of + eof -> + Acc; + {ok, Data} -> + iodevice_foldl(F, F(Data, Acc), IoDevice, BufferSize) + end. + +iodevice_stream(Callback, IoDevice) -> + iodevice_stream(Callback, IoDevice, ?READ_SIZE). + +iodevice_stream(Callback, IoDevice, BufferSize) -> + F = fun (Data, ok) -> Callback(Data) end, + ok = iodevice_foldl(F, ok, IoDevice, BufferSize). + +iodevice_size(IoDevice) -> + {ok, Size} = file:position(IoDevice, eof), + {ok, 0} = file:position(IoDevice, bof), + Size. + + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_mime.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_mime.erl new file mode 100644 index 0000000..7d9f249 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_mime.erl @@ -0,0 +1,415 @@ +%% @author Bob Ippolito +%% @copyright 2007 Mochi Media, Inc. + +%% @doc Gives a good MIME type guess based on file extension. + +-module(mochiweb_mime). +-author('bob@mochimedia.com'). +-export([from_extension/1]). + +%% @spec from_extension(S::string()) -> string() | undefined +%% @doc Given a filename extension (e.g. ".html") return a guess for the MIME +%% type such as "text/html". Will return the atom undefined if no good +%% guess is available. + +from_extension(".stl") -> + "application/SLA"; +from_extension(".stp") -> + "application/STEP"; +from_extension(".step") -> + "application/STEP"; +from_extension(".dwg") -> + "application/acad"; +from_extension(".ez") -> + "application/andrew-inset"; +from_extension(".ccad") -> + "application/clariscad"; +from_extension(".drw") -> + "application/drafting"; +from_extension(".tsp") -> + "application/dsptype"; +from_extension(".dxf") -> + "application/dxf"; +from_extension(".xls") -> + "application/excel"; +from_extension(".unv") -> + "application/i-deas"; +from_extension(".jar") -> + "application/java-archive"; +from_extension(".hqx") -> + "application/mac-binhex40"; +from_extension(".cpt") -> + "application/mac-compactpro"; +from_extension(".pot") -> + "application/vnd.ms-powerpoint"; +from_extension(".ppt") -> + "application/vnd.ms-powerpoint"; +from_extension(".dms") -> + "application/octet-stream"; +from_extension(".lha") -> + "application/octet-stream"; +from_extension(".lzh") -> + "application/octet-stream"; +from_extension(".oda") -> + "application/oda"; +from_extension(".ogg") -> + "application/ogg"; +from_extension(".ogm") -> + "application/ogg"; +from_extension(".pdf") -> + "application/pdf"; +from_extension(".pgp") -> + "application/pgp"; +from_extension(".ai") -> + "application/postscript"; +from_extension(".eps") -> + "application/postscript"; +from_extension(".ps") -> + "application/postscript"; +from_extension(".prt") -> + "application/pro_eng"; +from_extension(".rtf") -> + "application/rtf"; +from_extension(".smi") -> + "application/smil"; +from_extension(".smil") -> + "application/smil"; +from_extension(".sol") -> + "application/solids"; +from_extension(".vda") -> + "application/vda"; +from_extension(".xlm") -> + "application/vnd.ms-excel"; +from_extension(".cod") -> + "application/vnd.rim.cod"; +from_extension(".pgn") -> + "application/x-chess-pgn"; +from_extension(".cpio") -> + "application/x-cpio"; +from_extension(".csh") -> + "application/x-csh"; +from_extension(".deb") -> + "application/x-debian-package"; +from_extension(".dcr") -> + "application/x-director"; +from_extension(".dir") -> + "application/x-director"; +from_extension(".dxr") -> + "application/x-director"; +from_extension(".gz") -> + "application/x-gzip"; +from_extension(".hdf") -> + "application/x-hdf"; +from_extension(".ipx") -> + "application/x-ipix"; +from_extension(".ips") -> + "application/x-ipscript"; +from_extension(".js") -> + "application/x-javascript"; +from_extension(".skd") -> + "application/x-koan"; +from_extension(".skm") -> + "application/x-koan"; +from_extension(".skp") -> + "application/x-koan"; +from_extension(".skt") -> + "application/x-koan"; +from_extension(".latex") -> + "application/x-latex"; +from_extension(".lsp") -> + "application/x-lisp"; +from_extension(".scm") -> + "application/x-lotusscreencam"; +from_extension(".mif") -> + "application/x-mif"; +from_extension(".com") -> + "application/x-msdos-program"; +from_extension(".exe") -> + "application/octet-stream"; +from_extension(".cdf") -> + "application/x-netcdf"; +from_extension(".nc") -> + "application/x-netcdf"; +from_extension(".pl") -> + "application/x-perl"; +from_extension(".pm") -> + "application/x-perl"; +from_extension(".rar") -> + "application/x-rar-compressed"; +from_extension(".sh") -> + "application/x-sh"; +from_extension(".shar") -> + "application/x-shar"; +from_extension(".swf") -> + "application/x-shockwave-flash"; +from_extension(".sit") -> + "application/x-stuffit"; +from_extension(".sv4cpio") -> + "application/x-sv4cpio"; +from_extension(".sv4crc") -> + "application/x-sv4crc"; +from_extension(".tar.gz") -> + "application/x-tar-gz"; +from_extension(".tgz") -> + "application/x-tar-gz"; +from_extension(".tar") -> + "application/x-tar"; +from_extension(".tcl") -> + "application/x-tcl"; +from_extension(".texi") -> + "application/x-texinfo"; +from_extension(".texinfo") -> + "application/x-texinfo"; +from_extension(".man") -> + "application/x-troff-man"; +from_extension(".me") -> + "application/x-troff-me"; +from_extension(".ms") -> + "application/x-troff-ms"; +from_extension(".roff") -> + "application/x-troff"; +from_extension(".t") -> + "application/x-troff"; +from_extension(".tr") -> + "application/x-troff"; +from_extension(".ustar") -> + "application/x-ustar"; +from_extension(".src") -> + "application/x-wais-source"; +from_extension(".zip") -> + "application/zip"; +from_extension(".tsi") -> + "audio/TSP-audio"; +from_extension(".au") -> + "audio/basic"; +from_extension(".snd") -> + "audio/basic"; +from_extension(".kar") -> + "audio/midi"; +from_extension(".mid") -> + "audio/midi"; +from_extension(".midi") -> + "audio/midi"; +from_extension(".mp2") -> + "audio/mpeg"; +from_extension(".mp3") -> + "audio/mpeg"; +from_extension(".mpga") -> + "audio/mpeg"; +from_extension(".aif") -> + "audio/x-aiff"; +from_extension(".aifc") -> + "audio/x-aiff"; +from_extension(".aiff") -> + "audio/x-aiff"; +from_extension(".m3u") -> + "audio/x-mpegurl"; +from_extension(".wax") -> + "audio/x-ms-wax"; +from_extension(".wma") -> + "audio/x-ms-wma"; +from_extension(".rpm") -> + "audio/x-pn-realaudio-plugin"; +from_extension(".ram") -> + "audio/x-pn-realaudio"; +from_extension(".rm") -> + "audio/x-pn-realaudio"; +from_extension(".ra") -> + "audio/x-realaudio"; +from_extension(".wav") -> + "audio/x-wav"; +from_extension(".pdb") -> + "chemical/x-pdb"; +from_extension(".ras") -> + "image/cmu-raster"; +from_extension(".gif") -> + "image/gif"; +from_extension(".ief") -> + "image/ief"; +from_extension(".jpe") -> + "image/jpeg"; +from_extension(".jpeg") -> + "image/jpeg"; +from_extension(".jpg") -> + "image/jpeg"; +from_extension(".jp2") -> + "image/jp2"; +from_extension(".png") -> + "image/png"; +from_extension(".tif") -> + "image/tiff"; +from_extension(".tiff") -> + "image/tiff"; +from_extension(".pnm") -> + "image/x-portable-anymap"; +from_extension(".pbm") -> + "image/x-portable-bitmap"; +from_extension(".pgm") -> + "image/x-portable-graymap"; +from_extension(".ppm") -> + "image/x-portable-pixmap"; +from_extension(".rgb") -> + "image/x-rgb"; +from_extension(".xbm") -> + "image/x-xbitmap"; +from_extension(".xwd") -> + "image/x-xwindowdump"; +from_extension(".iges") -> + "model/iges"; +from_extension(".igs") -> + "model/iges"; +from_extension(".mesh") -> + "model/mesh"; +from_extension(".") -> + ""; +from_extension(".msh") -> + "model/mesh"; +from_extension(".silo") -> + "model/mesh"; +from_extension(".vrml") -> + "model/vrml"; +from_extension(".wrl") -> + "model/vrml"; +from_extension(".css") -> + "text/css"; +from_extension(".htm") -> + "text/html"; +from_extension(".html") -> + "text/html"; +from_extension(".asc") -> + "text/plain"; +from_extension(".c") -> + "text/plain"; +from_extension(".cc") -> + "text/plain"; +from_extension(".f90") -> + "text/plain"; +from_extension(".f") -> + "text/plain"; +from_extension(".hh") -> + "text/plain"; +from_extension(".m") -> + "text/plain"; +from_extension(".txt") -> + "text/plain"; +from_extension(".rtx") -> + "text/richtext"; +from_extension(".sgm") -> + "text/sgml"; +from_extension(".sgml") -> + "text/sgml"; +from_extension(".tsv") -> + "text/tab-separated-values"; +from_extension(".jad") -> + "text/vnd.sun.j2me.app-descriptor"; +from_extension(".etx") -> + "text/x-setext"; +from_extension(".xml") -> + "application/xml"; +from_extension(".dl") -> + "video/dl"; +from_extension(".fli") -> + "video/fli"; +from_extension(".flv") -> + "video/x-flv"; +from_extension(".gl") -> + "video/gl"; +from_extension(".mp4") -> + "video/mp4"; +from_extension(".mpe") -> + "video/mpeg"; +from_extension(".mpeg") -> + "video/mpeg"; +from_extension(".mpg") -> + "video/mpeg"; +from_extension(".mov") -> + "video/quicktime"; +from_extension(".qt") -> + "video/quicktime"; +from_extension(".viv") -> + "video/vnd.vivo"; +from_extension(".vivo") -> + "video/vnd.vivo"; +from_extension(".asf") -> + "video/x-ms-asf"; +from_extension(".asx") -> + "video/x-ms-asx"; +from_extension(".wmv") -> + "video/x-ms-wmv"; +from_extension(".wmx") -> + "video/x-ms-wmx"; +from_extension(".wvx") -> + "video/x-ms-wvx"; +from_extension(".avi") -> + "video/x-msvideo"; +from_extension(".movie") -> + "video/x-sgi-movie"; +from_extension(".mime") -> + "www/mime"; +from_extension(".ice") -> + "x-conference/x-cooltalk"; +from_extension(".vrm") -> + "x-world/x-vrml"; +from_extension(".spx") -> + "audio/ogg"; +from_extension(".xhtml") -> + "application/xhtml+xml"; +from_extension(".bz2") -> + "application/x-bzip2"; +from_extension(".doc") -> + "application/msword"; +from_extension(".z") -> + "application/x-compress"; +from_extension(".ico") -> + "image/x-icon"; +from_extension(".bmp") -> + "image/bmp"; +from_extension(".m4a") -> + "audio/mpeg"; +from_extension(".csv") -> + "text/csv"; +from_extension(".eot") -> + "application/vnd.ms-fontobject"; +from_extension(".m4v") -> + "video/mp4"; +from_extension(".svg") -> + "image/svg+xml"; +from_extension(".svgz") -> + "image/svg+xml"; +from_extension(".ttc") -> + "application/x-font-ttf"; +from_extension(".ttf") -> + "application/x-font-ttf"; +from_extension(".vcf") -> + "text/x-vcard"; +from_extension(".webm") -> + "video/web"; +from_extension(".webp") -> + "image/web"; +from_extension(".woff") -> + "application/x-font-woff"; +from_extension(".otf") -> + "font/opentype"; +from_extension(_) -> + undefined. + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +exhaustive_from_extension_test() -> + T = mochiweb_cover:clause_lookup_table(?MODULE, from_extension), + [?assertEqual(V, from_extension(K)) || {K, V} <- T]. + +from_extension_test() -> + ?assertEqual("text/html", + from_extension(".html")), + ?assertEqual(undefined, + from_extension("")), + ?assertEqual(undefined, + from_extension(".wtf")), + ok. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_multipart.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_multipart.erl new file mode 100644 index 0000000..a4857d6 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_multipart.erl @@ -0,0 +1,872 @@ +%% @author Bob Ippolito +%% @copyright 2007 Mochi Media, Inc. + +%% @doc Utilities for parsing multipart/form-data. + +-module(mochiweb_multipart). +-author('bob@mochimedia.com'). + +-export([parse_form/1, parse_form/2]). +-export([parse_multipart_request/2]). +-export([parts_to_body/3, parts_to_multipart_body/4]). +-export([default_file_handler/2]). + +-define(CHUNKSIZE, 4096). + +-record(mp, {state, boundary, length, buffer, callback, req}). + +%% TODO: DOCUMENT THIS MODULE. +%% @type key() = atom() | string() | binary(). +%% @type value() = atom() | iolist() | integer(). +%% @type header() = {key(), value()}. +%% @type bodypart() = {Start::integer(), End::integer(), Body::iolist()}. +%% @type formfile() = {Name::string(), ContentType::string(), Content::binary()}. +%% @type request(). +%% @type file_handler() = (Filename::string(), ContentType::string()) -> file_handler_callback(). +%% @type file_handler_callback() = (binary() | eof) -> file_handler_callback() | term(). + +%% @spec parts_to_body([bodypart()], ContentType::string(), +%% Size::integer()) -> {[header()], iolist()} +%% @doc Return {[header()], iolist()} representing the body for the given +%% parts, may be a single part or multipart. +parts_to_body([{Start, End, Body}], ContentType, Size) -> + HeaderList = [{"Content-Type", ContentType}, + {"Content-Range", + ["bytes ", + mochiweb_util:make_io(Start), "-", mochiweb_util:make_io(End), + "/", mochiweb_util:make_io(Size)]}], + {HeaderList, Body}; +parts_to_body(BodyList, ContentType, Size) when is_list(BodyList) -> + parts_to_multipart_body(BodyList, ContentType, Size, + mochihex:to_hex(mochiweb_util:rand_bytes(8))). + +%% @spec parts_to_multipart_body([bodypart()], ContentType::string(), +%% Size::integer(), Boundary::string()) -> +%% {[header()], iolist()} +%% @doc Return {[header()], iolist()} representing the body for the given +%% parts, always a multipart response. +parts_to_multipart_body(BodyList, ContentType, Size, Boundary) -> + HeaderList = [{"Content-Type", + ["multipart/byteranges; ", + "boundary=", Boundary]}], + MultiPartBody = multipart_body(BodyList, ContentType, Boundary, Size), + + {HeaderList, MultiPartBody}. + +%% @spec multipart_body([bodypart()], ContentType::string(), +%% Boundary::string(), Size::integer()) -> iolist() +%% @doc Return the representation of a multipart body for the given [bodypart()]. +multipart_body([], _ContentType, Boundary, _Size) -> + ["--", Boundary, "--\r\n"]; +multipart_body([{Start, End, Body} | BodyList], ContentType, Boundary, Size) -> + ["--", Boundary, "\r\n", + "Content-Type: ", ContentType, "\r\n", + "Content-Range: ", + "bytes ", mochiweb_util:make_io(Start), "-", mochiweb_util:make_io(End), + "/", mochiweb_util:make_io(Size), "\r\n\r\n", + Body, "\r\n" + | multipart_body(BodyList, ContentType, Boundary, Size)]. + +%% @spec parse_form(request()) -> [{string(), string() | formfile()}] +%% @doc Parse a multipart form from the given request using the in-memory +%% default_file_handler/2. +parse_form(Req) -> + parse_form(Req, fun default_file_handler/2). + +%% @spec parse_form(request(), F::file_handler()) -> [{string(), string() | term()}] +%% @doc Parse a multipart form from the given request using the given file_handler(). +parse_form(Req, FileHandler) -> + Callback = fun (Next) -> parse_form_outer(Next, FileHandler, []) end, + {_, _, Res} = parse_multipart_request(Req, Callback), + Res. + +parse_form_outer(eof, _, Acc) -> + lists:reverse(Acc); +parse_form_outer({headers, H}, FileHandler, State) -> + {"form-data", H1} = proplists:get_value("content-disposition", H), + Name = proplists:get_value("name", H1), + Filename = proplists:get_value("filename", H1), + case Filename of + undefined -> + fun (Next) -> + parse_form_value(Next, {Name, []}, FileHandler, State) + end; + _ -> + ContentType = proplists:get_value("content-type", H), + Handler = FileHandler(Filename, ContentType), + fun (Next) -> + parse_form_file(Next, {Name, Handler}, FileHandler, State) + end + end. + +parse_form_value(body_end, {Name, Acc}, FileHandler, State) -> + Value = binary_to_list(iolist_to_binary(lists:reverse(Acc))), + State1 = [{Name, Value} | State], + fun (Next) -> parse_form_outer(Next, FileHandler, State1) end; +parse_form_value({body, Data}, {Name, Acc}, FileHandler, State) -> + Acc1 = [Data | Acc], + fun (Next) -> parse_form_value(Next, {Name, Acc1}, FileHandler, State) end. + +parse_form_file(body_end, {Name, Handler}, FileHandler, State) -> + Value = Handler(eof), + State1 = [{Name, Value} | State], + fun (Next) -> parse_form_outer(Next, FileHandler, State1) end; +parse_form_file({body, Data}, {Name, Handler}, FileHandler, State) -> + H1 = Handler(Data), + fun (Next) -> parse_form_file(Next, {Name, H1}, FileHandler, State) end. + +default_file_handler(Filename, ContentType) -> + default_file_handler_1(Filename, ContentType, []). + +default_file_handler_1(Filename, ContentType, Acc) -> + fun(eof) -> + Value = iolist_to_binary(lists:reverse(Acc)), + {Filename, ContentType, Value}; + (Next) -> + default_file_handler_1(Filename, ContentType, [Next | Acc]) + end. + +parse_multipart_request(Req, Callback) -> + %% TODO: Support chunked? + Length = list_to_integer(Req:get_combined_header_value("content-length")), + Boundary = iolist_to_binary( + get_boundary(Req:get_header_value("content-type"))), + Prefix = <<"\r\n--", Boundary/binary>>, + BS = byte_size(Boundary), + Chunk = read_chunk(Req, Length), + Length1 = Length - byte_size(Chunk), + <<"--", Boundary:BS/binary, "\r\n", Rest/binary>> = Chunk, + feed_mp(headers, flash_multipart_hack(#mp{boundary=Prefix, + length=Length1, + buffer=Rest, + callback=Callback, + req=Req})). + +parse_headers(<<>>) -> + []; +parse_headers(Binary) -> + parse_headers(Binary, []). + +parse_headers(Binary, Acc) -> + case find_in_binary(<<"\r\n">>, Binary) of + {exact, N} -> + <> = Binary, + parse_headers(Rest, [split_header(Line) | Acc]); + not_found -> + lists:reverse([split_header(Binary) | Acc]) + end. + +split_header(Line) -> + {Name, [$: | Value]} = lists:splitwith(fun (C) -> C =/= $: end, + binary_to_list(Line)), + {string:to_lower(string:strip(Name)), + mochiweb_util:parse_header(Value)}. + +read_chunk(Req, Length) when Length > 0 -> + case Length of + Length when Length < ?CHUNKSIZE -> + Req:recv(Length); + _ -> + Req:recv(?CHUNKSIZE) + end. + +read_more(State=#mp{length=Length, buffer=Buffer, req=Req}) -> + Data = read_chunk(Req, Length), + Buffer1 = <>, + flash_multipart_hack(State#mp{length=Length - byte_size(Data), + buffer=Buffer1}). + +flash_multipart_hack(State=#mp{length=0, buffer=Buffer, boundary=Prefix}) -> + %% http://code.google.com/p/mochiweb/issues/detail?id=22 + %% Flash doesn't terminate multipart with \r\n properly so we fix it up here + PrefixSize = size(Prefix), + case size(Buffer) - (2 + PrefixSize) of + Seek when Seek >= 0 -> + case Buffer of + <<_:Seek/binary, Prefix:PrefixSize/binary, "--">> -> + Buffer1 = <>, + State#mp{buffer=Buffer1}; + _ -> + State + end; + _ -> + State + end; +flash_multipart_hack(State) -> + State. + +feed_mp(headers, State=#mp{buffer=Buffer, callback=Callback}) -> + {State1, P} = case find_in_binary(<<"\r\n\r\n">>, Buffer) of + {exact, N} -> + {State, N}; + _ -> + S1 = read_more(State), + %% Assume headers must be less than ?CHUNKSIZE + {exact, N} = find_in_binary(<<"\r\n\r\n">>, + S1#mp.buffer), + {S1, N} + end, + <> = State1#mp.buffer, + NextCallback = Callback({headers, parse_headers(Headers)}), + feed_mp(body, State1#mp{buffer=Rest, + callback=NextCallback}); +feed_mp(body, State=#mp{boundary=Prefix, buffer=Buffer, callback=Callback}) -> + Boundary = find_boundary(Prefix, Buffer), + case Boundary of + {end_boundary, Start, Skip} -> + <> = Buffer, + C1 = Callback({body, Data}), + C2 = C1(body_end), + {State#mp.length, Rest, C2(eof)}; + {next_boundary, Start, Skip} -> + <> = Buffer, + C1 = Callback({body, Data}), + feed_mp(headers, State#mp{callback=C1(body_end), + buffer=Rest}); + {maybe, Start} -> + <> = Buffer, + feed_mp(body, read_more(State#mp{callback=Callback({body, Data}), + buffer=Rest})); + not_found -> + {Data, Rest} = {Buffer, <<>>}, + feed_mp(body, read_more(State#mp{callback=Callback({body, Data}), + buffer=Rest})) + end. + +get_boundary(ContentType) -> + {"multipart/form-data", Opts} = mochiweb_util:parse_header(ContentType), + case proplists:get_value("boundary", Opts) of + S when is_list(S) -> + S + end. + +%% @spec find_in_binary(Pattern::binary(), Data::binary()) -> +%% {exact, N} | {partial, N, K} | not_found +%% @doc Searches for the given pattern in the given binary. +find_in_binary(P, Data) when size(P) > 0 -> + PS = size(P), + DS = size(Data), + case DS - PS of + Last when Last < 0 -> + partial_find(P, Data, 0, DS); + Last -> + case binary:match(Data, P) of + {Pos, _} -> {exact, Pos}; + nomatch -> partial_find(P, Data, Last+1, PS-1) + end + end. + +partial_find(_B, _D, _N, 0) -> + not_found; +partial_find(B, D, N, K) -> + <> = B, + case D of + <<_Skip:N/binary, B1:K/binary>> -> + {partial, N, K}; + _ -> + partial_find(B, D, 1 + N, K - 1) + end. + +find_boundary(Prefix, Data) -> + case find_in_binary(Prefix, Data) of + {exact, Skip} -> + PrefixSkip = Skip + size(Prefix), + case Data of + <<_:PrefixSkip/binary, "\r\n", _/binary>> -> + {next_boundary, Skip, size(Prefix) + 2}; + <<_:PrefixSkip/binary, "--\r\n", _/binary>> -> + {end_boundary, Skip, size(Prefix) + 4}; + _ when size(Data) < PrefixSkip + 4 -> + %% Underflow + {maybe, Skip}; + _ -> + %% False positive + not_found + end; + {partial, Skip, Length} when (Skip + Length) =:= size(Data) -> + %% Underflow + {maybe, Skip}; + _ -> + not_found + end. + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +ssl_cert_opts() -> + EbinDir = filename:dirname(code:which(?MODULE)), + CertDir = filename:join([EbinDir, "..", "support", "test-materials"]), + CertFile = filename:join(CertDir, "test_ssl_cert.pem"), + KeyFile = filename:join(CertDir, "test_ssl_key.pem"), + [{certfile, CertFile}, {keyfile, KeyFile}]. + +with_socket_server(Transport, ServerFun, ClientFun) -> + ServerOpts0 = [{ip, "127.0.0.1"}, {port, 0}, {loop, ServerFun}], + ServerOpts = case Transport of + plain -> + ServerOpts0; + ssl -> + ServerOpts0 ++ [{ssl, true}, {ssl_opts, ssl_cert_opts()}] + end, + {ok, Server} = mochiweb_socket_server:start_link(ServerOpts), + Port = mochiweb_socket_server:get(Server, port), + ClientOpts = [binary, {active, false}], + {ok, Client} = case Transport of + plain -> + gen_tcp:connect("127.0.0.1", Port, ClientOpts); + ssl -> + ClientOpts1 = [{ssl_imp, new} | ClientOpts], + {ok, SslSocket} = ssl:connect("127.0.0.1", Port, ClientOpts1), + {ok, {ssl, SslSocket}} + end, + Res = (catch ClientFun(Client)), + mochiweb_socket_server:stop(Server), + Res. + +fake_request(Socket, ContentType, Length) -> + mochiweb_request:new(Socket, + 'POST', + "/multipart", + {1,1}, + mochiweb_headers:make( + [{"content-type", ContentType}, + {"content-length", Length}])). + +test_callback({body, <<>>}, Rest=[body_end | _]) -> + %% When expecting the body_end we might get an empty binary + fun (Next) -> test_callback(Next, Rest) end; +test_callback({body, Got}, [{body, Expect} | Rest]) when Got =/= Expect -> + %% Partial response + GotSize = size(Got), + <> = Expect, + fun (Next) -> test_callback(Next, [{body, Expect1} | Rest]) end; +test_callback(Got, [Expect | Rest]) -> + ?assertEqual(Got, Expect), + case Rest of + [] -> + ok; + _ -> + fun (Next) -> test_callback(Next, Rest) end + end. + +parse3_http_test() -> + parse3(plain). + +parse3_https_test() -> + parse3(ssl). + +parse3(Transport) -> + ContentType = "multipart/form-data; boundary=---------------------------7386909285754635891697677882", + BinContent = <<"-----------------------------7386909285754635891697677882\r\nContent-Disposition: form-data; name=\"hidden\"\r\n\r\nmultipart message\r\n-----------------------------7386909285754635891697677882\r\nContent-Disposition: form-data; name=\"file\"; filename=\"test_file.txt\"\r\nContent-Type: text/plain\r\n\r\nWoo multiline text file\n\nLa la la\r\n-----------------------------7386909285754635891697677882--\r\n">>, + Expect = [{headers, + [{"content-disposition", + {"form-data", [{"name", "hidden"}]}}]}, + {body, <<"multipart message">>}, + body_end, + {headers, + [{"content-disposition", + {"form-data", [{"name", "file"}, {"filename", "test_file.txt"}]}}, + {"content-type", {"text/plain", []}}]}, + {body, <<"Woo multiline text file\n\nLa la la">>}, + body_end, + eof], + TestCallback = fun (Next) -> test_callback(Next, Expect) end, + ServerFun = fun (Socket) -> + ok = mochiweb_socket:send(Socket, BinContent), + exit(normal) + end, + ClientFun = fun (Socket) -> + Req = fake_request(Socket, ContentType, + byte_size(BinContent)), + Res = parse_multipart_request(Req, TestCallback), + {0, <<>>, ok} = Res, + ok + end, + ok = with_socket_server(Transport, ServerFun, ClientFun), + ok. + +parse2_http_test() -> + parse2(plain). + +parse2_https_test() -> + parse2(ssl). + +parse2(Transport) -> + ContentType = "multipart/form-data; boundary=---------------------------6072231407570234361599764024", + BinContent = <<"-----------------------------6072231407570234361599764024\r\nContent-Disposition: form-data; name=\"hidden\"\r\n\r\nmultipart message\r\n-----------------------------6072231407570234361599764024\r\nContent-Disposition: form-data; name=\"file\"; filename=\"\"\r\nContent-Type: application/octet-stream\r\n\r\n\r\n-----------------------------6072231407570234361599764024--\r\n">>, + Expect = [{headers, + [{"content-disposition", + {"form-data", [{"name", "hidden"}]}}]}, + {body, <<"multipart message">>}, + body_end, + {headers, + [{"content-disposition", + {"form-data", [{"name", "file"}, {"filename", ""}]}}, + {"content-type", {"application/octet-stream", []}}]}, + {body, <<>>}, + body_end, + eof], + TestCallback = fun (Next) -> test_callback(Next, Expect) end, + ServerFun = fun (Socket) -> + ok = mochiweb_socket:send(Socket, BinContent), + exit(normal) + end, + ClientFun = fun (Socket) -> + Req = fake_request(Socket, ContentType, + byte_size(BinContent)), + Res = parse_multipart_request(Req, TestCallback), + {0, <<>>, ok} = Res, + ok + end, + ok = with_socket_server(Transport, ServerFun, ClientFun), + ok. + +parse_form_http_test() -> + do_parse_form(plain). + +parse_form_https_test() -> + do_parse_form(ssl). + +do_parse_form(Transport) -> + ContentType = "multipart/form-data; boundary=AaB03x", + "AaB03x" = get_boundary(ContentType), + Content = mochiweb_util:join( + ["--AaB03x", + "Content-Disposition: form-data; name=\"submit-name\"", + "", + "Larry", + "--AaB03x", + "Content-Disposition: form-data; name=\"files\";" + ++ "filename=\"file1.txt\"", + "Content-Type: text/plain", + "", + "... contents of file1.txt ...", + "--AaB03x--", + ""], "\r\n"), + BinContent = iolist_to_binary(Content), + ServerFun = fun (Socket) -> + ok = mochiweb_socket:send(Socket, BinContent), + exit(normal) + end, + ClientFun = fun (Socket) -> + Req = fake_request(Socket, ContentType, + byte_size(BinContent)), + Res = parse_form(Req), + [{"submit-name", "Larry"}, + {"files", {"file1.txt", {"text/plain",[]}, + <<"... contents of file1.txt ...">>} + }] = Res, + ok + end, + ok = with_socket_server(Transport, ServerFun, ClientFun), + ok. + +parse_http_test() -> + do_parse(plain). + +parse_https_test() -> + do_parse(ssl). + +do_parse(Transport) -> + ContentType = "multipart/form-data; boundary=AaB03x", + "AaB03x" = get_boundary(ContentType), + Content = mochiweb_util:join( + ["--AaB03x", + "Content-Disposition: form-data; name=\"submit-name\"", + "", + "Larry", + "--AaB03x", + "Content-Disposition: form-data; name=\"files\";" + ++ "filename=\"file1.txt\"", + "Content-Type: text/plain", + "", + "... contents of file1.txt ...", + "--AaB03x--", + ""], "\r\n"), + BinContent = iolist_to_binary(Content), + Expect = [{headers, + [{"content-disposition", + {"form-data", [{"name", "submit-name"}]}}]}, + {body, <<"Larry">>}, + body_end, + {headers, + [{"content-disposition", + {"form-data", [{"name", "files"}, {"filename", "file1.txt"}]}}, + {"content-type", {"text/plain", []}}]}, + {body, <<"... contents of file1.txt ...">>}, + body_end, + eof], + TestCallback = fun (Next) -> test_callback(Next, Expect) end, + ServerFun = fun (Socket) -> + ok = mochiweb_socket:send(Socket, BinContent), + exit(normal) + end, + ClientFun = fun (Socket) -> + Req = fake_request(Socket, ContentType, + byte_size(BinContent)), + Res = parse_multipart_request(Req, TestCallback), + {0, <<>>, ok} = Res, + ok + end, + ok = with_socket_server(Transport, ServerFun, ClientFun), + ok. + +parse_partial_body_boundary_http_test() -> + parse_partial_body_boundary(plain). + +parse_partial_body_boundary_https_test() -> + parse_partial_body_boundary(ssl). + +parse_partial_body_boundary(Transport) -> + Boundary = string:copies("$", 2048), + ContentType = "multipart/form-data; boundary=" ++ Boundary, + ?assertEqual(Boundary, get_boundary(ContentType)), + Content = mochiweb_util:join( + ["--" ++ Boundary, + "Content-Disposition: form-data; name=\"submit-name\"", + "", + "Larry", + "--" ++ Boundary, + "Content-Disposition: form-data; name=\"files\";" + ++ "filename=\"file1.txt\"", + "Content-Type: text/plain", + "", + "... contents of file1.txt ...", + "--" ++ Boundary ++ "--", + ""], "\r\n"), + BinContent = iolist_to_binary(Content), + Expect = [{headers, + [{"content-disposition", + {"form-data", [{"name", "submit-name"}]}}]}, + {body, <<"Larry">>}, + body_end, + {headers, + [{"content-disposition", + {"form-data", [{"name", "files"}, {"filename", "file1.txt"}]}}, + {"content-type", {"text/plain", []}} + ]}, + {body, <<"... contents of file1.txt ...">>}, + body_end, + eof], + TestCallback = fun (Next) -> test_callback(Next, Expect) end, + ServerFun = fun (Socket) -> + ok = mochiweb_socket:send(Socket, BinContent), + exit(normal) + end, + ClientFun = fun (Socket) -> + Req = fake_request(Socket, ContentType, + byte_size(BinContent)), + Res = parse_multipart_request(Req, TestCallback), + {0, <<>>, ok} = Res, + ok + end, + ok = with_socket_server(Transport, ServerFun, ClientFun), + ok. + +parse_large_header_http_test() -> + parse_large_header(plain). + +parse_large_header_https_test() -> + parse_large_header(ssl). + +parse_large_header(Transport) -> + ContentType = "multipart/form-data; boundary=AaB03x", + "AaB03x" = get_boundary(ContentType), + Content = mochiweb_util:join( + ["--AaB03x", + "Content-Disposition: form-data; name=\"submit-name\"", + "", + "Larry", + "--AaB03x", + "Content-Disposition: form-data; name=\"files\";" + ++ "filename=\"file1.txt\"", + "Content-Type: text/plain", + "x-large-header: " ++ string:copies("%", 4096), + "", + "... contents of file1.txt ...", + "--AaB03x--", + ""], "\r\n"), + BinContent = iolist_to_binary(Content), + Expect = [{headers, + [{"content-disposition", + {"form-data", [{"name", "submit-name"}]}}]}, + {body, <<"Larry">>}, + body_end, + {headers, + [{"content-disposition", + {"form-data", [{"name", "files"}, {"filename", "file1.txt"}]}}, + {"content-type", {"text/plain", []}}, + {"x-large-header", {string:copies("%", 4096), []}} + ]}, + {body, <<"... contents of file1.txt ...">>}, + body_end, + eof], + TestCallback = fun (Next) -> test_callback(Next, Expect) end, + ServerFun = fun (Socket) -> + ok = mochiweb_socket:send(Socket, BinContent), + exit(normal) + end, + ClientFun = fun (Socket) -> + Req = fake_request(Socket, ContentType, + byte_size(BinContent)), + Res = parse_multipart_request(Req, TestCallback), + {0, <<>>, ok} = Res, + ok + end, + ok = with_socket_server(Transport, ServerFun, ClientFun), + ok. + +find_boundary_test() -> + B = <<"\r\n--X">>, + {next_boundary, 0, 7} = find_boundary(B, <<"\r\n--X\r\nRest">>), + {next_boundary, 1, 7} = find_boundary(B, <<"!\r\n--X\r\nRest">>), + {end_boundary, 0, 9} = find_boundary(B, <<"\r\n--X--\r\nRest">>), + {end_boundary, 1, 9} = find_boundary(B, <<"!\r\n--X--\r\nRest">>), + not_found = find_boundary(B, <<"--X\r\nRest">>), + {maybe, 0} = find_boundary(B, <<"\r\n--X\r">>), + {maybe, 1} = find_boundary(B, <<"!\r\n--X\r">>), + P = <<"\r\n-----------------------------16037454351082272548568224146">>, + B0 = <<55,212,131,77,206,23,216,198,35,87,252,118,252,8,25,211,132,229, + 182,42,29,188,62,175,247,243,4,4,0,59, 13,10,45,45,45,45,45,45,45, + 45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45, + 49,54,48,51,55,52,53,52,51,53,49>>, + {maybe, 30} = find_boundary(P, B0), + not_found = find_boundary(B, <<"\r\n--XJOPKE">>), + ok. + +find_in_binary_test() -> + {exact, 0} = find_in_binary(<<"foo">>, <<"foobarbaz">>), + {exact, 1} = find_in_binary(<<"oo">>, <<"foobarbaz">>), + {exact, 8} = find_in_binary(<<"z">>, <<"foobarbaz">>), + not_found = find_in_binary(<<"q">>, <<"foobarbaz">>), + {partial, 7, 2} = find_in_binary(<<"azul">>, <<"foobarbaz">>), + {exact, 0} = find_in_binary(<<"foobarbaz">>, <<"foobarbaz">>), + {partial, 0, 3} = find_in_binary(<<"foobar">>, <<"foo">>), + {partial, 1, 3} = find_in_binary(<<"foobar">>, <<"afoo">>), + ok. + +flash_parse_http_test() -> + flash_parse(plain). + +flash_parse_https_test() -> + flash_parse(ssl). + +flash_parse(Transport) -> + ContentType = "multipart/form-data; boundary=----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5", + "----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5" = get_boundary(ContentType), + BinContent = <<"------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"Filename\"\r\n\r\nhello.txt\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"success_action_status\"\r\n\r\n201\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"file\"; filename=\"hello.txt\"\r\nContent-Type: application/octet-stream\r\n\r\nhello\n\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"Upload\"\r\n\r\nSubmit Query\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5--">>, + Expect = [{headers, + [{"content-disposition", + {"form-data", [{"name", "Filename"}]}}]}, + {body, <<"hello.txt">>}, + body_end, + {headers, + [{"content-disposition", + {"form-data", [{"name", "success_action_status"}]}}]}, + {body, <<"201">>}, + body_end, + {headers, + [{"content-disposition", + {"form-data", [{"name", "file"}, {"filename", "hello.txt"}]}}, + {"content-type", {"application/octet-stream", []}}]}, + {body, <<"hello\n">>}, + body_end, + {headers, + [{"content-disposition", + {"form-data", [{"name", "Upload"}]}}]}, + {body, <<"Submit Query">>}, + body_end, + eof], + TestCallback = fun (Next) -> test_callback(Next, Expect) end, + ServerFun = fun (Socket) -> + ok = mochiweb_socket:send(Socket, BinContent), + exit(normal) + end, + ClientFun = fun (Socket) -> + Req = fake_request(Socket, ContentType, + byte_size(BinContent)), + Res = parse_multipart_request(Req, TestCallback), + {0, <<>>, ok} = Res, + ok + end, + ok = with_socket_server(Transport, ServerFun, ClientFun), + ok. + +flash_parse2_http_test() -> + flash_parse2(plain). + +flash_parse2_https_test() -> + flash_parse2(ssl). + +flash_parse2(Transport) -> + ContentType = "multipart/form-data; boundary=----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5", + "----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5" = get_boundary(ContentType), + Chunk = iolist_to_binary(string:copies("%", 4096)), + BinContent = <<"------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"Filename\"\r\n\r\nhello.txt\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"success_action_status\"\r\n\r\n201\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"file\"; filename=\"hello.txt\"\r\nContent-Type: application/octet-stream\r\n\r\n", Chunk/binary, "\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"Upload\"\r\n\r\nSubmit Query\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5--">>, + Expect = [{headers, + [{"content-disposition", + {"form-data", [{"name", "Filename"}]}}]}, + {body, <<"hello.txt">>}, + body_end, + {headers, + [{"content-disposition", + {"form-data", [{"name", "success_action_status"}]}}]}, + {body, <<"201">>}, + body_end, + {headers, + [{"content-disposition", + {"form-data", [{"name", "file"}, {"filename", "hello.txt"}]}}, + {"content-type", {"application/octet-stream", []}}]}, + {body, Chunk}, + body_end, + {headers, + [{"content-disposition", + {"form-data", [{"name", "Upload"}]}}]}, + {body, <<"Submit Query">>}, + body_end, + eof], + TestCallback = fun (Next) -> test_callback(Next, Expect) end, + ServerFun = fun (Socket) -> + ok = mochiweb_socket:send(Socket, BinContent), + exit(normal) + end, + ClientFun = fun (Socket) -> + Req = fake_request(Socket, ContentType, + byte_size(BinContent)), + Res = parse_multipart_request(Req, TestCallback), + {0, <<>>, ok} = Res, + ok + end, + ok = with_socket_server(Transport, ServerFun, ClientFun), + ok. + +parse_headers_test() -> + ?assertEqual([], parse_headers(<<>>)). + +flash_multipart_hack_test() -> + Buffer = <<"prefix-">>, + Prefix = <<"prefix">>, + State = #mp{length=0, buffer=Buffer, boundary=Prefix}, + ?assertEqual(State, + flash_multipart_hack(State)). + +parts_to_body_single_test() -> + {HL, B} = parts_to_body([{0, 5, <<"01234">>}], + "text/plain", + 10), + [{"Content-Range", Range}, + {"Content-Type", Type}] = lists:sort(HL), + ?assertEqual( + <<"bytes 0-5/10">>, + iolist_to_binary(Range)), + ?assertEqual( + <<"text/plain">>, + iolist_to_binary(Type)), + ?assertEqual( + <<"01234">>, + iolist_to_binary(B)), + ok. + +parts_to_body_multi_test() -> + {[{"Content-Type", Type}], + _B} = parts_to_body([{0, 5, <<"01234">>}, {5, 10, <<"56789">>}], + "text/plain", + 10), + ?assertMatch( + <<"multipart/byteranges; boundary=", _/binary>>, + iolist_to_binary(Type)), + ok. + +parts_to_multipart_body_test() -> + {[{"Content-Type", V}], B} = parts_to_multipart_body( + [{0, 5, <<"01234">>}, {5, 10, <<"56789">>}], + "text/plain", + 10, + "BOUNDARY"), + MB = multipart_body( + [{0, 5, <<"01234">>}, {5, 10, <<"56789">>}], + "text/plain", + "BOUNDARY", + 10), + ?assertEqual( + <<"multipart/byteranges; boundary=BOUNDARY">>, + iolist_to_binary(V)), + ?assertEqual( + iolist_to_binary(MB), + iolist_to_binary(B)), + ok. + +multipart_body_test() -> + ?assertEqual( + <<"--BOUNDARY--\r\n">>, + iolist_to_binary(multipart_body([], "text/plain", "BOUNDARY", 0))), + ?assertEqual( + <<"--BOUNDARY\r\n" + "Content-Type: text/plain\r\n" + "Content-Range: bytes 0-5/10\r\n\r\n" + "01234\r\n" + "--BOUNDARY\r\n" + "Content-Type: text/plain\r\n" + "Content-Range: bytes 5-10/10\r\n\r\n" + "56789\r\n" + "--BOUNDARY--\r\n">>, + iolist_to_binary(multipart_body([{0, 5, <<"01234">>}, {5, 10, <<"56789">>}], + "text/plain", + "BOUNDARY", + 10))), + ok. + +%% @todo Move somewhere more appropriate than in the test suite + +multipart_parsing_benchmark_test() -> + run_multipart_parsing_benchmark(1). + +run_multipart_parsing_benchmark(0) -> ok; +run_multipart_parsing_benchmark(N) -> + multipart_parsing_benchmark(), + run_multipart_parsing_benchmark(N-1). + +multipart_parsing_benchmark() -> + ContentType = "multipart/form-data; boundary=----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5", + Chunk = binary:copy(<<"This Is_%Some=Quite0Long4String2Used9For7BenchmarKing.5">>, 102400), + BinContent = <<"------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"Filename\"\r\n\r\nhello.txt\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"success_action_status\"\r\n\r\n201\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"file\"; filename=\"hello.txt\"\r\nContent-Type: application/octet-stream\r\n\r\n", Chunk/binary, "\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"Upload\"\r\n\r\nSubmit Query\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5--">>, + Expect = [{headers, + [{"content-disposition", + {"form-data", [{"name", "Filename"}]}}]}, + {body, <<"hello.txt">>}, + body_end, + {headers, + [{"content-disposition", + {"form-data", [{"name", "success_action_status"}]}}]}, + {body, <<"201">>}, + body_end, + {headers, + [{"content-disposition", + {"form-data", [{"name", "file"}, {"filename", "hello.txt"}]}}, + {"content-type", {"application/octet-stream", []}}]}, + {body, Chunk}, + body_end, + {headers, + [{"content-disposition", + {"form-data", [{"name", "Upload"}]}}]}, + {body, <<"Submit Query">>}, + body_end, + eof], + TestCallback = fun (Next) -> test_callback(Next, Expect) end, + ServerFun = fun (Socket) -> + ok = mochiweb_socket:send(Socket, BinContent), + exit(normal) + end, + ClientFun = fun (Socket) -> + Req = fake_request(Socket, ContentType, + byte_size(BinContent)), + Res = parse_multipart_request(Req, TestCallback), + {0, <<>>, ok} = Res, + ok + end, + ok = with_socket_server(plain, ServerFun, ClientFun), + ok. +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_request.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_request.erl new file mode 100644 index 0000000..d967bdb --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_request.erl @@ -0,0 +1,857 @@ +%% @author Bob Ippolito +%% @copyright 2007 Mochi Media, Inc. + +%% @doc MochiWeb HTTP Request abstraction. + +-module(mochiweb_request). +-author('bob@mochimedia.com'). + +-include_lib("kernel/include/file.hrl"). +-include("internal.hrl"). + +-define(QUIP, "Any of you quaids got a smint?"). + +-export([new/5]). +-export([get_header_value/2, get_primary_header_value/2, get_combined_header_value/2, get/2, dump/1]). +-export([send/2, recv/2, recv/3, recv_body/1, recv_body/2, stream_body/4]). +-export([start_response/2, start_response_length/2, start_raw_response/2]). +-export([respond/2, ok/2]). +-export([not_found/1, not_found/2]). +-export([parse_post/1, parse_qs/1]). +-export([should_close/1, cleanup/1]). +-export([parse_cookie/1, get_cookie_value/2]). +-export([serve_file/3, serve_file/4]). +-export([accepted_encodings/2]). +-export([accepts_content_type/2, accepted_content_types/2]). + +-define(SAVE_QS, mochiweb_request_qs). +-define(SAVE_PATH, mochiweb_request_path). +-define(SAVE_RECV, mochiweb_request_recv). +-define(SAVE_BODY, mochiweb_request_body). +-define(SAVE_BODY_LENGTH, mochiweb_request_body_length). +-define(SAVE_POST, mochiweb_request_post). +-define(SAVE_COOKIE, mochiweb_request_cookie). +-define(SAVE_FORCE_CLOSE, mochiweb_request_force_close). + +%% @type key() = atom() | string() | binary() +%% @type value() = atom() | string() | binary() | integer() +%% @type headers(). A mochiweb_headers structure. +%% @type request(). A mochiweb_request parameterized module instance. +%% @type response(). A mochiweb_response parameterized module instance. +%% @type ioheaders() = headers() | [{key(), value()}]. + +% 5 minute default idle timeout +-define(IDLE_TIMEOUT, 300000). + +% Maximum recv_body() length of 1MB +-define(MAX_RECV_BODY, 104857600). + +%% @spec new(Socket, Method, RawPath, Version, headers()) -> request() +%% @doc Create a new request instance. +new(Socket, Method, RawPath, Version, Headers) -> + {?MODULE, [Socket, Method, RawPath, Version, Headers]}. + +%% @spec get_header_value(K, request()) -> undefined | Value +%% @doc Get the value of a given request header. +get_header_value(K, {?MODULE, [_Socket, _Method, _RawPath, _Version, Headers]}) -> + mochiweb_headers:get_value(K, Headers). + +get_primary_header_value(K, {?MODULE, [_Socket, _Method, _RawPath, _Version, Headers]}) -> + mochiweb_headers:get_primary_value(K, Headers). + +get_combined_header_value(K, {?MODULE, [_Socket, _Method, _RawPath, _Version, Headers]}) -> + mochiweb_headers:get_combined_value(K, Headers). + +%% @type field() = socket | scheme | method | raw_path | version | headers | peer | path | body_length | range + +%% @spec get(field(), request()) -> term() +%% @doc Return the internal representation of the given field. If +%% socket is requested on a HTTPS connection, then +%% an ssl socket will be returned as {ssl, SslSocket}. +%% You can use SslSocket with the ssl +%% application, eg: ssl:peercert(SslSocket). +get(socket, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) -> + Socket; +get(scheme, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) -> + case mochiweb_socket:type(Socket) of + plain -> + http; + ssl -> + https + end; +get(method, {?MODULE, [_Socket, Method, _RawPath, _Version, _Headers]}) -> + Method; +get(raw_path, {?MODULE, [_Socket, _Method, RawPath, _Version, _Headers]}) -> + RawPath; +get(version, {?MODULE, [_Socket, _Method, _RawPath, Version, _Headers]}) -> + Version; +get(headers, {?MODULE, [_Socket, _Method, _RawPath, _Version, Headers]}) -> + Headers; +get(peer, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case mochiweb_socket:peername(Socket) of + {ok, {Addr={10, _, _, _}, _Port}} -> + case get_header_value("x-forwarded-for", THIS) of + undefined -> + inet_parse:ntoa(Addr); + Hosts -> + string:strip(lists:last(string:tokens(Hosts, ","))) + end; + {ok, {{127, 0, 0, 1}, _Port}} -> + case get_header_value("x-forwarded-for", THIS) of + undefined -> + "127.0.0.1"; + Hosts -> + string:strip(lists:last(string:tokens(Hosts, ","))) + end; + {ok, {Addr, _Port}} -> + inet_parse:ntoa(Addr); + {error, enotconn} -> + exit(normal) + end; +get(path, {?MODULE, [_Socket, _Method, RawPath, _Version, _Headers]}) -> + case erlang:get(?SAVE_PATH) of + undefined -> + {Path0, _, _} = mochiweb_util:urlsplit_path(RawPath), + Path = mochiweb_util:unquote(Path0), + put(?SAVE_PATH, Path), + Path; + Cached -> + Cached + end; +get(body_length, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case erlang:get(?SAVE_BODY_LENGTH) of + undefined -> + BodyLength = body_length(THIS), + put(?SAVE_BODY_LENGTH, {cached, BodyLength}), + BodyLength; + {cached, Cached} -> + Cached + end; +get(range, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case get_header_value(range, THIS) of + undefined -> + undefined; + RawRange -> + mochiweb_http:parse_range_request(RawRange) + end. + +%% @spec dump(request()) -> {mochiweb_request, [{atom(), term()}]} +%% @doc Dump the internal representation to a "human readable" set of terms +%% for debugging/inspection purposes. +dump({?MODULE, [_Socket, Method, RawPath, Version, Headers]}) -> + {?MODULE, [{method, Method}, + {version, Version}, + {raw_path, RawPath}, + {headers, mochiweb_headers:to_list(Headers)}]}. + +%% @spec send(iodata(), request()) -> ok +%% @doc Send data over the socket. +send(Data, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) -> + case mochiweb_socket:send(Socket, Data) of + ok -> + ok; + _ -> + exit(normal) + end. + +%% @spec recv(integer(), request()) -> binary() +%% @doc Receive Length bytes from the client as a binary, with the default +%% idle timeout. +recv(Length, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + recv(Length, ?IDLE_TIMEOUT, THIS). + +%% @spec recv(integer(), integer(), request()) -> binary() +%% @doc Receive Length bytes from the client as a binary, with the given +%% Timeout in msec. +recv(Length, Timeout, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) -> + case mochiweb_socket:recv(Socket, Length, Timeout) of + {ok, Data} -> + put(?SAVE_RECV, true), + Data; + _ -> + exit(normal) + end. + +%% @spec body_length(request()) -> undefined | chunked | unknown_transfer_encoding | integer() +%% @doc Infer body length from transfer-encoding and content-length headers. +body_length({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case get_header_value("transfer-encoding", THIS) of + undefined -> + case get_combined_header_value("content-length", THIS) of + undefined -> + undefined; + Length -> + list_to_integer(Length) + end; + "chunked" -> + chunked; + Unknown -> + {unknown_transfer_encoding, Unknown} + end. + + +%% @spec recv_body(request()) -> binary() +%% @doc Receive the body of the HTTP request (defined by Content-Length). +%% Will only receive up to the default max-body length of 1MB. +recv_body({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + recv_body(?MAX_RECV_BODY, THIS). + +%% @spec recv_body(integer(), request()) -> binary() +%% @doc Receive the body of the HTTP request (defined by Content-Length). +%% Will receive up to MaxBody bytes. +recv_body(MaxBody, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case erlang:get(?SAVE_BODY) of + undefined -> + % we could use a sane constant for max chunk size + Body = stream_body(?MAX_RECV_BODY, fun + ({0, _ChunkedFooter}, {_LengthAcc, BinAcc}) -> + iolist_to_binary(lists:reverse(BinAcc)); + ({Length, Bin}, {LengthAcc, BinAcc}) -> + NewLength = Length + LengthAcc, + if NewLength > MaxBody -> + exit({body_too_large, chunked}); + true -> + {NewLength, [Bin | BinAcc]} + end + end, {0, []}, MaxBody, THIS), + put(?SAVE_BODY, Body), + Body; + Cached -> Cached + end. + +stream_body(MaxChunkSize, ChunkFun, FunState, {?MODULE,[_Socket,_Method,_RawPath,_Version,_Headers]}=THIS) -> + stream_body(MaxChunkSize, ChunkFun, FunState, undefined, THIS). + +stream_body(MaxChunkSize, ChunkFun, FunState, MaxBodyLength, + {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + Expect = case get_header_value("expect", THIS) of + undefined -> + undefined; + Value when is_list(Value) -> + string:to_lower(Value) + end, + case Expect of + "100-continue" -> + _ = start_raw_response({100, gb_trees:empty()}, THIS), + ok; + _Else -> + ok + end, + case body_length(THIS) of + undefined -> + undefined; + {unknown_transfer_encoding, Unknown} -> + exit({unknown_transfer_encoding, Unknown}); + chunked -> + % In this case the MaxBody is actually used to + % determine the maximum allowed size of a single + % chunk. + stream_chunked_body(MaxChunkSize, ChunkFun, FunState, THIS); + 0 -> + <<>>; + Length when is_integer(Length) -> + case MaxBodyLength of + MaxBodyLength when is_integer(MaxBodyLength), MaxBodyLength < Length -> + exit({body_too_large, content_length}); + _ -> + stream_unchunked_body(Length, ChunkFun, FunState, THIS) + end + end. + + +%% @spec start_response({integer(), ioheaders()}, request()) -> response() +%% @doc Start the HTTP response by sending the Code HTTP response and +%% ResponseHeaders. The server will set header defaults such as Server +%% and Date if not present in ResponseHeaders. +start_response({Code, ResponseHeaders}, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + HResponse = mochiweb_headers:make(ResponseHeaders), + HResponse1 = mochiweb_headers:default_from_list(server_headers(), + HResponse), + start_raw_response({Code, HResponse1}, THIS). + +%% @spec start_raw_response({integer(), headers()}, request()) -> response() +%% @doc Start the HTTP response by sending the Code HTTP response and +%% ResponseHeaders. +start_raw_response({Code, ResponseHeaders}, {?MODULE, [_Socket, _Method, _RawPath, Version, _Headers]}=THIS) -> + F = fun ({K, V}, Acc) -> + [mochiweb_util:make_io(K), <<": ">>, V, <<"\r\n">> | Acc] + end, + End = lists:foldl(F, [<<"\r\n">>], + mochiweb_headers:to_list(ResponseHeaders)), + send([make_version(Version), make_code(Code), <<"\r\n">> | End], THIS), + mochiweb:new_response({THIS, Code, ResponseHeaders}). + + +%% @spec start_response_length({integer(), ioheaders(), integer()}, request()) -> response() +%% @doc Start the HTTP response by sending the Code HTTP response and +%% ResponseHeaders including a Content-Length of Length. The server +%% will set header defaults such as Server +%% and Date if not present in ResponseHeaders. +start_response_length({Code, ResponseHeaders, Length}, + {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + HResponse = mochiweb_headers:make(ResponseHeaders), + HResponse1 = mochiweb_headers:enter("Content-Length", Length, HResponse), + start_response({Code, HResponse1}, THIS). + +%% @spec respond({integer(), ioheaders(), iodata() | chunked | {file, IoDevice}}, request()) -> response() +%% @doc Start the HTTP response with start_response, and send Body to the +%% client (if the get(method) /= 'HEAD'). The Content-Length header +%% will be set by the Body length, and the server will insert header +%% defaults. +respond({Code, ResponseHeaders, {file, IoDevice}}, + {?MODULE, [_Socket, Method, _RawPath, _Version, _Headers]}=THIS) -> + Length = mochiweb_io:iodevice_size(IoDevice), + Response = start_response_length({Code, ResponseHeaders, Length}, THIS), + case Method of + 'HEAD' -> + ok; + _ -> + mochiweb_io:iodevice_stream( + fun (Body) -> send(Body, THIS) end, + IoDevice) + end, + Response; +respond({Code, ResponseHeaders, chunked}, {?MODULE, [_Socket, Method, _RawPath, Version, _Headers]}=THIS) -> + HResponse = mochiweb_headers:make(ResponseHeaders), + HResponse1 = case Method of + 'HEAD' -> + %% This is what Google does, http://www.google.com/ + %% is chunked but HEAD gets Content-Length: 0. + %% The RFC is ambiguous so emulating Google is smart. + mochiweb_headers:enter("Content-Length", "0", + HResponse); + _ when Version >= {1, 1} -> + %% Only use chunked encoding for HTTP/1.1 + mochiweb_headers:enter("Transfer-Encoding", "chunked", + HResponse); + _ -> + %% For pre-1.1 clients we send the data as-is + %% without a Content-Length header and without + %% chunk delimiters. Since the end of the document + %% is now ambiguous we must force a close. + put(?SAVE_FORCE_CLOSE, true), + HResponse + end, + start_response({Code, HResponse1}, THIS); +respond({Code, ResponseHeaders, Body}, {?MODULE, [_Socket, Method, _RawPath, _Version, _Headers]}=THIS) -> + Response = start_response_length({Code, ResponseHeaders, iolist_size(Body)}, THIS), + case Method of + 'HEAD' -> + ok; + _ -> + send(Body, THIS) + end, + Response. + +%% @spec not_found(request()) -> response() +%% @doc Alias for not_found([]). +not_found({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + not_found([], THIS). + +%% @spec not_found(ExtraHeaders, request()) -> response() +%% @doc Alias for respond({404, [{"Content-Type", "text/plain"} +%% | ExtraHeaders], <<"Not found.">>}). +not_found(ExtraHeaders, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + respond({404, [{"Content-Type", "text/plain"} | ExtraHeaders], + <<"Not found.">>}, THIS). + +%% @spec ok({value(), iodata()} | {value(), ioheaders(), iodata() | {file, IoDevice}}, request()) -> +%% response() +%% @doc respond({200, [{"Content-Type", ContentType} | Headers], Body}). +ok({ContentType, Body}, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + ok({ContentType, [], Body}, THIS); +ok({ContentType, ResponseHeaders, Body}, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + HResponse = mochiweb_headers:make(ResponseHeaders), + case THIS:get(range) of + X when (X =:= undefined orelse X =:= fail) orelse Body =:= chunked -> + %% http://code.google.com/p/mochiweb/issues/detail?id=54 + %% Range header not supported when chunked, return 200 and provide + %% full response. + HResponse1 = mochiweb_headers:enter("Content-Type", ContentType, + HResponse), + respond({200, HResponse1, Body}, THIS); + Ranges -> + {PartList, Size} = range_parts(Body, Ranges), + case PartList of + [] -> %% no valid ranges + HResponse1 = mochiweb_headers:enter("Content-Type", + ContentType, + HResponse), + %% could be 416, for now we'll just return 200 + respond({200, HResponse1, Body}, THIS); + PartList -> + {RangeHeaders, RangeBody} = + mochiweb_multipart:parts_to_body(PartList, ContentType, Size), + HResponse1 = mochiweb_headers:enter_from_list( + [{"Accept-Ranges", "bytes"} | + RangeHeaders], + HResponse), + respond({206, HResponse1, RangeBody}, THIS) + end + end. + +%% @spec should_close(request()) -> bool() +%% @doc Return true if the connection must be closed. If false, using +%% Keep-Alive should be safe. +should_close({?MODULE, [_Socket, _Method, _RawPath, Version, _Headers]}=THIS) -> + ForceClose = erlang:get(?SAVE_FORCE_CLOSE) =/= undefined, + DidNotRecv = erlang:get(?SAVE_RECV) =:= undefined, + ForceClose orelse Version < {1, 0} + %% Connection: close + orelse is_close(get_header_value("connection", THIS)) + %% HTTP 1.0 requires Connection: Keep-Alive + orelse (Version =:= {1, 0} + andalso get_header_value("connection", THIS) =/= "Keep-Alive") + %% unread data left on the socket, can't safely continue + orelse (DidNotRecv + andalso get_combined_header_value("content-length", THIS) =/= undefined + andalso list_to_integer(get_combined_header_value("content-length", THIS)) > 0) + orelse (DidNotRecv + andalso get_header_value("transfer-encoding", THIS) =:= "chunked"). + +is_close("close") -> + true; +is_close(S=[_C, _L, _O, _S, _E]) -> + string:to_lower(S) =:= "close"; +is_close(_) -> + false. + +%% @spec cleanup(request()) -> ok +%% @doc Clean up any junk in the process dictionary, required before continuing +%% a Keep-Alive request. +cleanup({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}) -> + L = [?SAVE_QS, ?SAVE_PATH, ?SAVE_RECV, ?SAVE_BODY, ?SAVE_BODY_LENGTH, + ?SAVE_POST, ?SAVE_COOKIE, ?SAVE_FORCE_CLOSE], + lists:foreach(fun(K) -> + erase(K) + end, L), + ok. + +%% @spec parse_qs(request()) -> [{Key::string(), Value::string()}] +%% @doc Parse the query string of the URL. +parse_qs({?MODULE, [_Socket, _Method, RawPath, _Version, _Headers]}) -> + case erlang:get(?SAVE_QS) of + undefined -> + {_, QueryString, _} = mochiweb_util:urlsplit_path(RawPath), + Parsed = mochiweb_util:parse_qs(QueryString), + put(?SAVE_QS, Parsed), + Parsed; + Cached -> + Cached + end. + +%% @spec get_cookie_value(Key::string, request()) -> string() | undefined +%% @doc Get the value of the given cookie. +get_cookie_value(Key, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + proplists:get_value(Key, parse_cookie(THIS)). + +%% @spec parse_cookie(request()) -> [{Key::string(), Value::string()}] +%% @doc Parse the cookie header. +parse_cookie({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case erlang:get(?SAVE_COOKIE) of + undefined -> + Cookies = case get_header_value("cookie", THIS) of + undefined -> + []; + Value -> + mochiweb_cookies:parse_cookie(Value) + end, + put(?SAVE_COOKIE, Cookies), + Cookies; + Cached -> + Cached + end. + +%% @spec parse_post(request()) -> [{Key::string(), Value::string()}] +%% @doc Parse an application/x-www-form-urlencoded form POST. This +%% has the side-effect of calling recv_body(). +parse_post({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case erlang:get(?SAVE_POST) of + undefined -> + Parsed = case recv_body(THIS) of + undefined -> + []; + Binary -> + case get_primary_header_value("content-type",THIS) of + "application/x-www-form-urlencoded" ++ _ -> + mochiweb_util:parse_qs(Binary); + _ -> + [] + end + end, + put(?SAVE_POST, Parsed), + Parsed; + Cached -> + Cached + end. + +%% @spec stream_chunked_body(integer(), fun(), term(), request()) -> term() +%% @doc The function is called for each chunk. +%% Used internally by read_chunked_body. +stream_chunked_body(MaxChunkSize, Fun, FunState, + {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case read_chunk_length(THIS) of + 0 -> + Fun({0, read_chunk(0, THIS)}, FunState); + Length when Length > MaxChunkSize -> + NewState = read_sub_chunks(Length, MaxChunkSize, Fun, FunState, THIS), + stream_chunked_body(MaxChunkSize, Fun, NewState, THIS); + Length -> + NewState = Fun({Length, read_chunk(Length, THIS)}, FunState), + stream_chunked_body(MaxChunkSize, Fun, NewState, THIS) + end. + +stream_unchunked_body(0, Fun, FunState, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}) -> + Fun({0, <<>>}, FunState); +stream_unchunked_body(Length, Fun, FunState, + {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) when Length > 0 -> + PktSize = case Length > ?RECBUF_SIZE of + true -> + ?RECBUF_SIZE; + false -> + Length + end, + Bin = recv(PktSize, THIS), + NewState = Fun({PktSize, Bin}, FunState), + stream_unchunked_body(Length - PktSize, Fun, NewState, THIS). + +%% @spec read_chunk_length(request()) -> integer() +%% @doc Read the length of the next HTTP chunk. +read_chunk_length({?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) -> + ok = mochiweb_socket:setopts(Socket, [{packet, line}]), + case mochiweb_socket:recv(Socket, 0, ?IDLE_TIMEOUT) of + {ok, Header} -> + ok = mochiweb_socket:setopts(Socket, [{packet, raw}]), + Splitter = fun (C) -> + C =/= $\r andalso C =/= $\n andalso C =/= $ + end, + {Hex, _Rest} = lists:splitwith(Splitter, binary_to_list(Header)), + mochihex:to_int(Hex); + _ -> + exit(normal) + end. + +%% @spec read_chunk(integer(), request()) -> Chunk::binary() | [Footer::binary()] +%% @doc Read in a HTTP chunk of the given length. If Length is 0, then read the +%% HTTP footers (as a list of binaries, since they're nominal). +read_chunk(0, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) -> + ok = mochiweb_socket:setopts(Socket, [{packet, line}]), + F = fun (F1, Acc) -> + case mochiweb_socket:recv(Socket, 0, ?IDLE_TIMEOUT) of + {ok, <<"\r\n">>} -> + Acc; + {ok, Footer} -> + F1(F1, [Footer | Acc]); + _ -> + exit(normal) + end + end, + Footers = F(F, []), + ok = mochiweb_socket:setopts(Socket, [{packet, raw}]), + put(?SAVE_RECV, true), + Footers; +read_chunk(Length, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) -> + case mochiweb_socket:recv(Socket, 2 + Length, ?IDLE_TIMEOUT) of + {ok, <>} -> + Chunk; + _ -> + exit(normal) + end. + +read_sub_chunks(Length, MaxChunkSize, Fun, FunState, + {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) when Length > MaxChunkSize -> + Bin = recv(MaxChunkSize, THIS), + NewState = Fun({size(Bin), Bin}, FunState), + read_sub_chunks(Length - MaxChunkSize, MaxChunkSize, Fun, NewState, THIS); + +read_sub_chunks(Length, _MaxChunkSize, Fun, FunState, + {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + Fun({Length, read_chunk(Length, THIS)}, FunState). + +%% @spec serve_file(Path, DocRoot, request()) -> Response +%% @doc Serve a file relative to DocRoot. +serve_file(Path, DocRoot, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + serve_file(Path, DocRoot, [], THIS). + +%% @spec serve_file(Path, DocRoot, ExtraHeaders, request()) -> Response +%% @doc Serve a file relative to DocRoot. +serve_file(Path, DocRoot, ExtraHeaders, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case mochiweb_util:safe_relative_path(Path) of + undefined -> + not_found(ExtraHeaders, THIS); + RelPath -> + FullPath = filename:join([DocRoot, RelPath]), + case filelib:is_dir(FullPath) of + true -> + maybe_redirect(RelPath, FullPath, ExtraHeaders, THIS); + false -> + maybe_serve_file(FullPath, ExtraHeaders, THIS) + end + end. + +%% Internal API + +%% This has the same effect as the DirectoryIndex directive in httpd +directory_index(FullPath) -> + filename:join([FullPath, "index.html"]). + +maybe_redirect([], FullPath, ExtraHeaders, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + maybe_serve_file(directory_index(FullPath), ExtraHeaders, THIS); + +maybe_redirect(RelPath, FullPath, ExtraHeaders, + {?MODULE, [_Socket, _Method, _RawPath, _Version, Headers]}=THIS) -> + case string:right(RelPath, 1) of + "/" -> + maybe_serve_file(directory_index(FullPath), ExtraHeaders, THIS); + _ -> + Host = mochiweb_headers:get_value("host", Headers), + Location = "http://" ++ Host ++ "/" ++ RelPath ++ "/", + LocationBin = list_to_binary(Location), + MoreHeaders = [{"Location", Location}, + {"Content-Type", "text/html"} | ExtraHeaders], + Top = <<"" + "" + "301 Moved Permanently" + "" + "

Moved Permanently

" + "

The document has moved >, + Bottom = <<">here.

\n">>, + Body = <>, + respond({301, MoreHeaders, Body}, THIS) + end. + +maybe_serve_file(File, ExtraHeaders, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case file:read_file_info(File) of + {ok, FileInfo} -> + LastModified = httpd_util:rfc1123_date(FileInfo#file_info.mtime), + case get_header_value("if-modified-since", THIS) of + LastModified -> + respond({304, ExtraHeaders, ""}, THIS); + _ -> + case file:open(File, [raw, binary]) of + {ok, IoDevice} -> + ContentType = mochiweb_util:guess_mime(File), + Res = ok({ContentType, + [{"last-modified", LastModified} + | ExtraHeaders], + {file, IoDevice}}, THIS), + ok = file:close(IoDevice), + Res; + _ -> + not_found(ExtraHeaders, THIS) + end + end; + {error, _} -> + not_found(ExtraHeaders, THIS) + end. + +server_headers() -> + [{"Server", "MochiWeb/1.0 (" ++ ?QUIP ++ ")"}, + {"Date", httpd_util:rfc1123_date()}]. + +make_code(X) when is_integer(X) -> + [integer_to_list(X), [" " | httpd_util:reason_phrase(X)]]; +make_code(Io) when is_list(Io); is_binary(Io) -> + Io. + +make_version({1, 0}) -> + <<"HTTP/1.0 ">>; +make_version(_) -> + <<"HTTP/1.1 ">>. + +range_parts({file, IoDevice}, Ranges) -> + Size = mochiweb_io:iodevice_size(IoDevice), + F = fun (Spec, Acc) -> + case mochiweb_http:range_skip_length(Spec, Size) of + invalid_range -> + Acc; + V -> + [V | Acc] + end + end, + LocNums = lists:foldr(F, [], Ranges), + {ok, Data} = file:pread(IoDevice, LocNums), + Bodies = lists:zipwith(fun ({Skip, Length}, PartialBody) -> + {Skip, Skip + Length - 1, PartialBody} + end, + LocNums, Data), + {Bodies, Size}; +range_parts(Body0, Ranges) -> + Body = iolist_to_binary(Body0), + Size = size(Body), + F = fun(Spec, Acc) -> + case mochiweb_http:range_skip_length(Spec, Size) of + invalid_range -> + Acc; + {Skip, Length} -> + <<_:Skip/binary, PartialBody:Length/binary, _/binary>> = Body, + [{Skip, Skip + Length - 1, PartialBody} | Acc] + end + end, + {lists:foldr(F, [], Ranges), Size}. + +%% @spec accepted_encodings([encoding()], request()) -> [encoding()] | bad_accept_encoding_value +%% @type encoding() = string(). +%% +%% @doc Returns a list of encodings accepted by a request. Encodings that are +%% not supported by the server will not be included in the return list. +%% This list is computed from the "Accept-Encoding" header and +%% its elements are ordered, descendingly, according to their Q values. +%% +%% Section 14.3 of the RFC 2616 (HTTP 1.1) describes the "Accept-Encoding" +%% header and the process of determining which server supported encodings +%% can be used for encoding the body for the request's response. +%% +%% Examples +%% +%% 1) For a missing "Accept-Encoding" header: +%% accepted_encodings(["gzip", "identity"]) -> ["identity"] +%% +%% 2) For an "Accept-Encoding" header with value "gzip, deflate": +%% accepted_encodings(["gzip", "identity"]) -> ["gzip", "identity"] +%% +%% 3) For an "Accept-Encoding" header with value "gzip;q=0.5, deflate": +%% accepted_encodings(["gzip", "deflate", "identity"]) -> +%% ["deflate", "gzip", "identity"] +%% +accepted_encodings(SupportedEncodings, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + AcceptEncodingHeader = case get_header_value("Accept-Encoding", THIS) of + undefined -> + ""; + Value -> + Value + end, + case mochiweb_util:parse_qvalues(AcceptEncodingHeader) of + invalid_qvalue_string -> + bad_accept_encoding_value; + QList -> + mochiweb_util:pick_accepted_encodings( + QList, SupportedEncodings, "identity" + ) + end. + +%% @spec accepts_content_type(string() | binary(), request()) -> boolean() | bad_accept_header +%% +%% @doc Determines whether a request accepts a given media type by analyzing its +%% "Accept" header. +%% +%% Examples +%% +%% 1) For a missing "Accept" header: +%% accepts_content_type("application/json") -> true +%% +%% 2) For an "Accept" header with value "text/plain, application/*": +%% accepts_content_type("application/json") -> true +%% +%% 3) For an "Accept" header with value "text/plain, */*; q=0.0": +%% accepts_content_type("application/json") -> false +%% +%% 4) For an "Accept" header with value "text/plain; q=0.5, */*; q=0.1": +%% accepts_content_type("application/json") -> true +%% +%% 5) For an "Accept" header with value "text/*; q=0.0, */*": +%% accepts_content_type("text/plain") -> false +%% +accepts_content_type(ContentType1, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + ContentType = re:replace(ContentType1, "\\s", "", [global, {return, list}]), + AcceptHeader = accept_header(THIS), + case mochiweb_util:parse_qvalues(AcceptHeader) of + invalid_qvalue_string -> + bad_accept_header; + QList -> + [MainType, _SubType] = string:tokens(ContentType, "/"), + SuperType = MainType ++ "/*", + lists:any( + fun({"*/*", Q}) when Q > 0.0 -> + true; + ({Type, Q}) when Q > 0.0 -> + Type =:= ContentType orelse Type =:= SuperType; + (_) -> + false + end, + QList + ) andalso + (not lists:member({ContentType, 0.0}, QList)) andalso + (not lists:member({SuperType, 0.0}, QList)) + end. + +%% @spec accepted_content_types([string() | binary()], request()) -> [string()] | bad_accept_header +%% +%% @doc Filters which of the given media types this request accepts. This filtering +%% is performed by analyzing the "Accept" header. The returned list is sorted +%% according to the preferences specified in the "Accept" header (higher Q values +%% first). If two or more types have the same preference (Q value), they're order +%% in the returned list is the same as they're order in the input list. +%% +%% Examples +%% +%% 1) For a missing "Accept" header: +%% accepted_content_types(["text/html", "application/json"]) -> +%% ["text/html", "application/json"] +%% +%% 2) For an "Accept" header with value "text/html, application/*": +%% accepted_content_types(["application/json", "text/html"]) -> +%% ["application/json", "text/html"] +%% +%% 3) For an "Accept" header with value "text/html, */*; q=0.0": +%% accepted_content_types(["text/html", "application/json"]) -> +%% ["text/html"] +%% +%% 4) For an "Accept" header with value "text/html; q=0.5, */*; q=0.1": +%% accepts_content_types(["application/json", "text/html"]) -> +%% ["text/html", "application/json"] +%% +accepted_content_types(Types1, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + Types = lists:map( + fun(T) -> re:replace(T, "\\s", "", [global, {return, list}]) end, + Types1), + AcceptHeader = accept_header(THIS), + case mochiweb_util:parse_qvalues(AcceptHeader) of + invalid_qvalue_string -> + bad_accept_header; + QList -> + TypesQ = lists:foldr( + fun(T, Acc) -> + case proplists:get_value(T, QList) of + undefined -> + [MainType, _SubType] = string:tokens(T, "/"), + case proplists:get_value(MainType ++ "/*", QList) of + undefined -> + case proplists:get_value("*/*", QList) of + Q when is_float(Q), Q > 0.0 -> + [{Q, T} | Acc]; + _ -> + Acc + end; + Q when Q > 0.0 -> + [{Q, T} | Acc]; + _ -> + Acc + end; + Q when Q > 0.0 -> + [{Q, T} | Acc]; + _ -> + Acc + end + end, + [], Types), + % Note: Stable sort. If 2 types have the same Q value we leave them in the + % same order as in the input list. + SortFun = fun({Q1, _}, {Q2, _}) -> Q1 >= Q2 end, + [Type || {_Q, Type} <- lists:sort(SortFun, TypesQ)] + end. + +accept_header({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case get_header_value("Accept", THIS) of + undefined -> + "*/*"; + Value -> + Value + end. + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_request.erl.orig b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_request.erl.orig new file mode 100644 index 0000000..0fea1eb --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_request.erl.orig @@ -0,0 +1,857 @@ +%% @author Bob Ippolito +%% @copyright 2007 Mochi Media, Inc. + +%% @doc MochiWeb HTTP Request abstraction. + +-module(mochiweb_request). +-author('bob@mochimedia.com'). + +-include_lib("kernel/include/file.hrl"). +-include("internal.hrl"). + +-define(QUIP, "Any of you quaids got a smint?"). + +-export([new/5]). +-export([get_header_value/2, get_primary_header_value/2, get_combined_header_value/2, get/2, dump/1]). +-export([send/2, recv/2, recv/3, recv_body/1, recv_body/2, stream_body/4]). +-export([start_response/2, start_response_length/2, start_raw_response/2]). +-export([respond/2, ok/2]). +-export([not_found/1, not_found/2]). +-export([parse_post/1, parse_qs/1]). +-export([should_close/1, cleanup/1]). +-export([parse_cookie/1, get_cookie_value/2]). +-export([serve_file/3, serve_file/4]). +-export([accepted_encodings/2]). +-export([accepts_content_type/2, accepted_content_types/2]). + +-define(SAVE_QS, mochiweb_request_qs). +-define(SAVE_PATH, mochiweb_request_path). +-define(SAVE_RECV, mochiweb_request_recv). +-define(SAVE_BODY, mochiweb_request_body). +-define(SAVE_BODY_LENGTH, mochiweb_request_body_length). +-define(SAVE_POST, mochiweb_request_post). +-define(SAVE_COOKIE, mochiweb_request_cookie). +-define(SAVE_FORCE_CLOSE, mochiweb_request_force_close). + +%% @type key() = atom() | string() | binary() +%% @type value() = atom() | string() | binary() | integer() +%% @type headers(). A mochiweb_headers structure. +%% @type request(). A mochiweb_request parameterized module instance. +%% @type response(). A mochiweb_response parameterized module instance. +%% @type ioheaders() = headers() | [{key(), value()}]. + +% 5 minute default idle timeout +-define(IDLE_TIMEOUT, 300000). + +% Maximum recv_body() length of 1MB +-define(MAX_RECV_BODY, (1024*1024)). + +%% @spec new(Socket, Method, RawPath, Version, headers()) -> request() +%% @doc Create a new request instance. +new(Socket, Method, RawPath, Version, Headers) -> + {?MODULE, [Socket, Method, RawPath, Version, Headers]}. + +%% @spec get_header_value(K, request()) -> undefined | Value +%% @doc Get the value of a given request header. +get_header_value(K, {?MODULE, [_Socket, _Method, _RawPath, _Version, Headers]}) -> + mochiweb_headers:get_value(K, Headers). + +get_primary_header_value(K, {?MODULE, [_Socket, _Method, _RawPath, _Version, Headers]}) -> + mochiweb_headers:get_primary_value(K, Headers). + +get_combined_header_value(K, {?MODULE, [_Socket, _Method, _RawPath, _Version, Headers]}) -> + mochiweb_headers:get_combined_value(K, Headers). + +%% @type field() = socket | scheme | method | raw_path | version | headers | peer | path | body_length | range + +%% @spec get(field(), request()) -> term() +%% @doc Return the internal representation of the given field. If +%% socket is requested on a HTTPS connection, then +%% an ssl socket will be returned as {ssl, SslSocket}. +%% You can use SslSocket with the ssl +%% application, eg: ssl:peercert(SslSocket). +get(socket, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) -> + Socket; +get(scheme, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) -> + case mochiweb_socket:type(Socket) of + plain -> + http; + ssl -> + https + end; +get(method, {?MODULE, [_Socket, Method, _RawPath, _Version, _Headers]}) -> + Method; +get(raw_path, {?MODULE, [_Socket, _Method, RawPath, _Version, _Headers]}) -> + RawPath; +get(version, {?MODULE, [_Socket, _Method, _RawPath, Version, _Headers]}) -> + Version; +get(headers, {?MODULE, [_Socket, _Method, _RawPath, _Version, Headers]}) -> + Headers; +get(peer, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case mochiweb_socket:peername(Socket) of + {ok, {Addr={10, _, _, _}, _Port}} -> + case get_header_value("x-forwarded-for", THIS) of + undefined -> + inet_parse:ntoa(Addr); + Hosts -> + string:strip(lists:last(string:tokens(Hosts, ","))) + end; + {ok, {{127, 0, 0, 1}, _Port}} -> + case get_header_value("x-forwarded-for", THIS) of + undefined -> + "127.0.0.1"; + Hosts -> + string:strip(lists:last(string:tokens(Hosts, ","))) + end; + {ok, {Addr, _Port}} -> + inet_parse:ntoa(Addr); + {error, enotconn} -> + exit(normal) + end; +get(path, {?MODULE, [_Socket, _Method, RawPath, _Version, _Headers]}) -> + case erlang:get(?SAVE_PATH) of + undefined -> + {Path0, _, _} = mochiweb_util:urlsplit_path(RawPath), + Path = mochiweb_util:unquote(Path0), + put(?SAVE_PATH, Path), + Path; + Cached -> + Cached + end; +get(body_length, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case erlang:get(?SAVE_BODY_LENGTH) of + undefined -> + BodyLength = body_length(THIS), + put(?SAVE_BODY_LENGTH, {cached, BodyLength}), + BodyLength; + {cached, Cached} -> + Cached + end; +get(range, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case get_header_value(range, THIS) of + undefined -> + undefined; + RawRange -> + mochiweb_http:parse_range_request(RawRange) + end. + +%% @spec dump(request()) -> {mochiweb_request, [{atom(), term()}]} +%% @doc Dump the internal representation to a "human readable" set of terms +%% for debugging/inspection purposes. +dump({?MODULE, [_Socket, Method, RawPath, Version, Headers]}) -> + {?MODULE, [{method, Method}, + {version, Version}, + {raw_path, RawPath}, + {headers, mochiweb_headers:to_list(Headers)}]}. + +%% @spec send(iodata(), request()) -> ok +%% @doc Send data over the socket. +send(Data, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) -> + case mochiweb_socket:send(Socket, Data) of + ok -> + ok; + _ -> + exit(normal) + end. + +%% @spec recv(integer(), request()) -> binary() +%% @doc Receive Length bytes from the client as a binary, with the default +%% idle timeout. +recv(Length, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + recv(Length, ?IDLE_TIMEOUT, THIS). + +%% @spec recv(integer(), integer(), request()) -> binary() +%% @doc Receive Length bytes from the client as a binary, with the given +%% Timeout in msec. +recv(Length, Timeout, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) -> + case mochiweb_socket:recv(Socket, Length, Timeout) of + {ok, Data} -> + put(?SAVE_RECV, true), + Data; + _ -> + exit(normal) + end. + +%% @spec body_length(request()) -> undefined | chunked | unknown_transfer_encoding | integer() +%% @doc Infer body length from transfer-encoding and content-length headers. +body_length({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case get_header_value("transfer-encoding", THIS) of + undefined -> + case get_combined_header_value("content-length", THIS) of + undefined -> + undefined; + Length -> + list_to_integer(Length) + end; + "chunked" -> + chunked; + Unknown -> + {unknown_transfer_encoding, Unknown} + end. + + +%% @spec recv_body(request()) -> binary() +%% @doc Receive the body of the HTTP request (defined by Content-Length). +%% Will only receive up to the default max-body length of 1MB. +recv_body({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + recv_body(?MAX_RECV_BODY, THIS). + +%% @spec recv_body(integer(), request()) -> binary() +%% @doc Receive the body of the HTTP request (defined by Content-Length). +%% Will receive up to MaxBody bytes. +recv_body(MaxBody, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case erlang:get(?SAVE_BODY) of + undefined -> + % we could use a sane constant for max chunk size + Body = stream_body(?MAX_RECV_BODY, fun + ({0, _ChunkedFooter}, {_LengthAcc, BinAcc}) -> + iolist_to_binary(lists:reverse(BinAcc)); + ({Length, Bin}, {LengthAcc, BinAcc}) -> + NewLength = Length + LengthAcc, + if NewLength > MaxBody -> + exit({body_too_large, chunked}); + true -> + {NewLength, [Bin | BinAcc]} + end + end, {0, []}, MaxBody, THIS), + put(?SAVE_BODY, Body), + Body; + Cached -> Cached + end. + +stream_body(MaxChunkSize, ChunkFun, FunState, {?MODULE,[_Socket,_Method,_RawPath,_Version,_Headers]}=THIS) -> + stream_body(MaxChunkSize, ChunkFun, FunState, undefined, THIS). + +stream_body(MaxChunkSize, ChunkFun, FunState, MaxBodyLength, + {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + Expect = case get_header_value("expect", THIS) of + undefined -> + undefined; + Value when is_list(Value) -> + string:to_lower(Value) + end, + case Expect of + "100-continue" -> + _ = start_raw_response({100, gb_trees:empty()}, THIS), + ok; + _Else -> + ok + end, + case body_length(THIS) of + undefined -> + undefined; + {unknown_transfer_encoding, Unknown} -> + exit({unknown_transfer_encoding, Unknown}); + chunked -> + % In this case the MaxBody is actually used to + % determine the maximum allowed size of a single + % chunk. + stream_chunked_body(MaxChunkSize, ChunkFun, FunState, THIS); + 0 -> + <<>>; + Length when is_integer(Length) -> + case MaxBodyLength of + MaxBodyLength when is_integer(MaxBodyLength), MaxBodyLength < Length -> + exit({body_too_large, content_length}); + _ -> + stream_unchunked_body(Length, ChunkFun, FunState, THIS) + end + end. + + +%% @spec start_response({integer(), ioheaders()}, request()) -> response() +%% @doc Start the HTTP response by sending the Code HTTP response and +%% ResponseHeaders. The server will set header defaults such as Server +%% and Date if not present in ResponseHeaders. +start_response({Code, ResponseHeaders}, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + HResponse = mochiweb_headers:make(ResponseHeaders), + HResponse1 = mochiweb_headers:default_from_list(server_headers(), + HResponse), + start_raw_response({Code, HResponse1}, THIS). + +%% @spec start_raw_response({integer(), headers()}, request()) -> response() +%% @doc Start the HTTP response by sending the Code HTTP response and +%% ResponseHeaders. +start_raw_response({Code, ResponseHeaders}, {?MODULE, [_Socket, _Method, _RawPath, Version, _Headers]}=THIS) -> + F = fun ({K, V}, Acc) -> + [mochiweb_util:make_io(K), <<": ">>, V, <<"\r\n">> | Acc] + end, + End = lists:foldl(F, [<<"\r\n">>], + mochiweb_headers:to_list(ResponseHeaders)), + send([make_version(Version), make_code(Code), <<"\r\n">> | End], THIS), + mochiweb:new_response({THIS, Code, ResponseHeaders}). + + +%% @spec start_response_length({integer(), ioheaders(), integer()}, request()) -> response() +%% @doc Start the HTTP response by sending the Code HTTP response and +%% ResponseHeaders including a Content-Length of Length. The server +%% will set header defaults such as Server +%% and Date if not present in ResponseHeaders. +start_response_length({Code, ResponseHeaders, Length}, + {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + HResponse = mochiweb_headers:make(ResponseHeaders), + HResponse1 = mochiweb_headers:enter("Content-Length", Length, HResponse), + start_response({Code, HResponse1}, THIS). + +%% @spec respond({integer(), ioheaders(), iodata() | chunked | {file, IoDevice}}, request()) -> response() +%% @doc Start the HTTP response with start_response, and send Body to the +%% client (if the get(method) /= 'HEAD'). The Content-Length header +%% will be set by the Body length, and the server will insert header +%% defaults. +respond({Code, ResponseHeaders, {file, IoDevice}}, + {?MODULE, [_Socket, Method, _RawPath, _Version, _Headers]}=THIS) -> + Length = mochiweb_io:iodevice_size(IoDevice), + Response = start_response_length({Code, ResponseHeaders, Length}, THIS), + case Method of + 'HEAD' -> + ok; + _ -> + mochiweb_io:iodevice_stream( + fun (Body) -> send(Body, THIS) end, + IoDevice) + end, + Response; +respond({Code, ResponseHeaders, chunked}, {?MODULE, [_Socket, Method, _RawPath, Version, _Headers]}=THIS) -> + HResponse = mochiweb_headers:make(ResponseHeaders), + HResponse1 = case Method of + 'HEAD' -> + %% This is what Google does, http://www.google.com/ + %% is chunked but HEAD gets Content-Length: 0. + %% The RFC is ambiguous so emulating Google is smart. + mochiweb_headers:enter("Content-Length", "0", + HResponse); + _ when Version >= {1, 1} -> + %% Only use chunked encoding for HTTP/1.1 + mochiweb_headers:enter("Transfer-Encoding", "chunked", + HResponse); + _ -> + %% For pre-1.1 clients we send the data as-is + %% without a Content-Length header and without + %% chunk delimiters. Since the end of the document + %% is now ambiguous we must force a close. + put(?SAVE_FORCE_CLOSE, true), + HResponse + end, + start_response({Code, HResponse1}, THIS); +respond({Code, ResponseHeaders, Body}, {?MODULE, [_Socket, Method, _RawPath, _Version, _Headers]}=THIS) -> + Response = start_response_length({Code, ResponseHeaders, iolist_size(Body)}, THIS), + case Method of + 'HEAD' -> + ok; + _ -> + send(Body, THIS) + end, + Response. + +%% @spec not_found(request()) -> response() +%% @doc Alias for not_found([]). +not_found({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + not_found([], THIS). + +%% @spec not_found(ExtraHeaders, request()) -> response() +%% @doc Alias for respond({404, [{"Content-Type", "text/plain"} +%% | ExtraHeaders], <<"Not found.">>}). +not_found(ExtraHeaders, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + respond({404, [{"Content-Type", "text/plain"} | ExtraHeaders], + <<"Not found.">>}, THIS). + +%% @spec ok({value(), iodata()} | {value(), ioheaders(), iodata() | {file, IoDevice}}, request()) -> +%% response() +%% @doc respond({200, [{"Content-Type", ContentType} | Headers], Body}). +ok({ContentType, Body}, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + ok({ContentType, [], Body}, THIS); +ok({ContentType, ResponseHeaders, Body}, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + HResponse = mochiweb_headers:make(ResponseHeaders), + case THIS:get(range) of + X when (X =:= undefined orelse X =:= fail) orelse Body =:= chunked -> + %% http://code.google.com/p/mochiweb/issues/detail?id=54 + %% Range header not supported when chunked, return 200 and provide + %% full response. + HResponse1 = mochiweb_headers:enter("Content-Type", ContentType, + HResponse), + respond({200, HResponse1, Body}, THIS); + Ranges -> + {PartList, Size} = range_parts(Body, Ranges), + case PartList of + [] -> %% no valid ranges + HResponse1 = mochiweb_headers:enter("Content-Type", + ContentType, + HResponse), + %% could be 416, for now we'll just return 200 + respond({200, HResponse1, Body}, THIS); + PartList -> + {RangeHeaders, RangeBody} = + mochiweb_multipart:parts_to_body(PartList, ContentType, Size), + HResponse1 = mochiweb_headers:enter_from_list( + [{"Accept-Ranges", "bytes"} | + RangeHeaders], + HResponse), + respond({206, HResponse1, RangeBody}, THIS) + end + end. + +%% @spec should_close(request()) -> bool() +%% @doc Return true if the connection must be closed. If false, using +%% Keep-Alive should be safe. +should_close({?MODULE, [_Socket, _Method, _RawPath, Version, _Headers]}=THIS) -> + ForceClose = erlang:get(?SAVE_FORCE_CLOSE) =/= undefined, + DidNotRecv = erlang:get(?SAVE_RECV) =:= undefined, + ForceClose orelse Version < {1, 0} + %% Connection: close + orelse is_close(get_header_value("connection", THIS)) + %% HTTP 1.0 requires Connection: Keep-Alive + orelse (Version =:= {1, 0} + andalso get_header_value("connection", THIS) =/= "Keep-Alive") + %% unread data left on the socket, can't safely continue + orelse (DidNotRecv + andalso get_combined_header_value("content-length", THIS) =/= undefined + andalso list_to_integer(get_combined_header_value("content-length", THIS)) > 0) + orelse (DidNotRecv + andalso get_header_value("transfer-encoding", THIS) =:= "chunked"). + +is_close("close") -> + true; +is_close(S=[_C, _L, _O, _S, _E]) -> + string:to_lower(S) =:= "close"; +is_close(_) -> + false. + +%% @spec cleanup(request()) -> ok +%% @doc Clean up any junk in the process dictionary, required before continuing +%% a Keep-Alive request. +cleanup({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}) -> + L = [?SAVE_QS, ?SAVE_PATH, ?SAVE_RECV, ?SAVE_BODY, ?SAVE_BODY_LENGTH, + ?SAVE_POST, ?SAVE_COOKIE, ?SAVE_FORCE_CLOSE], + lists:foreach(fun(K) -> + erase(K) + end, L), + ok. + +%% @spec parse_qs(request()) -> [{Key::string(), Value::string()}] +%% @doc Parse the query string of the URL. +parse_qs({?MODULE, [_Socket, _Method, RawPath, _Version, _Headers]}) -> + case erlang:get(?SAVE_QS) of + undefined -> + {_, QueryString, _} = mochiweb_util:urlsplit_path(RawPath), + Parsed = mochiweb_util:parse_qs(QueryString), + put(?SAVE_QS, Parsed), + Parsed; + Cached -> + Cached + end. + +%% @spec get_cookie_value(Key::string, request()) -> string() | undefined +%% @doc Get the value of the given cookie. +get_cookie_value(Key, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + proplists:get_value(Key, parse_cookie(THIS)). + +%% @spec parse_cookie(request()) -> [{Key::string(), Value::string()}] +%% @doc Parse the cookie header. +parse_cookie({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case erlang:get(?SAVE_COOKIE) of + undefined -> + Cookies = case get_header_value("cookie", THIS) of + undefined -> + []; + Value -> + mochiweb_cookies:parse_cookie(Value) + end, + put(?SAVE_COOKIE, Cookies), + Cookies; + Cached -> + Cached + end. + +%% @spec parse_post(request()) -> [{Key::string(), Value::string()}] +%% @doc Parse an application/x-www-form-urlencoded form POST. This +%% has the side-effect of calling recv_body(). +parse_post({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case erlang:get(?SAVE_POST) of + undefined -> + Parsed = case recv_body(THIS) of + undefined -> + []; + Binary -> + case get_primary_header_value("content-type",THIS) of + "application/x-www-form-urlencoded" ++ _ -> + mochiweb_util:parse_qs(Binary); + _ -> + [] + end + end, + put(?SAVE_POST, Parsed), + Parsed; + Cached -> + Cached + end. + +%% @spec stream_chunked_body(integer(), fun(), term(), request()) -> term() +%% @doc The function is called for each chunk. +%% Used internally by read_chunked_body. +stream_chunked_body(MaxChunkSize, Fun, FunState, + {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case read_chunk_length(THIS) of + 0 -> + Fun({0, read_chunk(0, THIS)}, FunState); + Length when Length > MaxChunkSize -> + NewState = read_sub_chunks(Length, MaxChunkSize, Fun, FunState, THIS), + stream_chunked_body(MaxChunkSize, Fun, NewState, THIS); + Length -> + NewState = Fun({Length, read_chunk(Length, THIS)}, FunState), + stream_chunked_body(MaxChunkSize, Fun, NewState, THIS) + end. + +stream_unchunked_body(0, Fun, FunState, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}) -> + Fun({0, <<>>}, FunState); +stream_unchunked_body(Length, Fun, FunState, + {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) when Length > 0 -> + PktSize = case Length > ?RECBUF_SIZE of + true -> + ?RECBUF_SIZE; + false -> + Length + end, + Bin = recv(PktSize, THIS), + NewState = Fun({PktSize, Bin}, FunState), + stream_unchunked_body(Length - PktSize, Fun, NewState, THIS). + +%% @spec read_chunk_length(request()) -> integer() +%% @doc Read the length of the next HTTP chunk. +read_chunk_length({?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) -> + ok = mochiweb_socket:setopts(Socket, [{packet, line}]), + case mochiweb_socket:recv(Socket, 0, ?IDLE_TIMEOUT) of + {ok, Header} -> + ok = mochiweb_socket:setopts(Socket, [{packet, raw}]), + Splitter = fun (C) -> + C =/= $\r andalso C =/= $\n andalso C =/= $ + end, + {Hex, _Rest} = lists:splitwith(Splitter, binary_to_list(Header)), + mochihex:to_int(Hex); + _ -> + exit(normal) + end. + +%% @spec read_chunk(integer(), request()) -> Chunk::binary() | [Footer::binary()] +%% @doc Read in a HTTP chunk of the given length. If Length is 0, then read the +%% HTTP footers (as a list of binaries, since they're nominal). +read_chunk(0, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) -> + ok = mochiweb_socket:setopts(Socket, [{packet, line}]), + F = fun (F1, Acc) -> + case mochiweb_socket:recv(Socket, 0, ?IDLE_TIMEOUT) of + {ok, <<"\r\n">>} -> + Acc; + {ok, Footer} -> + F1(F1, [Footer | Acc]); + _ -> + exit(normal) + end + end, + Footers = F(F, []), + ok = mochiweb_socket:setopts(Socket, [{packet, raw}]), + put(?SAVE_RECV, true), + Footers; +read_chunk(Length, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) -> + case mochiweb_socket:recv(Socket, 2 + Length, ?IDLE_TIMEOUT) of + {ok, <>} -> + Chunk; + _ -> + exit(normal) + end. + +read_sub_chunks(Length, MaxChunkSize, Fun, FunState, + {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) when Length > MaxChunkSize -> + Bin = recv(MaxChunkSize, THIS), + NewState = Fun({size(Bin), Bin}, FunState), + read_sub_chunks(Length - MaxChunkSize, MaxChunkSize, Fun, NewState, THIS); + +read_sub_chunks(Length, _MaxChunkSize, Fun, FunState, + {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + Fun({Length, read_chunk(Length, THIS)}, FunState). + +%% @spec serve_file(Path, DocRoot, request()) -> Response +%% @doc Serve a file relative to DocRoot. +serve_file(Path, DocRoot, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + serve_file(Path, DocRoot, [], THIS). + +%% @spec serve_file(Path, DocRoot, ExtraHeaders, request()) -> Response +%% @doc Serve a file relative to DocRoot. +serve_file(Path, DocRoot, ExtraHeaders, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case mochiweb_util:safe_relative_path(Path) of + undefined -> + not_found(ExtraHeaders, THIS); + RelPath -> + FullPath = filename:join([DocRoot, RelPath]), + case filelib:is_dir(FullPath) of + true -> + maybe_redirect(RelPath, FullPath, ExtraHeaders, THIS); + false -> + maybe_serve_file(FullPath, ExtraHeaders, THIS) + end + end. + +%% Internal API + +%% This has the same effect as the DirectoryIndex directive in httpd +directory_index(FullPath) -> + filename:join([FullPath, "index.html"]). + +maybe_redirect([], FullPath, ExtraHeaders, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + maybe_serve_file(directory_index(FullPath), ExtraHeaders, THIS); + +maybe_redirect(RelPath, FullPath, ExtraHeaders, + {?MODULE, [_Socket, _Method, _RawPath, _Version, Headers]}=THIS) -> + case string:right(RelPath, 1) of + "/" -> + maybe_serve_file(directory_index(FullPath), ExtraHeaders, THIS); + _ -> + Host = mochiweb_headers:get_value("host", Headers), + Location = "http://" ++ Host ++ "/" ++ RelPath ++ "/", + LocationBin = list_to_binary(Location), + MoreHeaders = [{"Location", Location}, + {"Content-Type", "text/html"} | ExtraHeaders], + Top = <<"" + "" + "301 Moved Permanently" + "" + "

Moved Permanently

" + "

The document has moved >, + Bottom = <<">here.

\n">>, + Body = <>, + respond({301, MoreHeaders, Body}, THIS) + end. + +maybe_serve_file(File, ExtraHeaders, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case file:read_file_info(File) of + {ok, FileInfo} -> + LastModified = httpd_util:rfc1123_date(FileInfo#file_info.mtime), + case get_header_value("if-modified-since", THIS) of + LastModified -> + respond({304, ExtraHeaders, ""}, THIS); + _ -> + case file:open(File, [raw, binary]) of + {ok, IoDevice} -> + ContentType = mochiweb_util:guess_mime(File), + Res = ok({ContentType, + [{"last-modified", LastModified} + | ExtraHeaders], + {file, IoDevice}}, THIS), + ok = file:close(IoDevice), + Res; + _ -> + not_found(ExtraHeaders, THIS) + end + end; + {error, _} -> + not_found(ExtraHeaders, THIS) + end. + +server_headers() -> + [{"Server", "MochiWeb/1.0 (" ++ ?QUIP ++ ")"}, + {"Date", httpd_util:rfc1123_date()}]. + +make_code(X) when is_integer(X) -> + [integer_to_list(X), [" " | httpd_util:reason_phrase(X)]]; +make_code(Io) when is_list(Io); is_binary(Io) -> + Io. + +make_version({1, 0}) -> + <<"HTTP/1.0 ">>; +make_version(_) -> + <<"HTTP/1.1 ">>. + +range_parts({file, IoDevice}, Ranges) -> + Size = mochiweb_io:iodevice_size(IoDevice), + F = fun (Spec, Acc) -> + case mochiweb_http:range_skip_length(Spec, Size) of + invalid_range -> + Acc; + V -> + [V | Acc] + end + end, + LocNums = lists:foldr(F, [], Ranges), + {ok, Data} = file:pread(IoDevice, LocNums), + Bodies = lists:zipwith(fun ({Skip, Length}, PartialBody) -> + {Skip, Skip + Length - 1, PartialBody} + end, + LocNums, Data), + {Bodies, Size}; +range_parts(Body0, Ranges) -> + Body = iolist_to_binary(Body0), + Size = size(Body), + F = fun(Spec, Acc) -> + case mochiweb_http:range_skip_length(Spec, Size) of + invalid_range -> + Acc; + {Skip, Length} -> + <<_:Skip/binary, PartialBody:Length/binary, _/binary>> = Body, + [{Skip, Skip + Length - 1, PartialBody} | Acc] + end + end, + {lists:foldr(F, [], Ranges), Size}. + +%% @spec accepted_encodings([encoding()], request()) -> [encoding()] | bad_accept_encoding_value +%% @type encoding() = string(). +%% +%% @doc Returns a list of encodings accepted by a request. Encodings that are +%% not supported by the server will not be included in the return list. +%% This list is computed from the "Accept-Encoding" header and +%% its elements are ordered, descendingly, according to their Q values. +%% +%% Section 14.3 of the RFC 2616 (HTTP 1.1) describes the "Accept-Encoding" +%% header and the process of determining which server supported encodings +%% can be used for encoding the body for the request's response. +%% +%% Examples +%% +%% 1) For a missing "Accept-Encoding" header: +%% accepted_encodings(["gzip", "identity"]) -> ["identity"] +%% +%% 2) For an "Accept-Encoding" header with value "gzip, deflate": +%% accepted_encodings(["gzip", "identity"]) -> ["gzip", "identity"] +%% +%% 3) For an "Accept-Encoding" header with value "gzip;q=0.5, deflate": +%% accepted_encodings(["gzip", "deflate", "identity"]) -> +%% ["deflate", "gzip", "identity"] +%% +accepted_encodings(SupportedEncodings, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + AcceptEncodingHeader = case get_header_value("Accept-Encoding", THIS) of + undefined -> + ""; + Value -> + Value + end, + case mochiweb_util:parse_qvalues(AcceptEncodingHeader) of + invalid_qvalue_string -> + bad_accept_encoding_value; + QList -> + mochiweb_util:pick_accepted_encodings( + QList, SupportedEncodings, "identity" + ) + end. + +%% @spec accepts_content_type(string() | binary(), request()) -> boolean() | bad_accept_header +%% +%% @doc Determines whether a request accepts a given media type by analyzing its +%% "Accept" header. +%% +%% Examples +%% +%% 1) For a missing "Accept" header: +%% accepts_content_type("application/json") -> true +%% +%% 2) For an "Accept" header with value "text/plain, application/*": +%% accepts_content_type("application/json") -> true +%% +%% 3) For an "Accept" header with value "text/plain, */*; q=0.0": +%% accepts_content_type("application/json") -> false +%% +%% 4) For an "Accept" header with value "text/plain; q=0.5, */*; q=0.1": +%% accepts_content_type("application/json") -> true +%% +%% 5) For an "Accept" header with value "text/*; q=0.0, */*": +%% accepts_content_type("text/plain") -> false +%% +accepts_content_type(ContentType1, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + ContentType = re:replace(ContentType1, "\\s", "", [global, {return, list}]), + AcceptHeader = accept_header(THIS), + case mochiweb_util:parse_qvalues(AcceptHeader) of + invalid_qvalue_string -> + bad_accept_header; + QList -> + [MainType, _SubType] = string:tokens(ContentType, "/"), + SuperType = MainType ++ "/*", + lists:any( + fun({"*/*", Q}) when Q > 0.0 -> + true; + ({Type, Q}) when Q > 0.0 -> + Type =:= ContentType orelse Type =:= SuperType; + (_) -> + false + end, + QList + ) andalso + (not lists:member({ContentType, 0.0}, QList)) andalso + (not lists:member({SuperType, 0.0}, QList)) + end. + +%% @spec accepted_content_types([string() | binary()], request()) -> [string()] | bad_accept_header +%% +%% @doc Filters which of the given media types this request accepts. This filtering +%% is performed by analyzing the "Accept" header. The returned list is sorted +%% according to the preferences specified in the "Accept" header (higher Q values +%% first). If two or more types have the same preference (Q value), they're order +%% in the returned list is the same as they're order in the input list. +%% +%% Examples +%% +%% 1) For a missing "Accept" header: +%% accepted_content_types(["text/html", "application/json"]) -> +%% ["text/html", "application/json"] +%% +%% 2) For an "Accept" header with value "text/html, application/*": +%% accepted_content_types(["application/json", "text/html"]) -> +%% ["application/json", "text/html"] +%% +%% 3) For an "Accept" header with value "text/html, */*; q=0.0": +%% accepted_content_types(["text/html", "application/json"]) -> +%% ["text/html"] +%% +%% 4) For an "Accept" header with value "text/html; q=0.5, */*; q=0.1": +%% accepts_content_types(["application/json", "text/html"]) -> +%% ["text/html", "application/json"] +%% +accepted_content_types(Types1, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + Types = lists:map( + fun(T) -> re:replace(T, "\\s", "", [global, {return, list}]) end, + Types1), + AcceptHeader = accept_header(THIS), + case mochiweb_util:parse_qvalues(AcceptHeader) of + invalid_qvalue_string -> + bad_accept_header; + QList -> + TypesQ = lists:foldr( + fun(T, Acc) -> + case proplists:get_value(T, QList) of + undefined -> + [MainType, _SubType] = string:tokens(T, "/"), + case proplists:get_value(MainType ++ "/*", QList) of + undefined -> + case proplists:get_value("*/*", QList) of + Q when is_float(Q), Q > 0.0 -> + [{Q, T} | Acc]; + _ -> + Acc + end; + Q when Q > 0.0 -> + [{Q, T} | Acc]; + _ -> + Acc + end; + Q when Q > 0.0 -> + [{Q, T} | Acc]; + _ -> + Acc + end + end, + [], Types), + % Note: Stable sort. If 2 types have the same Q value we leave them in the + % same order as in the input list. + SortFun = fun({Q1, _}, {Q2, _}) -> Q1 >= Q2 end, + [Type || {_Q, Type} <- lists:sort(SortFun, TypesQ)] + end. + +accept_header({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) -> + case get_header_value("Accept", THIS) of + undefined -> + "*/*"; + Value -> + Value + end. + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_request_tests.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_request_tests.erl new file mode 100644 index 0000000..b40c867 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_request_tests.erl @@ -0,0 +1,182 @@ +-module(mochiweb_request_tests). + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +accepts_content_type_test() -> + Req1 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "multipart/related"}])), + ?assertEqual(true, Req1:accepts_content_type("multipart/related")), + ?assertEqual(true, Req1:accepts_content_type(<<"multipart/related">>)), + + Req2 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/html"}])), + ?assertEqual(false, Req2:accepts_content_type("multipart/related")), + + Req3 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/html, multipart/*"}])), + ?assertEqual(true, Req3:accepts_content_type("multipart/related")), + + Req4 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/html, multipart/*; q=0.0"}])), + ?assertEqual(false, Req4:accepts_content_type("multipart/related")), + + Req5 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/html, multipart/*; q=0"}])), + ?assertEqual(false, Req5:accepts_content_type("multipart/related")), + + Req6 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/html, */*; q=0.0"}])), + ?assertEqual(false, Req6:accepts_content_type("multipart/related")), + + Req7 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "multipart/*; q=0.0, */*"}])), + ?assertEqual(false, Req7:accepts_content_type("multipart/related")), + + Req8 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "*/*; q=0.0, multipart/*"}])), + ?assertEqual(true, Req8:accepts_content_type("multipart/related")), + + Req9 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "*/*; q=0.0, multipart/related"}])), + ?assertEqual(true, Req9:accepts_content_type("multipart/related")), + + Req10 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/html; level=1"}])), + ?assertEqual(true, Req10:accepts_content_type("text/html;level=1")), + + Req11 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/html; level=1, text/html"}])), + ?assertEqual(true, Req11:accepts_content_type("text/html")), + + Req12 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/html; level=1; q=0.0, text/html"}])), + ?assertEqual(false, Req12:accepts_content_type("text/html;level=1")), + + Req13 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/html; level=1; q=0.0, text/html"}])), + ?assertEqual(false, Req13:accepts_content_type("text/html; level=1")), + + Req14 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/html;level=1;q=0.1, text/html"}])), + ?assertEqual(true, Req14:accepts_content_type("text/html; level=1")). + +accepted_encodings_test() -> + Req1 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([])), + ?assertEqual(["identity"], + Req1:accepted_encodings(["gzip", "identity"])), + + Req2 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept-Encoding", "gzip, deflate"}])), + ?assertEqual(["gzip", "identity"], + Req2:accepted_encodings(["gzip", "identity"])), + + Req3 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept-Encoding", "gzip;q=0.5, deflate"}])), + ?assertEqual(["deflate", "gzip", "identity"], + Req3:accepted_encodings(["gzip", "deflate", "identity"])), + + Req4 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept-Encoding", "identity, *;q=0"}])), + ?assertEqual(["identity"], + Req4:accepted_encodings(["gzip", "deflate", "identity"])), + + Req5 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept-Encoding", "gzip; q=0.1, *;q=0"}])), + ?assertEqual(["gzip"], + Req5:accepted_encodings(["gzip", "deflate", "identity"])), + + Req6 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept-Encoding", "gzip; q=, *;q=0"}])), + ?assertEqual(bad_accept_encoding_value, + Req6:accepted_encodings(["gzip", "deflate", "identity"])), + + Req7 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept-Encoding", "gzip;q=2.0, *;q=0"}])), + ?assertEqual(bad_accept_encoding_value, + Req7:accepted_encodings(["gzip", "identity"])), + + Req8 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept-Encoding", "deflate, *;q=0.0"}])), + ?assertEqual([], + Req8:accepted_encodings(["gzip", "identity"])). + +accepted_content_types_test() -> + Req1 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/html"}])), + ?assertEqual(["text/html"], + Req1:accepted_content_types(["text/html", "application/json"])), + + Req2 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/html, */*;q=0"}])), + ?assertEqual(["text/html"], + Req2:accepted_content_types(["text/html", "application/json"])), + + Req3 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/*, */*;q=0"}])), + ?assertEqual(["text/html"], + Req3:accepted_content_types(["text/html", "application/json"])), + + Req4 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/*;q=0.8, */*;q=0.5"}])), + ?assertEqual(["text/html", "application/json"], + Req4:accepted_content_types(["application/json", "text/html"])), + + Req5 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/*;q=0.8, */*;q=0.5"}])), + ?assertEqual(["text/html", "application/json"], + Req5:accepted_content_types(["text/html", "application/json"])), + + Req6 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/*;q=0.5, */*;q=0.5"}])), + ?assertEqual(["application/json", "text/html"], + Req6:accepted_content_types(["application/json", "text/html"])), + + Req7 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make( + [{"Accept", "text/html;q=0.5, application/json;q=0.5"}])), + ?assertEqual(["application/json", "text/html"], + Req7:accepted_content_types(["application/json", "text/html"])), + + Req8 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/html"}])), + ?assertEqual([], + Req8:accepted_content_types(["application/json"])), + + Req9 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/*;q=0.9, text/html;q=0.5, */*;q=0.7"}])), + ?assertEqual(["application/json", "text/html"], + Req9:accepted_content_types(["text/html", "application/json"])). + +should_close_test() -> + F = fun (V, H) -> + (mochiweb_request:new( + nil, 'GET', "/", V, + mochiweb_headers:make(H) + )):should_close() + end, + ?assertEqual( + true, + F({1, 1}, [{"Connection", "close"}])), + ?assertEqual( + true, + F({1, 0}, [{"Connection", "close"}])), + ?assertEqual( + true, + F({1, 1}, [{"Connection", "ClOSe"}])), + ?assertEqual( + false, + F({1, 1}, [{"Connection", "closer"}])), + ?assertEqual( + false, + F({1, 1}, [])), + ?assertEqual( + true, + F({1, 0}, [])), + ?assertEqual( + false, + F({1, 0}, [{"Connection", "Keep-Alive"}])), + ok. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_response.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_response.erl new file mode 100644 index 0000000..308a26b --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_response.erl @@ -0,0 +1,72 @@ +%% @author Bob Ippolito +%% @copyright 2007 Mochi Media, Inc. + +%% @doc Response abstraction. + +-module(mochiweb_response). +-author('bob@mochimedia.com'). + +-define(QUIP, "Any of you quaids got a smint?"). + +-export([new/3, get_header_value/2, get/2, dump/1]). +-export([send/2, write_chunk/2]). + +%% @type response(). A mochiweb_response parameterized module instance. + +%% @spec new(Request, Code, Headers) -> response() +%% @doc Create a new mochiweb_response instance. +new(Request, Code, Headers) -> + {?MODULE, [Request, Code, Headers]}. + +%% @spec get_header_value(string() | atom() | binary(), response()) -> +%% string() | undefined +%% @doc Get the value of the given response header. +get_header_value(K, {?MODULE, [_Request, _Code, Headers]}) -> + mochiweb_headers:get_value(K, Headers). + +%% @spec get(request | code | headers, response()) -> term() +%% @doc Return the internal representation of the given field. +get(request, {?MODULE, [Request, _Code, _Headers]}) -> + Request; +get(code, {?MODULE, [_Request, Code, _Headers]}) -> + Code; +get(headers, {?MODULE, [_Request, _Code, Headers]}) -> + Headers. + +%% @spec dump(response()) -> {mochiweb_request, [{atom(), term()}]} +%% @doc Dump the internal representation to a "human readable" set of terms +%% for debugging/inspection purposes. +dump({?MODULE, [Request, Code, Headers]}) -> + [{request, Request:dump()}, + {code, Code}, + {headers, mochiweb_headers:to_list(Headers)}]. + +%% @spec send(iodata(), response()) -> ok +%% @doc Send data over the socket if the method is not HEAD. +send(Data, {?MODULE, [Request, _Code, _Headers]}) -> + case Request:get(method) of + 'HEAD' -> + ok; + _ -> + Request:send(Data) + end. + +%% @spec write_chunk(iodata(), response()) -> ok +%% @doc Write a chunk of a HTTP chunked response. If Data is zero length, +%% then the chunked response will be finished. +write_chunk(Data, {?MODULE, [Request, _Code, _Headers]}=THIS) -> + case Request:get(version) of + Version when Version >= {1, 1} -> + Length = iolist_size(Data), + send([io_lib:format("~.16b\r\n", [Length]), Data, <<"\r\n">>], THIS); + _ -> + send(Data, THIS) + end. + + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_session.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_session.erl new file mode 100644 index 0000000..ddf7c46 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_session.erl @@ -0,0 +1,189 @@ +%% @author Asier Azkuenaga Batiz + +%% @doc HTTP Cookie session. Note that the expiration time travels unencrypted +%% as far as this module is concerned. In order to achieve more security, +%% it is advised to use https. +%% Based on the paper +%% +%% "A Secure Cookie Protocol". +%% This module is only supported on R15B02 and later, the AES CFB mode is not +%% available in earlier releases of crypto. +-module(mochiweb_session). +-export([generate_session_data/4, generate_session_cookie/4, + check_session_cookie/4]). + +-export_types([expiration_time/0]). +-type expiration_time() :: integer(). +-type key_fun() :: fun((string()) -> iolist()). + +%% TODO: Import this from elsewhere after attribute types refactor. +-type header() :: {string(), string()}. + +%% @doc Generates a secure encrypted binary convining all the parameters. The +%% expiration time must be a 32-bit integer. +%% -spec generate_session_data( +%% ExpirationTime :: expiration_time(), +%% Data :: iolist(), +%% FSessionKey :: key_fun(), +%% ServerKey :: iolist()) -> binary(). +generate_session_data(ExpirationTime, Data, FSessionKey, ServerKey) + when is_integer(ExpirationTime), is_function(FSessionKey)-> + BData = ensure_binary(Data), + ExpTime = integer_to_list(ExpirationTime), + Key = gen_key(ExpTime, ServerKey), + Hmac = gen_hmac(ExpTime, BData, FSessionKey(ExpTime), Key), + EData = encrypt_data(BData, Key), + mochiweb_base64url:encode( + <>). + +%% @doc Convenience wrapper for generate_session_data that returns a +%% mochiweb cookie with "id" as the key, a max_age of 20000 seconds, +%% and the current local time as local time. +%% -spec generate_session_cookie( +%% ExpirationTime :: expiration_time(), +%% Data :: iolist(), +%% FSessionKey :: key_fun(), +%% ServerKey :: iolist()) -> header(). +generate_session_cookie(ExpirationTime, Data, FSessionKey, ServerKey) + when is_integer(ExpirationTime), is_function(FSessionKey)-> + CookieData = generate_session_data(ExpirationTime, Data, + FSessionKey, ServerKey), + mochiweb_cookies:cookie("id", CookieData, + [{max_age, 20000}, + {local_time, + calendar:universal_time_to_local_time( + calendar:universal_time())}]). + +%% TODO: This return type is messy to express in the type system. +%% -spec check_session_cookie( + %% ECookie :: binary(), + %% ExpirationTime :: string(), + %% FSessionKey :: key_fun(), + %% ServerKey :: iolist()) -> + %% {Success :: boolean(), + %% ExpTimeAndData :: [integer() | binary()]}. +check_session_cookie(ECookie, ExpirationTime, FSessionKey, ServerKey) + when is_binary(ECookie), is_integer(ExpirationTime), + is_function(FSessionKey) -> + case mochiweb_base64url:decode(ECookie) of + <> -> + ETString = integer_to_list(ExpirationTime1), + Key = gen_key(ETString, ServerKey), + Data = decrypt_data(EData, Key), + Hmac2 = gen_hmac(ETString, + Data, + FSessionKey(ETString), + Key), + {ExpirationTime1 >= ExpirationTime andalso eq(Hmac2, BHmac), + [ExpirationTime1, binary_to_list(Data)]}; + _ -> + {false, []} + end; +check_session_cookie(_ECookie, _ExpirationTime, _FSessionKey, _ServerKey) -> + {false, []}. + +%% 'Constant' time =:= operator for binary, to mitigate timing attacks. +%% -spec eq(binary(), binary()) -> boolean(). +eq(A, B) when is_binary(A) andalso is_binary(B) -> + eq(A, B, 0). + +eq(<>, <>, Acc) -> + eq(As, Bs, Acc bor (A bxor B)); +eq(<<>>, <<>>, 0) -> + true; +eq(_As, _Bs, _Acc) -> + false. + +%% -spec ensure_binary(iolist()) -> binary(). +ensure_binary(B) when is_binary(B) -> + B; +ensure_binary(L) when is_list(L) -> + iolist_to_binary(L). + +%% -spec encrypt_data(binary(), binary()) -> binary(). +encrypt_data(Data, Key) -> + IV = crypto:rand_bytes(16), + Crypt = crypto:aes_cfb_128_encrypt(Key, IV, Data), + <>. + +%% -spec decrypt_data(binary(), binary()) -> binary(). +decrypt_data(<>, Key) -> + crypto:aes_cfb_128_decrypt(Key, IV, Crypt). + +%% -spec gen_key(iolist(), iolist()) -> binary(). +gen_key(ExpirationTime, ServerKey)-> + crypto:md5_mac(ServerKey, [ExpirationTime]). + +%% -spec gen_hmac(iolist(), binary(), iolist(), binary()) -> binary(). +gen_hmac(ExpirationTime, Data, SessionKey, Key) -> + crypto:sha_mac(Key, [ExpirationTime, Data, SessionKey]). + + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +generate_check_session_cookie_test_() -> + {setup, + fun setup_server_key/0, + fun generate_check_session_cookie/1}. + +setup_server_key() -> + crypto:start(), + ["adfasdfasfs",30000]. + +generate_check_session_cookie([ServerKey, TS]) -> + Id = fun (A) -> A end, + TSFuture = TS + 1000, + TSPast = TS - 1, + [?_assertEqual( + {true, [TSFuture, "alice"]}, + check_session_cookie( + generate_session_data(TSFuture, "alice", Id, ServerKey), + TS, Id, ServerKey)), + ?_assertEqual( + {true, [TSFuture, "alice and"]}, + check_session_cookie( + generate_session_data(TSFuture, "alice and", Id, ServerKey), + TS, Id, ServerKey)), + ?_assertEqual( + {true, [TSFuture, "alice and"]}, + check_session_cookie( + generate_session_data(TSFuture, "alice and", Id, ServerKey), + TS, Id,ServerKey)), + ?_assertEqual( + {true, [TSFuture, "alice and bob"]}, + check_session_cookie( + generate_session_data(TSFuture, "alice and bob", + Id, ServerKey), + TS, Id, ServerKey)), + ?_assertEqual( + {true, [TSFuture, "alice jlkjfkjsdfg sdkfjgldsjgl"]}, + check_session_cookie( + generate_session_data(TSFuture, "alice jlkjfkjsdfg sdkfjgldsjgl", + Id, ServerKey), + TS, Id, ServerKey)), + ?_assertEqual( + {true, [TSFuture, "alice .'¡'ç+-$%/(&\""]}, + check_session_cookie( + generate_session_data(TSFuture, "alice .'¡'ç+-$%/(&\"" + ,Id, ServerKey), + TS, Id, ServerKey)), + ?_assertEqual( + {true,[TSFuture,"alice456689875"]}, + check_session_cookie( + generate_session_data(TSFuture, ["alice","456689875"], + Id, ServerKey), + TS, Id, ServerKey)), + ?_assertError( + function_clause, + check_session_cookie( + generate_session_data(TSFuture, {tuple,one}, + Id, ServerKey), + TS, Id,ServerKey)), + ?_assertEqual( + {false, [TSPast, "bob"]}, + check_session_cookie( + generate_session_data(TSPast, "bob", Id,ServerKey), + TS, Id, ServerKey)) + ]. +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_socket.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_socket.erl new file mode 100644 index 0000000..76b018c --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_socket.erl @@ -0,0 +1,84 @@ +%% @copyright 2010 Mochi Media, Inc. + +%% @doc MochiWeb socket - wrapper for plain and ssl sockets. + +-module(mochiweb_socket). + +-export([listen/4, accept/1, recv/3, send/2, close/1, port/1, peername/1, + setopts/2, type/1]). + +-define(ACCEPT_TIMEOUT, 2000). + +listen(Ssl, Port, Opts, SslOpts) -> + case Ssl of + true -> + case ssl:listen(Port, Opts ++ SslOpts) of + {ok, ListenSocket} -> + {ok, {ssl, ListenSocket}}; + {error, _} = Err -> + Err + end; + false -> + gen_tcp:listen(Port, Opts) + end. + +accept({ssl, ListenSocket}) -> + % There's a bug in ssl:transport_accept/2 at the moment, which is the + % reason for the try...catch block. Should be fixed in OTP R14. + try ssl:transport_accept(ListenSocket) of + {ok, Socket} -> + case ssl:ssl_accept(Socket) of + ok -> + {ok, {ssl, Socket}}; + {error, _} = Err -> + Err + end; + {error, _} = Err -> + Err + catch + error:{badmatch, {error, Reason}} -> + {error, Reason} + end; +accept(ListenSocket) -> + gen_tcp:accept(ListenSocket, ?ACCEPT_TIMEOUT). + +recv({ssl, Socket}, Length, Timeout) -> + ssl:recv(Socket, Length, Timeout); +recv(Socket, Length, Timeout) -> + gen_tcp:recv(Socket, Length, Timeout). + +send({ssl, Socket}, Data) -> + ssl:send(Socket, Data); +send(Socket, Data) -> + gen_tcp:send(Socket, Data). + +close({ssl, Socket}) -> + ssl:close(Socket); +close(Socket) -> + gen_tcp:close(Socket). + +port({ssl, Socket}) -> + case ssl:sockname(Socket) of + {ok, {_, Port}} -> + {ok, Port}; + {error, _} = Err -> + Err + end; +port(Socket) -> + inet:port(Socket). + +peername({ssl, Socket}) -> + ssl:peername(Socket); +peername(Socket) -> + inet:peername(Socket). + +setopts({ssl, Socket}, Opts) -> + ssl:setopts(Socket, Opts); +setopts(Socket, Opts) -> + inet:setopts(Socket, Opts). + +type({ssl, _}) -> + ssl; +type(_) -> + plain. + diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_socket_server.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_socket_server.erl new file mode 100644 index 0000000..a3d4da3 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_socket_server.erl @@ -0,0 +1,348 @@ +%% @author Bob Ippolito +%% @copyright 2007 Mochi Media, Inc. + +%% @doc MochiWeb socket server. + +-module(mochiweb_socket_server). +-author('bob@mochimedia.com'). +-behaviour(gen_server). + +-include("internal.hrl"). + +-export([start/1, start_link/1, stop/1]). +-export([init/1, handle_call/3, handle_cast/2, terminate/2, code_change/3, + handle_info/2]). +-export([get/2, set/3]). + +-record(mochiweb_socket_server, + {port, + loop, + name=undefined, + %% NOTE: This is currently ignored. + max=2048, + ip=any, + listen=null, + nodelay=false, + backlog=128, + active_sockets=0, + acceptor_pool_size=16, + ssl=false, + ssl_opts=[{ssl_imp, new}], + acceptor_pool=sets:new(), + profile_fun=undefined}). + +-define(is_old_state(State), not is_record(State, mochiweb_socket_server)). + +start_link(Options) -> + start_server(start_link, parse_options(Options)). + +start(Options) -> + case lists:keytake(link, 1, Options) of + {value, {_Key, false}, Options1} -> + start_server(start, parse_options(Options1)); + _ -> + %% TODO: https://github.com/mochi/mochiweb/issues/58 + %% [X] Phase 1: Add new APIs (Sep 2011) + %% [_] Phase 2: Add deprecation warning + %% [_] Phase 3: Change default to {link, false} and ignore link + %% [_] Phase 4: Add deprecation warning for {link, _} option + %% [_] Phase 5: Remove support for {link, _} option + start_link(Options) + end. + +get(Name, Property) -> + gen_server:call(Name, {get, Property}). + +set(Name, profile_fun, Fun) -> + gen_server:cast(Name, {set, profile_fun, Fun}); +set(Name, Property, _Value) -> + error_logger:info_msg("?MODULE:set for ~p with ~p not implemented~n", + [Name, Property]). + +stop(Name) when is_atom(Name) orelse is_pid(Name) -> + gen_server:call(Name, stop); +stop({Scope, Name}) when Scope =:= local orelse Scope =:= global -> + stop(Name); +stop(Options) -> + State = parse_options(Options), + stop(State#mochiweb_socket_server.name). + +%% Internal API + +parse_options(State=#mochiweb_socket_server{}) -> + State; +parse_options(Options) -> + parse_options(Options, #mochiweb_socket_server{}). + +parse_options([], State) -> + State; +parse_options([{name, L} | Rest], State) when is_list(L) -> + Name = {local, list_to_atom(L)}, + parse_options(Rest, State#mochiweb_socket_server{name=Name}); +parse_options([{name, A} | Rest], State) when A =:= undefined -> + parse_options(Rest, State#mochiweb_socket_server{name=A}); +parse_options([{name, A} | Rest], State) when is_atom(A) -> + Name = {local, A}, + parse_options(Rest, State#mochiweb_socket_server{name=Name}); +parse_options([{name, Name} | Rest], State) -> + parse_options(Rest, State#mochiweb_socket_server{name=Name}); +parse_options([{port, L} | Rest], State) when is_list(L) -> + Port = list_to_integer(L), + parse_options(Rest, State#mochiweb_socket_server{port=Port}); +parse_options([{port, Port} | Rest], State) -> + parse_options(Rest, State#mochiweb_socket_server{port=Port}); +parse_options([{ip, Ip} | Rest], State) -> + ParsedIp = case Ip of + any -> + any; + Ip when is_tuple(Ip) -> + Ip; + Ip when is_list(Ip) -> + {ok, IpTuple} = inet_parse:address(Ip), + IpTuple + end, + parse_options(Rest, State#mochiweb_socket_server{ip=ParsedIp}); +parse_options([{loop, Loop} | Rest], State) -> + parse_options(Rest, State#mochiweb_socket_server{loop=Loop}); +parse_options([{backlog, Backlog} | Rest], State) -> + parse_options(Rest, State#mochiweb_socket_server{backlog=Backlog}); +parse_options([{nodelay, NoDelay} | Rest], State) -> + parse_options(Rest, State#mochiweb_socket_server{nodelay=NoDelay}); +parse_options([{acceptor_pool_size, Max} | Rest], State) -> + MaxInt = ensure_int(Max), + parse_options(Rest, + State#mochiweb_socket_server{acceptor_pool_size=MaxInt}); +parse_options([{max, Max} | Rest], State) -> + error_logger:info_report([{warning, "TODO: max is currently unsupported"}, + {max, Max}]), + MaxInt = ensure_int(Max), + parse_options(Rest, State#mochiweb_socket_server{max=MaxInt}); +parse_options([{ssl, Ssl} | Rest], State) when is_boolean(Ssl) -> + parse_options(Rest, State#mochiweb_socket_server{ssl=Ssl}); +parse_options([{ssl_opts, SslOpts} | Rest], State) when is_list(SslOpts) -> + SslOpts1 = [{ssl_imp, new} | proplists:delete(ssl_imp, SslOpts)], + parse_options(Rest, State#mochiweb_socket_server{ssl_opts=SslOpts1}); +parse_options([{profile_fun, ProfileFun} | Rest], State) when is_function(ProfileFun) -> + parse_options(Rest, State#mochiweb_socket_server{profile_fun=ProfileFun}). + + +start_server(F, State=#mochiweb_socket_server{ssl=Ssl, name=Name}) -> + ok = prep_ssl(Ssl), + case Name of + undefined -> + gen_server:F(?MODULE, State, []); + _ -> + gen_server:F(Name, ?MODULE, State, []) + end. + +prep_ssl(true) -> + ok = mochiweb:ensure_started(crypto), + ok = mochiweb:ensure_started(asn1), + ok = mochiweb:ensure_started(public_key), + ok = mochiweb:ensure_started(ssl); +prep_ssl(false) -> + ok. + +ensure_int(N) when is_integer(N) -> + N; +ensure_int(S) when is_list(S) -> + list_to_integer(S). + +ipv6_supported() -> + case (catch inet:getaddr("localhost", inet6)) of + {ok, _Addr} -> + true; + {error, _} -> + false + end. + +init(State=#mochiweb_socket_server{ip=Ip, port=Port, backlog=Backlog, nodelay=NoDelay}) -> + process_flag(trap_exit, true), + BaseOpts = [binary, + {reuseaddr, true}, + {packet, 0}, + {backlog, Backlog}, + {recbuf, ?RECBUF_SIZE}, + {exit_on_close, false}, + {active, false}, + {nodelay, NoDelay}], + Opts = case Ip of + any -> + case ipv6_supported() of % IPv4, and IPv6 if supported + true -> [inet, inet6 | BaseOpts]; + _ -> BaseOpts + end; + {_, _, _, _} -> % IPv4 + [inet, {ip, Ip} | BaseOpts]; + {_, _, _, _, _, _, _, _} -> % IPv6 + [inet6, {ip, Ip} | BaseOpts] + end, + listen(Port, Opts, State). + +new_acceptor_pool(Listen, + State=#mochiweb_socket_server{acceptor_pool=Pool, + acceptor_pool_size=Size, + loop=Loop}) -> + F = fun (_, S) -> + Pid = mochiweb_acceptor:start_link(self(), Listen, Loop), + sets:add_element(Pid, S) + end, + Pool1 = lists:foldl(F, Pool, lists:seq(1, Size)), + State#mochiweb_socket_server{acceptor_pool=Pool1}. + +listen(Port, Opts, State=#mochiweb_socket_server{ssl=Ssl, ssl_opts=SslOpts}) -> + case mochiweb_socket:listen(Ssl, Port, Opts, SslOpts) of + {ok, Listen} -> + {ok, ListenPort} = mochiweb_socket:port(Listen), + {ok, new_acceptor_pool( + Listen, + State#mochiweb_socket_server{listen=Listen, + port=ListenPort})}; + {error, Reason} -> + {stop, Reason} + end. + +do_get(port, #mochiweb_socket_server{port=Port}) -> + Port; +do_get(active_sockets, #mochiweb_socket_server{active_sockets=ActiveSockets}) -> + ActiveSockets. + + +state_to_proplist(#mochiweb_socket_server{name=Name, + port=Port, + active_sockets=ActiveSockets}) -> + [{name, Name}, {port, Port}, {active_sockets, ActiveSockets}]. + +upgrade_state(State = #mochiweb_socket_server{}) -> + State; +upgrade_state({mochiweb_socket_server, Port, Loop, Name, + Max, IP, Listen, NoDelay, Backlog, ActiveSockets, + AcceptorPoolSize, SSL, SSL_opts, + AcceptorPool}) -> + #mochiweb_socket_server{port=Port, loop=Loop, name=Name, max=Max, ip=IP, + listen=Listen, nodelay=NoDelay, backlog=Backlog, + active_sockets=ActiveSockets, + acceptor_pool_size=AcceptorPoolSize, + ssl=SSL, + ssl_opts=SSL_opts, + acceptor_pool=AcceptorPool}. + +handle_call(Req, From, State) when ?is_old_state(State) -> + handle_call(Req, From, upgrade_state(State)); +handle_call({get, Property}, _From, State) -> + Res = do_get(Property, State), + {reply, Res, State}; +handle_call(stop, _From, State) -> + {stop, normal, ok, State}; +handle_call(_Message, _From, State) -> + Res = error, + {reply, Res, State}. + + +handle_cast(Req, State) when ?is_old_state(State) -> + handle_cast(Req, upgrade_state(State)); +handle_cast({accepted, Pid, Timing}, + State=#mochiweb_socket_server{active_sockets=ActiveSockets}) -> + State1 = State#mochiweb_socket_server{active_sockets=1 + ActiveSockets}, + case State#mochiweb_socket_server.profile_fun of + undefined -> + undefined; + F when is_function(F) -> + catch F([{timing, Timing} | state_to_proplist(State1)]) + end, + {noreply, recycle_acceptor(Pid, State1)}; +handle_cast({set, profile_fun, ProfileFun}, State) -> + State1 = case ProfileFun of + ProfileFun when is_function(ProfileFun); ProfileFun =:= undefined -> + State#mochiweb_socket_server{profile_fun=ProfileFun}; + _ -> + State + end, + {noreply, State1}. + + +terminate(Reason, State) when ?is_old_state(State) -> + terminate(Reason, upgrade_state(State)); +terminate(_Reason, #mochiweb_socket_server{listen=Listen}) -> + mochiweb_socket:close(Listen). + +code_change(_OldVsn, State, _Extra) -> + State. + +recycle_acceptor(Pid, State=#mochiweb_socket_server{ + acceptor_pool=Pool, + listen=Listen, + loop=Loop, + active_sockets=ActiveSockets}) -> + case sets:is_element(Pid, Pool) of + true -> + Acceptor = mochiweb_acceptor:start_link(self(), Listen, Loop), + Pool1 = sets:add_element(Acceptor, sets:del_element(Pid, Pool)), + State#mochiweb_socket_server{acceptor_pool=Pool1}; + false -> + State#mochiweb_socket_server{active_sockets=ActiveSockets - 1} + end. + +handle_info(Msg, State) when ?is_old_state(State) -> + handle_info(Msg, upgrade_state(State)); +handle_info({'EXIT', Pid, normal}, State) -> + {noreply, recycle_acceptor(Pid, State)}; +handle_info({'EXIT', Pid, Reason}, + State=#mochiweb_socket_server{acceptor_pool=Pool}) -> + case sets:is_element(Pid, Pool) of + true -> + %% If there was an unexpected error accepting, log and sleep. + error_logger:error_report({?MODULE, ?LINE, + {acceptor_error, Reason}}), + timer:sleep(100); + false -> + ok + end, + {noreply, recycle_acceptor(Pid, State)}; + +% this is what release_handler needs to get a list of modules, +% since our supervisor modules list is set to 'dynamic' +% see sasl-2.1.9.2/src/release_handler_1.erl get_dynamic_mods +handle_info({From, Tag, get_modules}, State = #mochiweb_socket_server{name={local,Mod}}) -> + From ! {element(2,Tag), [Mod]}, + {noreply, State}; + +% If for some reason we can't get the module name, send empty list to avoid release_handler timeout: +handle_info({From, Tag, get_modules}, State) -> + error_logger:info_msg("mochiweb_socket_server replying to dynamic modules request as '[]'~n",[]), + From ! {element(2,Tag), []}, + {noreply, State}; + +handle_info(Info, State) -> + error_logger:info_report([{'INFO', Info}, {'State', State}]), + {noreply, State}. + + + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +upgrade_state_test() -> + OldState = {mochiweb_socket_server, + port, loop, name, + max, ip, listen, + nodelay, backlog, + active_sockets, + acceptor_pool_size, + ssl, ssl_opts, acceptor_pool}, + State = upgrade_state(OldState), + CmpState = #mochiweb_socket_server{port=port, loop=loop, + name=name, max=max, ip=ip, + listen=listen, nodelay=nodelay, + backlog=backlog, + active_sockets=active_sockets, + acceptor_pool_size=acceptor_pool_size, + ssl=ssl, ssl_opts=ssl_opts, + acceptor_pool=acceptor_pool, + profile_fun=undefined}, + ?assertEqual(CmpState, State). + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_util.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_util.erl new file mode 100644 index 0000000..a0bc2bc --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_util.erl @@ -0,0 +1,992 @@ +%% @author Bob Ippolito +%% @copyright 2007 Mochi Media, Inc. + +%% @doc Utilities for parsing and quoting. + +-module(mochiweb_util). +-author('bob@mochimedia.com'). +-export([join/2, quote_plus/1, urlencode/1, parse_qs/1, unquote/1]). +-export([path_split/1]). +-export([urlsplit/1, urlsplit_path/1, urlunsplit/1, urlunsplit_path/1]). +-export([guess_mime/1, parse_header/1]). +-export([shell_quote/1, cmd/1, cmd_string/1, cmd_port/2, cmd_status/1, cmd_status/2]). +-export([record_to_proplist/2, record_to_proplist/3]). +-export([safe_relative_path/1, partition/2]). +-export([parse_qvalues/1, pick_accepted_encodings/3]). +-export([make_io/1, rand_bytes/1, rand_uniform/2]). + +-define(PERCENT, 37). % $\% +-define(FULLSTOP, 46). % $\. +-define(IS_HEX(C), ((C >= $0 andalso C =< $9) orelse + (C >= $a andalso C =< $f) orelse + (C >= $A andalso C =< $F))). +-define(QS_SAFE(C), ((C >= $a andalso C =< $z) orelse + (C >= $A andalso C =< $Z) orelse + (C >= $0 andalso C =< $9) orelse + (C =:= ?FULLSTOP orelse C =:= $- orelse C =:= $~ orelse + C =:= $_))). + +hexdigit(C) when C < 10 -> $0 + C; +hexdigit(C) when C < 16 -> $A + (C - 10). + +unhexdigit(C) when C >= $0, C =< $9 -> C - $0; +unhexdigit(C) when C >= $a, C =< $f -> C - $a + 10; +unhexdigit(C) when C >= $A, C =< $F -> C - $A + 10. + +%% @spec partition(String, Sep) -> {String, [], []} | {Prefix, Sep, Postfix} +%% @doc Inspired by Python 2.5's str.partition: +%% partition("foo/bar", "/") = {"foo", "/", "bar"}, +%% partition("foo", "/") = {"foo", "", ""}. +partition(String, Sep) -> + case partition(String, Sep, []) of + undefined -> + {String, "", ""}; + Result -> + Result + end. + +partition("", _Sep, _Acc) -> + undefined; +partition(S, Sep, Acc) -> + case partition2(S, Sep) of + undefined -> + [C | Rest] = S, + partition(Rest, Sep, [C | Acc]); + Rest -> + {lists:reverse(Acc), Sep, Rest} + end. + +partition2(Rest, "") -> + Rest; +partition2([C | R1], [C | R2]) -> + partition2(R1, R2); +partition2(_S, _Sep) -> + undefined. + + + +%% @spec safe_relative_path(string()) -> string() | undefined +%% @doc Return the reduced version of a relative path or undefined if it +%% is not safe. safe relative paths can be joined with an absolute path +%% and will result in a subdirectory of the absolute path. Safe paths +%% never contain a backslash character. +safe_relative_path("/" ++ _) -> + undefined; +safe_relative_path(P) -> + case string:chr(P, $\\) of + 0 -> + safe_relative_path(P, []); + _ -> + undefined + end. + +safe_relative_path("", Acc) -> + case Acc of + [] -> + ""; + _ -> + string:join(lists:reverse(Acc), "/") + end; +safe_relative_path(P, Acc) -> + case partition(P, "/") of + {"", "/", _} -> + %% /foo or foo//bar + undefined; + {"..", _, _} when Acc =:= [] -> + undefined; + {"..", _, Rest} -> + safe_relative_path(Rest, tl(Acc)); + {Part, "/", ""} -> + safe_relative_path("", ["", Part | Acc]); + {Part, _, Rest} -> + safe_relative_path(Rest, [Part | Acc]) + end. + +%% @spec shell_quote(string()) -> string() +%% @doc Quote a string according to UNIX shell quoting rules, returns a string +%% surrounded by double quotes. +shell_quote(L) -> + shell_quote(L, [$\"]). + +%% @spec cmd_port([string()], Options) -> port() +%% @doc open_port({spawn, mochiweb_util:cmd_string(Argv)}, Options). +cmd_port(Argv, Options) -> + open_port({spawn, cmd_string(Argv)}, Options). + +%% @spec cmd([string()]) -> string() +%% @doc os:cmd(cmd_string(Argv)). +cmd(Argv) -> + os:cmd(cmd_string(Argv)). + +%% @spec cmd_string([string()]) -> string() +%% @doc Create a shell quoted command string from a list of arguments. +cmd_string(Argv) -> + string:join([shell_quote(X) || X <- Argv], " "). + +%% @spec cmd_status([string()]) -> {ExitStatus::integer(), Stdout::binary()} +%% @doc Accumulate the output and exit status from the given application, +%% will be spawned with cmd_port/2. +cmd_status(Argv) -> + cmd_status(Argv, []). + +%% @spec cmd_status([string()], [atom()]) -> {ExitStatus::integer(), Stdout::binary()} +%% @doc Accumulate the output and exit status from the given application, +%% will be spawned with cmd_port/2. +cmd_status(Argv, Options) -> + Port = cmd_port(Argv, [exit_status, stderr_to_stdout, + use_stdio, binary | Options]), + try cmd_loop(Port, []) + after catch port_close(Port) + end. + +%% @spec cmd_loop(port(), list()) -> {ExitStatus::integer(), Stdout::binary()} +%% @doc Accumulate the output and exit status from a port. +cmd_loop(Port, Acc) -> + receive + {Port, {exit_status, Status}} -> + {Status, iolist_to_binary(lists:reverse(Acc))}; + {Port, {data, Data}} -> + cmd_loop(Port, [Data | Acc]) + end. + +%% @spec join([iolist()], iolist()) -> iolist() +%% @doc Join a list of strings or binaries together with the given separator +%% string or char or binary. The output is flattened, but may be an +%% iolist() instead of a string() if any of the inputs are binary(). +join([], _Separator) -> + []; +join([S], _Separator) -> + lists:flatten(S); +join(Strings, Separator) -> + lists:flatten(revjoin(lists:reverse(Strings), Separator, [])). + +revjoin([], _Separator, Acc) -> + Acc; +revjoin([S | Rest], Separator, []) -> + revjoin(Rest, Separator, [S]); +revjoin([S | Rest], Separator, Acc) -> + revjoin(Rest, Separator, [S, Separator | Acc]). + +%% @spec quote_plus(atom() | integer() | float() | string() | binary()) -> string() +%% @doc URL safe encoding of the given term. +quote_plus(Atom) when is_atom(Atom) -> + quote_plus(atom_to_list(Atom)); +quote_plus(Int) when is_integer(Int) -> + quote_plus(integer_to_list(Int)); +quote_plus(Binary) when is_binary(Binary) -> + quote_plus(binary_to_list(Binary)); +quote_plus(Float) when is_float(Float) -> + quote_plus(mochinum:digits(Float)); +quote_plus(String) -> + quote_plus(String, []). + +quote_plus([], Acc) -> + lists:reverse(Acc); +quote_plus([C | Rest], Acc) when ?QS_SAFE(C) -> + quote_plus(Rest, [C | Acc]); +quote_plus([$\s | Rest], Acc) -> + quote_plus(Rest, [$+ | Acc]); +quote_plus([C | Rest], Acc) -> + <> = <>, + quote_plus(Rest, [hexdigit(Lo), hexdigit(Hi), ?PERCENT | Acc]). + +%% @spec urlencode([{Key, Value}]) -> string() +%% @doc URL encode the property list. +urlencode(Props) -> + Pairs = lists:foldr( + fun ({K, V}, Acc) -> + [quote_plus(K) ++ "=" ++ quote_plus(V) | Acc] + end, [], Props), + string:join(Pairs, "&"). + +%% @spec parse_qs(string() | binary()) -> [{Key, Value}] +%% @doc Parse a query string or application/x-www-form-urlencoded. +parse_qs(Binary) when is_binary(Binary) -> + parse_qs(binary_to_list(Binary)); +parse_qs(String) -> + parse_qs(String, []). + +parse_qs([], Acc) -> + lists:reverse(Acc); +parse_qs(String, Acc) -> + {Key, Rest} = parse_qs_key(String), + {Value, Rest1} = parse_qs_value(Rest), + parse_qs(Rest1, [{Key, Value} | Acc]). + +parse_qs_key(String) -> + parse_qs_key(String, []). + +parse_qs_key([], Acc) -> + {qs_revdecode(Acc), ""}; +parse_qs_key([$= | Rest], Acc) -> + {qs_revdecode(Acc), Rest}; +parse_qs_key(Rest=[$; | _], Acc) -> + {qs_revdecode(Acc), Rest}; +parse_qs_key(Rest=[$& | _], Acc) -> + {qs_revdecode(Acc), Rest}; +parse_qs_key([C | Rest], Acc) -> + parse_qs_key(Rest, [C | Acc]). + +parse_qs_value(String) -> + parse_qs_value(String, []). + +parse_qs_value([], Acc) -> + {qs_revdecode(Acc), ""}; +parse_qs_value([$; | Rest], Acc) -> + {qs_revdecode(Acc), Rest}; +parse_qs_value([$& | Rest], Acc) -> + {qs_revdecode(Acc), Rest}; +parse_qs_value([C | Rest], Acc) -> + parse_qs_value(Rest, [C | Acc]). + +%% @spec unquote(string() | binary()) -> string() +%% @doc Unquote a URL encoded string. +unquote(Binary) when is_binary(Binary) -> + unquote(binary_to_list(Binary)); +unquote(String) -> + qs_revdecode(lists:reverse(String)). + +qs_revdecode(S) -> + qs_revdecode(S, []). + +qs_revdecode([], Acc) -> + Acc; +qs_revdecode([$+ | Rest], Acc) -> + qs_revdecode(Rest, [$\s | Acc]); +qs_revdecode([Lo, Hi, ?PERCENT | Rest], Acc) when ?IS_HEX(Lo), ?IS_HEX(Hi) -> + qs_revdecode(Rest, [(unhexdigit(Lo) bor (unhexdigit(Hi) bsl 4)) | Acc]); +qs_revdecode([C | Rest], Acc) -> + qs_revdecode(Rest, [C | Acc]). + +%% @spec urlsplit(Url) -> {Scheme, Netloc, Path, Query, Fragment} +%% @doc Return a 5-tuple, does not expand % escapes. Only supports HTTP style +%% URLs. +urlsplit(Url) -> + {Scheme, Url1} = urlsplit_scheme(Url), + {Netloc, Url2} = urlsplit_netloc(Url1), + {Path, Query, Fragment} = urlsplit_path(Url2), + {Scheme, Netloc, Path, Query, Fragment}. + +urlsplit_scheme(Url) -> + case urlsplit_scheme(Url, []) of + no_scheme -> + {"", Url}; + Res -> + Res + end. + +urlsplit_scheme([C | Rest], Acc) when ((C >= $a andalso C =< $z) orelse + (C >= $A andalso C =< $Z) orelse + (C >= $0 andalso C =< $9) orelse + C =:= $+ orelse C =:= $- orelse + C =:= $.) -> + urlsplit_scheme(Rest, [C | Acc]); +urlsplit_scheme([$: | Rest], Acc=[_ | _]) -> + {string:to_lower(lists:reverse(Acc)), Rest}; +urlsplit_scheme(_Rest, _Acc) -> + no_scheme. + +urlsplit_netloc("//" ++ Rest) -> + urlsplit_netloc(Rest, []); +urlsplit_netloc(Path) -> + {"", Path}. + +urlsplit_netloc("", Acc) -> + {lists:reverse(Acc), ""}; +urlsplit_netloc(Rest=[C | _], Acc) when C =:= $/; C =:= $?; C =:= $# -> + {lists:reverse(Acc), Rest}; +urlsplit_netloc([C | Rest], Acc) -> + urlsplit_netloc(Rest, [C | Acc]). + + +%% @spec path_split(string()) -> {Part, Rest} +%% @doc Split a path starting from the left, as in URL traversal. +%% path_split("foo/bar") = {"foo", "bar"}, +%% path_split("/foo/bar") = {"", "foo/bar"}. +path_split(S) -> + path_split(S, []). + +path_split("", Acc) -> + {lists:reverse(Acc), ""}; +path_split("/" ++ Rest, Acc) -> + {lists:reverse(Acc), Rest}; +path_split([C | Rest], Acc) -> + path_split(Rest, [C | Acc]). + + +%% @spec urlunsplit({Scheme, Netloc, Path, Query, Fragment}) -> string() +%% @doc Assemble a URL from the 5-tuple. Path must be absolute. +urlunsplit({Scheme, Netloc, Path, Query, Fragment}) -> + lists:flatten([case Scheme of "" -> ""; _ -> [Scheme, "://"] end, + Netloc, + urlunsplit_path({Path, Query, Fragment})]). + +%% @spec urlunsplit_path({Path, Query, Fragment}) -> string() +%% @doc Assemble a URL path from the 3-tuple. +urlunsplit_path({Path, Query, Fragment}) -> + lists:flatten([Path, + case Query of "" -> ""; _ -> [$? | Query] end, + case Fragment of "" -> ""; _ -> [$# | Fragment] end]). + +%% @spec urlsplit_path(Url) -> {Path, Query, Fragment} +%% @doc Return a 3-tuple, does not expand % escapes. Only supports HTTP style +%% paths. +urlsplit_path(Path) -> + urlsplit_path(Path, []). + +urlsplit_path("", Acc) -> + {lists:reverse(Acc), "", ""}; +urlsplit_path("?" ++ Rest, Acc) -> + {Query, Fragment} = urlsplit_query(Rest), + {lists:reverse(Acc), Query, Fragment}; +urlsplit_path("#" ++ Rest, Acc) -> + {lists:reverse(Acc), "", Rest}; +urlsplit_path([C | Rest], Acc) -> + urlsplit_path(Rest, [C | Acc]). + +urlsplit_query(Query) -> + urlsplit_query(Query, []). + +urlsplit_query("", Acc) -> + {lists:reverse(Acc), ""}; +urlsplit_query("#" ++ Rest, Acc) -> + {lists:reverse(Acc), Rest}; +urlsplit_query([C | Rest], Acc) -> + urlsplit_query(Rest, [C | Acc]). + +%% @spec guess_mime(string()) -> string() +%% @doc Guess the mime type of a file by the extension of its filename. +guess_mime(File) -> + case mochiweb_mime:from_extension(filename:extension(File)) of + undefined -> + "text/plain"; + Mime -> + Mime + end. + +%% @spec parse_header(string()) -> {Type, [{K, V}]} +%% @doc Parse a Content-Type like header, return the main Content-Type +%% and a property list of options. +parse_header(String) -> + %% TODO: This is exactly as broken as Python's cgi module. + %% Should parse properly like mochiweb_cookies. + [Type | Parts] = [string:strip(S) || S <- string:tokens(String, ";")], + F = fun (S, Acc) -> + case lists:splitwith(fun (C) -> C =/= $= end, S) of + {"", _} -> + %% Skip anything with no name + Acc; + {_, ""} -> + %% Skip anything with no value + Acc; + {Name, [$\= | Value]} -> + [{string:to_lower(string:strip(Name)), + unquote_header(string:strip(Value))} | Acc] + end + end, + {string:to_lower(Type), + lists:foldr(F, [], Parts)}. + +unquote_header("\"" ++ Rest) -> + unquote_header(Rest, []); +unquote_header(S) -> + S. + +unquote_header("", Acc) -> + lists:reverse(Acc); +unquote_header("\"", Acc) -> + lists:reverse(Acc); +unquote_header([$\\, C | Rest], Acc) -> + unquote_header(Rest, [C | Acc]); +unquote_header([C | Rest], Acc) -> + unquote_header(Rest, [C | Acc]). + +%% @spec record_to_proplist(Record, Fields) -> proplist() +%% @doc calls record_to_proplist/3 with a default TypeKey of '__record' +record_to_proplist(Record, Fields) -> + record_to_proplist(Record, Fields, '__record'). + +%% @spec record_to_proplist(Record, Fields, TypeKey) -> proplist() +%% @doc Return a proplist of the given Record with each field in the +%% Fields list set as a key with the corresponding value in the Record. +%% TypeKey is the key that is used to store the record type +%% Fields should be obtained by calling record_info(fields, record_type) +%% where record_type is the record type of Record +record_to_proplist(Record, Fields, TypeKey) + when tuple_size(Record) - 1 =:= length(Fields) -> + lists:zip([TypeKey | Fields], tuple_to_list(Record)). + + +shell_quote([], Acc) -> + lists:reverse([$\" | Acc]); +shell_quote([C | Rest], Acc) when C =:= $\" orelse C =:= $\` orelse + C =:= $\\ orelse C =:= $\$ -> + shell_quote(Rest, [C, $\\ | Acc]); +shell_quote([C | Rest], Acc) -> + shell_quote(Rest, [C | Acc]). + +%% @spec parse_qvalues(string()) -> [qvalue()] | invalid_qvalue_string +%% @type qvalue() = {media_type() | encoding() , float()}. +%% @type media_type() = string(). +%% @type encoding() = string(). +%% +%% @doc Parses a list (given as a string) of elements with Q values associated +%% to them. Elements are separated by commas and each element is separated +%% from its Q value by a semicolon. Q values are optional but when missing +%% the value of an element is considered as 1.0. A Q value is always in the +%% range [0.0, 1.0]. A Q value list is used for example as the value of the +%% HTTP "Accept" and "Accept-Encoding" headers. +%% +%% Q values are described in section 2.9 of the RFC 2616 (HTTP 1.1). +%% +%% Example: +%% +%% parse_qvalues("gzip; q=0.5, deflate, identity;q=0.0") -> +%% [{"gzip", 0.5}, {"deflate", 1.0}, {"identity", 0.0}] +%% +parse_qvalues(QValuesStr) -> + try + lists:map( + fun(Pair) -> + [Type | Params] = string:tokens(Pair, ";"), + NormParams = normalize_media_params(Params), + {Q, NonQParams} = extract_q(NormParams), + {string:join([string:strip(Type) | NonQParams], ";"), Q} + end, + string:tokens(string:to_lower(QValuesStr), ",") + ) + catch + _Type:_Error -> + invalid_qvalue_string + end. + +normalize_media_params(Params) -> + {ok, Re} = re:compile("\\s"), + normalize_media_params(Re, Params, []). + +normalize_media_params(_Re, [], Acc) -> + lists:reverse(Acc); +normalize_media_params(Re, [Param | Rest], Acc) -> + NormParam = re:replace(Param, Re, "", [global, {return, list}]), + normalize_media_params(Re, Rest, [NormParam | Acc]). + +extract_q(NormParams) -> + {ok, KVRe} = re:compile("^([^=]+)=([^=]+)$"), + {ok, QRe} = re:compile("^((?:0|1)(?:\\.\\d{1,3})?)$"), + extract_q(KVRe, QRe, NormParams, []). + +extract_q(_KVRe, _QRe, [], Acc) -> + {1.0, lists:reverse(Acc)}; +extract_q(KVRe, QRe, [Param | Rest], Acc) -> + case re:run(Param, KVRe, [{capture, [1, 2], list}]) of + {match, [Name, Value]} -> + case Name of + "q" -> + {match, [Q]} = re:run(Value, QRe, [{capture, [1], list}]), + QVal = case Q of + "0" -> + 0.0; + "1" -> + 1.0; + Else -> + list_to_float(Else) + end, + case QVal < 0.0 orelse QVal > 1.0 of + false -> + {QVal, lists:reverse(Acc) ++ Rest} + end; + _ -> + extract_q(KVRe, QRe, Rest, [Param | Acc]) + end + end. + +%% @spec pick_accepted_encodings([qvalue()], [encoding()], encoding()) -> +%% [encoding()] +%% +%% @doc Determines which encodings specified in the given Q values list are +%% valid according to a list of supported encodings and a default encoding. +%% +%% The returned list of encodings is sorted, descendingly, according to the +%% Q values of the given list. The last element of this list is the given +%% default encoding unless this encoding is explicitily or implicitily +%% marked with a Q value of 0.0 in the given Q values list. +%% Note: encodings with the same Q value are kept in the same order as +%% found in the input Q values list. +%% +%% This encoding picking process is described in section 14.3 of the +%% RFC 2616 (HTTP 1.1). +%% +%% Example: +%% +%% pick_accepted_encodings( +%% [{"gzip", 0.5}, {"deflate", 1.0}], +%% ["gzip", "identity"], +%% "identity" +%% ) -> +%% ["gzip", "identity"] +%% +pick_accepted_encodings(AcceptedEncs, SupportedEncs, DefaultEnc) -> + SortedQList = lists:reverse( + lists:sort(fun({_, Q1}, {_, Q2}) -> Q1 < Q2 end, AcceptedEncs) + ), + {Accepted, Refused} = lists:foldr( + fun({E, Q}, {A, R}) -> + case Q > 0.0 of + true -> + {[E | A], R}; + false -> + {A, [E | R]} + end + end, + {[], []}, + SortedQList + ), + Refused1 = lists:foldr( + fun(Enc, Acc) -> + case Enc of + "*" -> + lists:subtract(SupportedEncs, Accepted) ++ Acc; + _ -> + [Enc | Acc] + end + end, + [], + Refused + ), + Accepted1 = lists:foldr( + fun(Enc, Acc) -> + case Enc of + "*" -> + lists:subtract(SupportedEncs, Accepted ++ Refused1) ++ Acc; + _ -> + [Enc | Acc] + end + end, + [], + Accepted + ), + Accepted2 = case lists:member(DefaultEnc, Accepted1) of + true -> + Accepted1; + false -> + Accepted1 ++ [DefaultEnc] + end, + [E || E <- Accepted2, lists:member(E, SupportedEncs), + not lists:member(E, Refused1)]. + +make_io(Atom) when is_atom(Atom) -> + atom_to_list(Atom); +make_io(Integer) when is_integer(Integer) -> + integer_to_list(Integer); +make_io(Io) when is_list(Io); is_binary(Io) -> + Io. + +rand_bytes(Count) -> + list_to_binary([rand_uniform(0, 16#FF + 1) || _ <- lists:seq(1, Count)]). + +rand_uniform(Lo, Hi) -> + random:uniform(Hi - Lo) + Lo - 1. + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +make_io_test() -> + ?assertEqual( + <<"atom">>, + iolist_to_binary(make_io(atom))), + ?assertEqual( + <<"20">>, + iolist_to_binary(make_io(20))), + ?assertEqual( + <<"list">>, + iolist_to_binary(make_io("list"))), + ?assertEqual( + <<"binary">>, + iolist_to_binary(make_io(<<"binary">>))), + ok. + +-record(test_record, {field1=f1, field2=f2}). +record_to_proplist_test() -> + ?assertEqual( + [{'__record', test_record}, + {field1, f1}, + {field2, f2}], + record_to_proplist(#test_record{}, record_info(fields, test_record))), + ?assertEqual( + [{'typekey', test_record}, + {field1, f1}, + {field2, f2}], + record_to_proplist(#test_record{}, + record_info(fields, test_record), + typekey)), + ok. + +shell_quote_test() -> + ?assertEqual( + "\"foo \\$bar\\\"\\`' baz\"", + shell_quote("foo $bar\"`' baz")), + ok. + +cmd_port_test_spool(Port, Acc) -> + receive + {Port, eof} -> + Acc; + {Port, {data, {eol, Data}}} -> + cmd_port_test_spool(Port, ["\n", Data | Acc]); + {Port, Unknown} -> + throw({unknown, Unknown}) + after 1000 -> + throw(timeout) + end. + +cmd_port_test() -> + Port = cmd_port(["echo", "$bling$ `word`!"], + [eof, stream, {line, 4096}]), + Res = try lists:append(lists:reverse(cmd_port_test_spool(Port, []))) + after catch port_close(Port) + end, + self() ! {Port, wtf}, + try cmd_port_test_spool(Port, []) + catch throw:{unknown, wtf} -> ok + end, + try cmd_port_test_spool(Port, []) + catch throw:timeout -> ok + end, + ?assertEqual( + "$bling$ `word`!\n", + Res). + +cmd_test() -> + ?assertEqual( + "$bling$ `word`!\n", + cmd(["echo", "$bling$ `word`!"])), + ok. + +cmd_string_test() -> + ?assertEqual( + "\"echo\" \"\\$bling\\$ \\`word\\`!\"", + cmd_string(["echo", "$bling$ `word`!"])), + ok. + +cmd_status_test() -> + ?assertEqual( + {0, <<"$bling$ `word`!\n">>}, + cmd_status(["echo", "$bling$ `word`!"])), + ok. + + +parse_header_test() -> + ?assertEqual( + {"multipart/form-data", [{"boundary", "AaB03x"}]}, + parse_header("multipart/form-data; boundary=AaB03x")), + %% This tests (currently) intentionally broken behavior + ?assertEqual( + {"multipart/form-data", + [{"b", ""}, + {"cgi", "is"}, + {"broken", "true\"e"}]}, + parse_header("multipart/form-data;b=;cgi=\"i\\s;broken=true\"e;=z;z")), + ok. + +guess_mime_test() -> + "text/plain" = guess_mime(""), + "text/plain" = guess_mime(".text"), + "application/zip" = guess_mime(".zip"), + "application/zip" = guess_mime("x.zip"), + "text/html" = guess_mime("x.html"), + "application/xhtml+xml" = guess_mime("x.xhtml"), + ok. + +path_split_test() -> + {"", "foo/bar"} = path_split("/foo/bar"), + {"foo", "bar"} = path_split("foo/bar"), + {"bar", ""} = path_split("bar"), + ok. + +urlsplit_test() -> + {"", "", "/foo", "", "bar?baz"} = urlsplit("/foo#bar?baz"), + {"http", "host:port", "/foo", "", "bar?baz"} = + urlsplit("http://host:port/foo#bar?baz"), + {"http", "host", "", "", ""} = urlsplit("http://host"), + {"", "", "/wiki/Category:Fruit", "", ""} = + urlsplit("/wiki/Category:Fruit"), + ok. + +urlsplit_path_test() -> + {"/foo/bar", "", ""} = urlsplit_path("/foo/bar"), + {"/foo", "baz", ""} = urlsplit_path("/foo?baz"), + {"/foo", "", "bar?baz"} = urlsplit_path("/foo#bar?baz"), + {"/foo", "", "bar?baz#wibble"} = urlsplit_path("/foo#bar?baz#wibble"), + {"/foo", "bar", "baz"} = urlsplit_path("/foo?bar#baz"), + {"/foo", "bar?baz", "baz"} = urlsplit_path("/foo?bar?baz#baz"), + ok. + +urlunsplit_test() -> + "/foo#bar?baz" = urlunsplit({"", "", "/foo", "", "bar?baz"}), + "http://host:port/foo#bar?baz" = + urlunsplit({"http", "host:port", "/foo", "", "bar?baz"}), + ok. + +urlunsplit_path_test() -> + "/foo/bar" = urlunsplit_path({"/foo/bar", "", ""}), + "/foo?baz" = urlunsplit_path({"/foo", "baz", ""}), + "/foo#bar?baz" = urlunsplit_path({"/foo", "", "bar?baz"}), + "/foo#bar?baz#wibble" = urlunsplit_path({"/foo", "", "bar?baz#wibble"}), + "/foo?bar#baz" = urlunsplit_path({"/foo", "bar", "baz"}), + "/foo?bar?baz#baz" = urlunsplit_path({"/foo", "bar?baz", "baz"}), + ok. + +join_test() -> + ?assertEqual("foo,bar,baz", + join(["foo", "bar", "baz"], $,)), + ?assertEqual("foo,bar,baz", + join(["foo", "bar", "baz"], ",")), + ?assertEqual("foo bar", + join([["foo", " bar"]], ",")), + ?assertEqual("foo bar,baz", + join([["foo", " bar"], "baz"], ",")), + ?assertEqual("foo", + join(["foo"], ",")), + ?assertEqual("foobarbaz", + join(["foo", "bar", "baz"], "")), + ?assertEqual("foo" ++ [<<>>] ++ "bar" ++ [<<>>] ++ "baz", + join(["foo", "bar", "baz"], <<>>)), + ?assertEqual("foobar" ++ [<<"baz">>], + join(["foo", "bar", <<"baz">>], "")), + ?assertEqual("", + join([], "any")), + ok. + +quote_plus_test() -> + "foo" = quote_plus(foo), + "1" = quote_plus(1), + "1.1" = quote_plus(1.1), + "foo" = quote_plus("foo"), + "foo+bar" = quote_plus("foo bar"), + "foo%0A" = quote_plus("foo\n"), + "foo%0A" = quote_plus("foo\n"), + "foo%3B%26%3D" = quote_plus("foo;&="), + "foo%3B%26%3D" = quote_plus(<<"foo;&=">>), + ok. + +unquote_test() -> + ?assertEqual("foo bar", + unquote("foo+bar")), + ?assertEqual("foo bar", + unquote("foo%20bar")), + ?assertEqual("foo\r\n", + unquote("foo%0D%0A")), + ?assertEqual("foo\r\n", + unquote(<<"foo%0D%0A">>)), + ok. + +urlencode_test() -> + "foo=bar&baz=wibble+%0D%0A&z=1" = urlencode([{foo, "bar"}, + {"baz", "wibble \r\n"}, + {z, 1}]), + ok. + +parse_qs_test() -> + ?assertEqual( + [{"foo", "bar"}, {"baz", "wibble \r\n"}, {"z", "1"}], + parse_qs("foo=bar&baz=wibble+%0D%0a&z=1")), + ?assertEqual( + [{"", "bar"}, {"baz", "wibble \r\n"}, {"z", ""}], + parse_qs("=bar&baz=wibble+%0D%0a&z=")), + ?assertEqual( + [{"foo", "bar"}, {"baz", "wibble \r\n"}, {"z", "1"}], + parse_qs(<<"foo=bar&baz=wibble+%0D%0a&z=1">>)), + ?assertEqual( + [], + parse_qs("")), + ?assertEqual( + [{"foo", ""}, {"bar", ""}, {"baz", ""}], + parse_qs("foo;bar&baz")), + ok. + +partition_test() -> + {"foo", "", ""} = partition("foo", "/"), + {"foo", "/", "bar"} = partition("foo/bar", "/"), + {"foo", "/", ""} = partition("foo/", "/"), + {"", "/", "bar"} = partition("/bar", "/"), + {"f", "oo/ba", "r"} = partition("foo/bar", "oo/ba"), + ok. + +safe_relative_path_test() -> + "foo" = safe_relative_path("foo"), + "foo/" = safe_relative_path("foo/"), + "foo" = safe_relative_path("foo/bar/.."), + "bar" = safe_relative_path("foo/../bar"), + "bar/" = safe_relative_path("foo/../bar/"), + "" = safe_relative_path("foo/.."), + "" = safe_relative_path("foo/../"), + undefined = safe_relative_path("/foo"), + undefined = safe_relative_path("../foo"), + undefined = safe_relative_path("foo/../.."), + undefined = safe_relative_path("foo//"), + undefined = safe_relative_path("foo\\bar"), + ok. + +parse_qvalues_test() -> + [] = parse_qvalues(""), + [{"identity", 0.0}] = parse_qvalues("identity;q=0"), + [{"identity", 0.0}] = parse_qvalues("identity ;q=0"), + [{"identity", 0.0}] = parse_qvalues(" identity; q =0 "), + [{"identity", 0.0}] = parse_qvalues("identity ; q = 0"), + [{"identity", 0.0}] = parse_qvalues("identity ; q= 0.0"), + [{"gzip", 1.0}, {"deflate", 1.0}, {"identity", 0.0}] = parse_qvalues( + "gzip,deflate,identity;q=0.0" + ), + [{"deflate", 1.0}, {"gzip", 1.0}, {"identity", 0.0}] = parse_qvalues( + "deflate,gzip,identity;q=0.0" + ), + [{"gzip", 1.0}, {"deflate", 1.0}, {"gzip", 1.0}, {"identity", 0.0}] = + parse_qvalues("gzip,deflate,gzip,identity;q=0"), + [{"gzip", 1.0}, {"deflate", 1.0}, {"identity", 0.0}] = parse_qvalues( + "gzip, deflate , identity; q=0.0" + ), + [{"gzip", 1.0}, {"deflate", 1.0}, {"identity", 0.0}] = parse_qvalues( + "gzip; q=1, deflate;q=1.0, identity;q=0.0" + ), + [{"gzip", 0.5}, {"deflate", 1.0}, {"identity", 0.0}] = parse_qvalues( + "gzip; q=0.5, deflate;q=1.0, identity;q=0" + ), + [{"gzip", 0.5}, {"deflate", 1.0}, {"identity", 0.0}] = parse_qvalues( + "gzip; q=0.5, deflate , identity;q=0.0" + ), + [{"gzip", 0.5}, {"deflate", 0.8}, {"identity", 0.0}] = parse_qvalues( + "gzip; q=0.5, deflate;q=0.8, identity;q=0.0" + ), + [{"gzip", 0.5}, {"deflate", 1.0}, {"identity", 1.0}] = parse_qvalues( + "gzip; q=0.5,deflate,identity" + ), + [{"gzip", 0.5}, {"deflate", 1.0}, {"identity", 1.0}, {"identity", 1.0}] = + parse_qvalues("gzip; q=0.5,deflate,identity, identity "), + [{"text/html;level=1", 1.0}, {"text/plain", 0.5}] = + parse_qvalues("text/html;level=1, text/plain;q=0.5"), + [{"text/html;level=1", 0.3}, {"text/plain", 1.0}] = + parse_qvalues("text/html;level=1;q=0.3, text/plain"), + [{"text/html;level=1", 0.3}, {"text/plain", 1.0}] = + parse_qvalues("text/html; level = 1; q = 0.3, text/plain"), + [{"text/html;level=1", 0.3}, {"text/plain", 1.0}] = + parse_qvalues("text/html;q=0.3;level=1, text/plain"), + invalid_qvalue_string = parse_qvalues("gzip; q=1.1, deflate"), + invalid_qvalue_string = parse_qvalues("gzip; q=0.5, deflate;q=2"), + invalid_qvalue_string = parse_qvalues("gzip, deflate;q=AB"), + invalid_qvalue_string = parse_qvalues("gzip; q=2.1, deflate"), + invalid_qvalue_string = parse_qvalues("gzip; q=0.1234, deflate"), + invalid_qvalue_string = parse_qvalues("text/html;level=1;q=0.3, text/html;level"), + ok. + +pick_accepted_encodings_test() -> + ["identity"] = pick_accepted_encodings( + [], + ["gzip", "identity"], + "identity" + ), + ["gzip", "identity"] = pick_accepted_encodings( + [{"gzip", 1.0}], + ["gzip", "identity"], + "identity" + ), + ["identity"] = pick_accepted_encodings( + [{"gzip", 0.0}], + ["gzip", "identity"], + "identity" + ), + ["gzip", "identity"] = pick_accepted_encodings( + [{"gzip", 1.0}, {"deflate", 1.0}], + ["gzip", "identity"], + "identity" + ), + ["gzip", "identity"] = pick_accepted_encodings( + [{"gzip", 0.5}, {"deflate", 1.0}], + ["gzip", "identity"], + "identity" + ), + ["identity"] = pick_accepted_encodings( + [{"gzip", 0.0}, {"deflate", 0.0}], + ["gzip", "identity"], + "identity" + ), + ["gzip"] = pick_accepted_encodings( + [{"gzip", 1.0}, {"deflate", 1.0}, {"identity", 0.0}], + ["gzip", "identity"], + "identity" + ), + ["gzip", "deflate", "identity"] = pick_accepted_encodings( + [{"gzip", 1.0}, {"deflate", 1.0}], + ["gzip", "deflate", "identity"], + "identity" + ), + ["gzip", "deflate"] = pick_accepted_encodings( + [{"gzip", 1.0}, {"deflate", 1.0}, {"identity", 0.0}], + ["gzip", "deflate", "identity"], + "identity" + ), + ["deflate", "gzip", "identity"] = pick_accepted_encodings( + [{"gzip", 0.2}, {"deflate", 1.0}], + ["gzip", "deflate", "identity"], + "identity" + ), + ["deflate", "deflate", "gzip", "identity"] = pick_accepted_encodings( + [{"gzip", 0.2}, {"deflate", 1.0}, {"deflate", 1.0}], + ["gzip", "deflate", "identity"], + "identity" + ), + ["deflate", "gzip", "gzip", "identity"] = pick_accepted_encodings( + [{"gzip", 0.2}, {"deflate", 1.0}, {"gzip", 1.0}], + ["gzip", "deflate", "identity"], + "identity" + ), + ["gzip", "deflate", "gzip", "identity"] = pick_accepted_encodings( + [{"gzip", 0.2}, {"deflate", 0.9}, {"gzip", 1.0}], + ["gzip", "deflate", "identity"], + "identity" + ), + [] = pick_accepted_encodings( + [{"*", 0.0}], + ["gzip", "deflate", "identity"], + "identity" + ), + ["gzip", "deflate", "identity"] = pick_accepted_encodings( + [{"*", 1.0}], + ["gzip", "deflate", "identity"], + "identity" + ), + ["gzip", "deflate", "identity"] = pick_accepted_encodings( + [{"*", 0.6}], + ["gzip", "deflate", "identity"], + "identity" + ), + ["gzip"] = pick_accepted_encodings( + [{"gzip", 1.0}, {"*", 0.0}], + ["gzip", "deflate", "identity"], + "identity" + ), + ["gzip", "deflate"] = pick_accepted_encodings( + [{"gzip", 1.0}, {"deflate", 0.6}, {"*", 0.0}], + ["gzip", "deflate", "identity"], + "identity" + ), + ["deflate", "gzip"] = pick_accepted_encodings( + [{"gzip", 0.5}, {"deflate", 1.0}, {"*", 0.0}], + ["gzip", "deflate", "identity"], + "identity" + ), + ["gzip", "identity"] = pick_accepted_encodings( + [{"deflate", 0.0}, {"*", 1.0}], + ["gzip", "deflate", "identity"], + "identity" + ), + ["gzip", "identity"] = pick_accepted_encodings( + [{"*", 1.0}, {"deflate", 0.0}], + ["gzip", "deflate", "identity"], + "identity" + ), + ok. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/reloader.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/reloader.erl new file mode 100644 index 0000000..8266b33 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/src/reloader.erl @@ -0,0 +1,161 @@ +%% @copyright 2007 Mochi Media, Inc. +%% @author Matthew Dempsky +%% +%% @doc Erlang module for automatically reloading modified modules +%% during development. + +-module(reloader). +-author("Matthew Dempsky "). + +-include_lib("kernel/include/file.hrl"). + +-behaviour(gen_server). +-export([start/0, start_link/0]). +-export([stop/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +-export([all_changed/0]). +-export([is_changed/1]). +-export([reload_modules/1]). +-record(state, {last, tref}). + +%% External API + +%% @spec start() -> ServerRet +%% @doc Start the reloader. +start() -> + gen_server:start({local, ?MODULE}, ?MODULE, [], []). + +%% @spec start_link() -> ServerRet +%% @doc Start the reloader. +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +%% @spec stop() -> ok +%% @doc Stop the reloader. +stop() -> + gen_server:call(?MODULE, stop). + +%% gen_server callbacks + +%% @spec init([]) -> {ok, State} +%% @doc gen_server init, opens the server in an initial state. +init([]) -> + {ok, TRef} = timer:send_interval(timer:seconds(1), doit), + {ok, #state{last = stamp(), tref = TRef}}. + +%% @spec handle_call(Args, From, State) -> tuple() +%% @doc gen_server callback. +handle_call(stop, _From, State) -> + {stop, shutdown, stopped, State}; +handle_call(_Req, _From, State) -> + {reply, {error, badrequest}, State}. + +%% @spec handle_cast(Cast, State) -> tuple() +%% @doc gen_server callback. +handle_cast(_Req, State) -> + {noreply, State}. + +%% @spec handle_info(Info, State) -> tuple() +%% @doc gen_server callback. +handle_info(doit, State) -> + Now = stamp(), + _ = doit(State#state.last, Now), + {noreply, State#state{last = Now}}; +handle_info(_Info, State) -> + {noreply, State}. + +%% @spec terminate(Reason, State) -> ok +%% @doc gen_server termination callback. +terminate(_Reason, State) -> + {ok, cancel} = timer:cancel(State#state.tref), + ok. + + +%% @spec code_change(_OldVsn, State, _Extra) -> State +%% @doc gen_server code_change callback (trivial). +code_change(_Vsn, State, _Extra) -> + {ok, State}. + +%% @spec reload_modules([atom()]) -> [{module, atom()} | {error, term()}] +%% @doc code:purge/1 and code:load_file/1 the given list of modules in order, +%% return the results of code:load_file/1. +reload_modules(Modules) -> + [begin code:purge(M), code:load_file(M) end || M <- Modules]. + +%% @spec all_changed() -> [atom()] +%% @doc Return a list of beam modules that have changed. +all_changed() -> + [M || {M, Fn} <- code:all_loaded(), is_list(Fn), is_changed(M)]. + +%% @spec is_changed(atom()) -> boolean() +%% @doc true if the loaded module is a beam with a vsn attribute +%% and does not match the on-disk beam file, returns false otherwise. +is_changed(M) -> + try + module_vsn(M:module_info()) =/= module_vsn(code:get_object_code(M)) + catch _:_ -> + false + end. + +%% Internal API + +module_vsn({M, Beam, _Fn}) -> + {ok, {M, Vsn}} = beam_lib:version(Beam), + Vsn; +module_vsn(L) when is_list(L) -> + {_, Attrs} = lists:keyfind(attributes, 1, L), + {_, Vsn} = lists:keyfind(vsn, 1, Attrs), + Vsn. + +doit(From, To) -> + [case file:read_file_info(Filename) of + {ok, #file_info{mtime = Mtime}} when Mtime >= From, Mtime < To -> + reload(Module); + {ok, _} -> + unmodified; + {error, enoent} -> + %% The Erlang compiler deletes existing .beam files if + %% recompiling fails. Maybe it's worth spitting out a + %% warning here, but I'd want to limit it to just once. + gone; + {error, Reason} -> + io:format("Error reading ~s's file info: ~p~n", + [Filename, Reason]), + error + end || {Module, Filename} <- code:all_loaded(), is_list(Filename)]. + +reload(Module) -> + io:format("Reloading ~p ...", [Module]), + code:purge(Module), + case code:load_file(Module) of + {module, Module} -> + io:format(" ok.~n"), + case erlang:function_exported(Module, test, 0) of + true -> + io:format(" - Calling ~p:test() ...", [Module]), + case catch Module:test() of + ok -> + io:format(" ok.~n"), + reload; + Reason -> + io:format(" fail: ~p.~n", [Reason]), + reload_but_test_failed + end; + false -> + reload + end; + {error, Reason} -> + io:format(" fail: ~p.~n", [Reason]), + error + end. + + +stamp() -> + erlang:localtime(). + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp.template b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp.template new file mode 100644 index 0000000..4942609 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp.template @@ -0,0 +1,22 @@ +%% -*- erlang -*- +{variables, [{appid, "mochiwebapp"}, + {author, "Mochi Media "}, + {year, "2010"}, + {version, "0.1"}, + {port, 8080}, + {dest, "{{appid}}"}]}. +{dir, "{{dest}}"}. +{template, "mochiwebapp_skel/src/mochiapp.app.src", "{{dest}}/src/{{appid}}.app.src"}. +{template, "mochiwebapp_skel/src/mochiapp.erl", "{{dest}}/src/{{appid}}.erl"}. +{template, "mochiwebapp_skel/src/mochiapp_app.erl", "{{dest}}/src/{{appid}}_app.erl"}. +{template, "mochiwebapp_skel/src/mochiapp_deps.erl", "{{dest}}/src/{{appid}}_deps.erl"}. +{template, "mochiwebapp_skel/src/mochiapp_sup.erl", "{{dest}}/src/{{appid}}_sup.erl"}. +{template, "mochiwebapp_skel/src/mochiapp_web.erl", "{{dest}}/src/{{appid}}_web.erl"}. +{template, "mochiwebapp_skel/start-dev.sh", "{{dest}}/start-dev.sh"}. +{template, "mochiwebapp_skel/priv/www/index.html", "{{dest}}/priv/www/index.html"}. +{file, "../../.gitignore", "{{dest}}/.gitignore"}. +{file, "../../Makefile", "{{dest}}/Makefile"}. +{file, "mochiwebapp_skel/rebar.config", "{{dest}}/rebar.config"}. +{file, "../../rebar", "{{dest}}/rebar"}. +{chmod, 8#755, "{{dest}}/rebar"}. +{chmod, 8#755, "{{dest}}/start-dev.sh"}. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/priv/www/index.html b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/priv/www/index.html new file mode 100644 index 0000000..40ac0c8 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/priv/www/index.html @@ -0,0 +1,8 @@ + + +It Worked + + +{{appid}} running. + + diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/rebar.config b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/rebar.config new file mode 100644 index 0000000..da4939c --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/rebar.config @@ -0,0 +1,7 @@ +%% -*- erlang -*- +{erl_opts, [debug_info]}. +{deps, [ + {mochiweb, ".*", + {git, "git://github.com/mochi/mochiweb.git", {branch, "master"}}}]}. +{cover_enabled, true}. +{eunit_opts, [verbose, {report,{eunit_surefire,[{dir,"."}]}}]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp.app.src b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp.app.src new file mode 100644 index 0000000..c0bb11f --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp.app.src @@ -0,0 +1,9 @@ +%% -*- erlang -*- +{application, {{appid}}, + [{description, "{{appid}}"}, + {vsn, "{{version}}"}, + {modules, []}, + {registered, []}, + {mod, {'{{appid}}_app', []}}, + {env, []}, + {applications, [kernel, stdlib, crypto]}]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp.erl new file mode 100644 index 0000000..9770f2c --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp.erl @@ -0,0 +1,30 @@ +%% @author {{author}} +%% @copyright {{year}} {{author}} + +%% @doc {{appid}}. + +-module({{appid}}). +-author("{{author}}"). +-export([start/0, stop/0]). + +ensure_started(App) -> + case application:start(App) of + ok -> + ok; + {error, {already_started, App}} -> + ok + end. + + +%% @spec start() -> ok +%% @doc Start the {{appid}} server. +start() -> + {{appid}}_deps:ensure(), + ensure_started(crypto), + application:start({{appid}}). + + +%% @spec stop() -> ok +%% @doc Stop the {{appid}} server. +stop() -> + application:stop({{appid}}). diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp_app.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp_app.erl new file mode 100644 index 0000000..6bbb255 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp_app.erl @@ -0,0 +1,22 @@ +%% @author {{author}} +%% @copyright {{appid}} {{author}} + +%% @doc Callbacks for the {{appid}} application. + +-module({{appid}}_app). +-author("{{author}}"). + +-behaviour(application). +-export([start/2,stop/1]). + + +%% @spec start(_Type, _StartArgs) -> ServerRet +%% @doc application start callback for {{appid}}. +start(_Type, _StartArgs) -> + {{appid}}_deps:ensure(), + {{appid}}_sup:start_link(). + +%% @spec stop(_State) -> ServerRet +%% @doc application stop callback for {{appid}}. +stop(_State) -> + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp_deps.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp_deps.erl new file mode 100644 index 0000000..ad151bc --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp_deps.erl @@ -0,0 +1,84 @@ +%% @author {{author}} +%% @copyright {{year}} {{author}} + +%% @doc Ensure that the relatively-installed dependencies are on the code +%% loading path, and locate resources relative +%% to this application's path. + +-module({{appid}}_deps). +-author("{{author}}"). + +-export([ensure/0, ensure/1]). +-export([get_base_dir/0, get_base_dir/1]). +-export([local_path/1, local_path/2]). +-export([deps_on_path/0, new_siblings/1]). + +%% @spec deps_on_path() -> [ProjNameAndVers] +%% @doc List of project dependencies on the path. +deps_on_path() -> + F = fun (X, Acc) -> + ProjDir = filename:dirname(X), + case {filename:basename(X), + filename:basename(filename:dirname(ProjDir))} of + {"ebin", "deps"} -> + [filename:basename(ProjDir) | Acc]; + _ -> + Acc + end + end, + ordsets:from_list(lists:foldl(F, [], code:get_path())). + +%% @spec new_siblings(Module) -> [Dir] +%% @doc Find new siblings paths relative to Module that aren't already on the +%% code path. +new_siblings(Module) -> + Existing = deps_on_path(), + SiblingEbin = filelib:wildcard(local_path(["deps", "*", "ebin"], Module)), + Siblings = [filename:dirname(X) || X <- SiblingEbin, + ordsets:is_element( + filename:basename(filename:dirname(X)), + Existing) =:= false], + lists:filter(fun filelib:is_dir/1, + lists:append([[filename:join([X, "ebin"]), + filename:join([X, "include"])] || + X <- Siblings])). + + +%% @spec ensure(Module) -> ok +%% @doc Ensure that all ebin and include paths for dependencies +%% of the application for Module are on the code path. +ensure(Module) -> + code:add_paths(new_siblings(Module)), + code:clash(), + ok. + +%% @spec ensure() -> ok +%% @doc Ensure that the ebin and include paths for dependencies of +%% this application are on the code path. Equivalent to +%% ensure(?Module). +ensure() -> + ensure(?MODULE). + +%% @spec get_base_dir(Module) -> string() +%% @doc Return the application directory for Module. It assumes Module is in +%% a standard OTP layout application in the ebin or src directory. +get_base_dir(Module) -> + {file, Here} = code:is_loaded(Module), + filename:dirname(filename:dirname(Here)). + +%% @spec get_base_dir() -> string() +%% @doc Return the application directory for this application. Equivalent to +%% get_base_dir(?MODULE). +get_base_dir() -> + get_base_dir(?MODULE). + +%% @spec local_path([string()], Module) -> string() +%% @doc Return an application-relative directory from Module's application. +local_path(Components, Module) -> + filename:join([get_base_dir(Module) | Components]). + +%% @spec local_path(Components) -> string() +%% @doc Return an application-relative directory for this application. +%% Equivalent to local_path(Components, ?MODULE). +local_path(Components) -> + local_path(Components, ?MODULE). diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp_sup.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp_sup.erl new file mode 100644 index 0000000..39db395 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp_sup.erl @@ -0,0 +1,56 @@ +%% @author {{author}} +%% @copyright {{year}} {{author}} + +%% @doc Supervisor for the {{appid}} application. + +-module({{appid}}_sup). +-author("{{author}}"). + +-behaviour(supervisor). + +%% External exports +-export([start_link/0, upgrade/0]). + +%% supervisor callbacks +-export([init/1]). + +%% @spec start_link() -> ServerRet +%% @doc API for starting the supervisor. +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%% @spec upgrade() -> ok +%% @doc Add processes if necessary. +upgrade() -> + {ok, {_, Specs}} = init([]), + + Old = sets:from_list( + [Name || {Name, _, _, _} <- supervisor:which_children(?MODULE)]), + New = sets:from_list([Name || {Name, _, _, _, _, _} <- Specs]), + Kill = sets:subtract(Old, New), + + sets:fold(fun (Id, ok) -> + supervisor:terminate_child(?MODULE, Id), + supervisor:delete_child(?MODULE, Id), + ok + end, ok, Kill), + + [supervisor:start_child(?MODULE, Spec) || Spec <- Specs], + ok. + +%% @spec init([]) -> SupervisorTree +%% @doc supervisor callback. +init([]) -> + Web = web_specs({{appid}}_web, {{port}}), + Processes = [Web], + Strategy = {one_for_one, 10, 10}, + {ok, + {Strategy, lists:flatten(Processes)}}. + +web_specs(Mod, Port) -> + WebConfig = [{ip, {0,0,0,0}}, + {port, Port}, + {docroot, {{appid}}_deps:local_path(["priv", "www"])}], + {Mod, + {Mod, start, [WebConfig]}, + permanent, 5000, worker, dynamic}. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp_web.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp_web.erl new file mode 100644 index 0000000..8976265 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/src/mochiapp_web.erl @@ -0,0 +1,69 @@ +%% @author {{author}} +%% @copyright {{year}} {{author}} + +%% @doc Web server for {{appid}}. + +-module({{appid}}_web). +-author("{{author}}"). + +-export([start/1, stop/0, loop/2]). + +%% External API + +start(Options) -> + {DocRoot, Options1} = get_option(docroot, Options), + Loop = fun (Req) -> + ?MODULE:loop(Req, DocRoot) + end, + mochiweb_http:start([{name, ?MODULE}, {loop, Loop} | Options1]). + +stop() -> + mochiweb_http:stop(?MODULE). + +loop(Req, DocRoot) -> + "/" ++ Path = Req:get(path), + try + case Req:get(method) of + Method when Method =:= 'GET'; Method =:= 'HEAD' -> + case Path of + _ -> + Req:serve_file(Path, DocRoot) + end; + 'POST' -> + case Path of + _ -> + Req:not_found() + end; + _ -> + Req:respond({501, [], []}) + end + catch + Type:What -> + Report = ["web request failed", + {path, Path}, + {type, Type}, {what, What}, + {trace, erlang:get_stacktrace()}], + error_logger:error_report(Report), + %% NOTE: mustache templates need \\ because they are not awesome. + Req:respond({500, [{"Content-Type", "text/plain"}], + "request failed, sorry\\n"}) + end. + +%% Internal API + +get_option(Option, Options) -> + {proplists:get_value(Option, Options), proplists:delete(Option, Options)}. + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +you_should_write_a_test() -> + ?assertEqual( + "No, but I will!", + "Have you written any tests?"), + ok. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/start-dev.sh b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/start-dev.sh new file mode 100755 index 0000000..fb7c45e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/templates/mochiwebapp_skel/start-dev.sh @@ -0,0 +1,6 @@ +#!/bin/sh +# NOTE: mustache templates need \\ because they are not awesome. +exec erl -pa ebin edit deps/*/ebin -boot start_sasl \\ + -sname {{appid}}_dev \\ + -s {{appid}} \\ + -s reloader diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/test-materials/test_ssl_cert.pem b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/test-materials/test_ssl_cert.pem new file mode 100644 index 0000000..f84ccca --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/test-materials/test_ssl_cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDIDCCAgigAwIBAgIJAJLkNZzERPIUMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV +BAMTCWxvY2FsaG9zdDAeFw0xMDAzMTgxOTM5MThaFw0yMDAzMTUxOTM5MThaMBQx +EjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAJeUCOZxbmtngF4S5lXckjSDLc+8C+XjMBYBPyy5eKdJY20AQ1s9/hhp3ulI +8pAvl+xVo4wQ+iBSvOzcy248Q+Xi6+zjceF7UNRgoYPgtJjKhdwcHV3mvFFrS/fp +9ggoAChaJQWDO1OCfUgTWXImhkw+vcDR11OVMAJ/h73dqzJPI9mfq44PTTHfYtgr +v4LAQAOlhXIAa2B+a6PlF6sqDqJaW5jLTcERjsBwnRhUGi7JevQzkejujX/vdA+N +jRBjKH/KLU5h3Q7wUchvIez0PXWVTCnZjpA9aR4m7YV05nKQfxtGd71czYDYk+j8 +hd005jetT4ir7JkAWValBybJVksCAwEAAaN1MHMwHQYDVR0OBBYEFJl9s51SnjJt +V/wgKWqV5Q6jnv1ZMEQGA1UdIwQ9MDuAFJl9s51SnjJtV/wgKWqV5Q6jnv1ZoRik +FjAUMRIwEAYDVQQDEwlsb2NhbGhvc3SCCQCS5DWcxETyFDAMBgNVHRMEBTADAQH/ +MA0GCSqGSIb3DQEBBQUAA4IBAQB2ldLeLCc+lxK5i0EZquLamMBJwDIjGpT0JMP9 +b4XQOK2JABIu54BQIZhwcjk3FDJz/uOW5vm8k1kYni8FCjNZAaRZzCUfiUYTbTKL +Rq9LuIAODyP2dnTqyKaQOOJHvrx9MRZ3XVecXPS0Tib4aO57vCaAbIkmhtYpTWmw +e3t8CAIDVtgvjR6Se0a1JA4LktR7hBu22tDImvCSJn1nVAaHpani6iPBPPdMuMsP +TBoeQfj8VpqBUjCStqJGa8ytjDFX73YaxV2mgrtGwPNme1x3YNRR11yTu7tksyMO +GrmgxNriqYRchBhNEf72AKF0LR1ByKwfbDB9rIsV00HtCgOp +-----END CERTIFICATE----- diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/test-materials/test_ssl_key.pem b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/test-materials/test_ssl_key.pem new file mode 100644 index 0000000..69bbf82 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/support/test-materials/test_ssl_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAl5QI5nFua2eAXhLmVdySNIMtz7wL5eMwFgE/LLl4p0ljbQBD +Wz3+GGne6UjykC+X7FWjjBD6IFK87NzLbjxD5eLr7ONx4XtQ1GChg+C0mMqF3Bwd +Xea8UWtL9+n2CCgAKFolBYM7U4J9SBNZciaGTD69wNHXU5UwAn+Hvd2rMk8j2Z+r +jg9NMd9i2Cu/gsBAA6WFcgBrYH5ro+UXqyoOolpbmMtNwRGOwHCdGFQaLsl69DOR +6O6Nf+90D42NEGMof8otTmHdDvBRyG8h7PQ9dZVMKdmOkD1pHibthXTmcpB/G0Z3 +vVzNgNiT6PyF3TTmN61PiKvsmQBZVqUHJslWSwIDAQABAoIBACI8Ky5xHDFh9RpK +Rn/KC7OUlTpADKflgizWJ0Cgu2F9L9mkn5HyFHvLHa+u7CootbWJOiEejH/UcBtH +WyMQtX0snYCpdkUpJv5wvMoebGu+AjHOn8tfm9T/2O6rhwgckLyMb6QpGbMo28b1 +p9QiY17BJPZx7qJQJcHKsAvwDwSThlb7MFmWf42LYWlzybpeYQvwpd+UY4I0WXLu +/dqJIS9Npq+5Y5vbo2kAEAssb2hSCvhCfHmwFdKmBzlvgOn4qxgZ1iHQgfKI6Z3Y +J0573ZgOVTuacn+lewtdg5AaHFcl/zIYEr9SNqRoPNGbPliuv6k6N2EYcufWL5lR +sCmmmHECgYEAxm+7OpepGr++K3+O1e1MUhD7vSPkKJrCzNtUxbOi2NWj3FFUSPRU +adWhuxvUnZgTcgM1+KuQ0fB2VmxXe9IDcrSFS7PKFGtd2kMs/5mBw4UgDZkOQh+q +kDiBEV3HYYJWRq0w3NQ/9Iy1jxxdENHtGmG9aqamHxNtuO608wGW2S8CgYEAw4yG +ZyAic0Q/U9V2OHI0MLxLCzuQz17C2wRT1+hBywNZuil5YeTuIt2I46jro6mJmWI2 +fH4S/geSZzg2RNOIZ28+aK79ab2jWBmMnvFCvaru+odAuser4N9pfAlHZvY0pT+S +1zYX3f44ygiio+oosabLC5nWI0zB2gG8pwaJlaUCgYEAgr7poRB+ZlaCCY0RYtjo +mYYBKD02vp5BzdKSB3V1zeLuBWM84pjB6b3Nw0fyDig+X7fH3uHEGN+USRs3hSj6 +BqD01s1OT6fyfbYXNw5A1r+nP+5h26Wbr0zblcKxdQj4qbbBZC8hOJNhqTqqA0Qe +MmzF7jiBaiZV/Cyj4x1f9BcCgYEAhjL6SeuTuOctTqs/5pz5lDikh6DpUGcH8qaV +o6aRAHHcMhYkZzpk8yh1uUdD7516APmVyvn6rrsjjhLVq4ZAJjwB6HWvE9JBN0TR +bILF+sREHUqU8Zn2Ku0nxyfXCKIOnxlx/J/y4TaGYqBqfXNFWiXNUrjQbIlQv/xR +K48g/MECgYBZdQlYbMSDmfPCC5cxkdjrkmAl0EgV051PWAi4wR+hLxIMRjHBvAk7 +IweobkFvT4TICulgroLkYcSa5eOZGxB/DHqcQCbWj3reFV0VpzmTDoFKG54sqBRl +vVntGt0pfA40fF17VoS7riAdHF53ippTtsovHEsg5tq5NrBl5uKm2g== +-----END RSA PRIVATE KEY----- diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/test/mochiweb_base64url_tests.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/test/mochiweb_base64url_tests.erl new file mode 100644 index 0000000..69f276a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/test/mochiweb_base64url_tests.erl @@ -0,0 +1,27 @@ +-module(mochiweb_base64url_tests). +-include_lib("eunit/include/eunit.hrl"). + +id(X) -> + ?assertEqual( + X, + mochiweb_base64url:decode(mochiweb_base64url:encode(X))), + ?assertEqual( + X, + mochiweb_base64url:decode( + binary_to_list(mochiweb_base64url:encode(binary_to_list(X))))). + +random_binary(Short,Long) -> + << <<(random:uniform(256) - 1)>> + || _ <- lists:seq(1, Short + random:uniform(1 + Long - Short) - 1) >>. + +empty_test() -> + id(<<>>). + +onechar_test() -> + [id(<>) || C <- lists:seq(0,255)], + ok. + +nchar_test() -> + %% 1000 tests of 2-6 char strings + [id(B) || _ <- lists:seq(1,1000), B <- [random_binary(2, 6)]], + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/test/mochiweb_html_tests.erl b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/test/mochiweb_html_tests.erl new file mode 100644 index 0000000..3d35400 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/mochiweb-wrapper/mochiweb-git/test/mochiweb_html_tests.erl @@ -0,0 +1,589 @@ +-module(mochiweb_html_tests). +-include_lib("eunit/include/eunit.hrl"). + +to_html_test() -> + ?assertEqual( + <<"hey!

what's up

sucka
RAW!">>, + iolist_to_binary( + mochiweb_html:to_html({html, [], + [{<<"head">>, [], + [{title, <<"hey!">>}]}, + {body, [], + [{p, [{class, foo}], [<<"what's">>, <<" up">>, {br}]}, + {'div', <<"sucka">>}, + {'=', <<"RAW!">>}, + {comment, <<" comment! ">>}]}]}))), + ?assertEqual( + <<"">>, + iolist_to_binary( + mochiweb_html:to_html({doctype, + [<<"html">>, <<"PUBLIC">>, + <<"-//W3C//DTD XHTML 1.0 Transitional//EN">>, + <<"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">>]}))), + ?assertEqual( + <<"">>, + iolist_to_binary( + mochiweb_html:to_html({<<"html">>,[], + [{pi, <<"xml:namespace">>, + [{<<"prefix">>,<<"o">>}, + {<<"ns">>,<<"urn:schemas-microsoft-com:office:office">>}]}]}))), + ok. + +escape_test() -> + ?assertEqual( + <<"&quot;\"word ><<up!&quot;">>, + mochiweb_html:escape(<<""\"word ><>)), + ?assertEqual( + <<"&quot;\"word ><<up!&quot;">>, + mochiweb_html:escape(""\"word ><>, + mochiweb_html:escape('"\"word >< + ?assertEqual( + <<"&quot;"word ><<up!&quot;">>, + mochiweb_html:escape_attr(<<""\"word ><>)), + ?assertEqual( + <<"&quot;"word ><<up!&quot;">>, + mochiweb_html:escape_attr(""\"word ><>, + mochiweb_html:escape_attr('"\"word ><>, + mochiweb_html:escape_attr(12345)), + ?assertEqual( + <<"1.5">>, + mochiweb_html:escape_attr(1.5)), + ok. + +tokens_test() -> + ?assertEqual( + [{start_tag, <<"foo">>, [{<<"bar">>, <<"baz">>}, + {<<"wibble">>, <<"wibble">>}, + {<<"alice">>, <<"bob">>}], true}], + mochiweb_html:tokens(<<"">>)), + ?assertEqual( + [{start_tag, <<"foo">>, [{<<"bar">>, <<"baz">>}, + {<<"wibble">>, <<"wibble">>}, + {<<"alice">>, <<"bob">>}], true}], + mochiweb_html:tokens(<<"">>)), + ?assertEqual( + [{comment, <<"[if lt IE 7]>\n\n>}], + mochiweb_html:tokens(<<"">>)), + ?assertEqual( + [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false}, + {data, <<" A= B <= C ">>, false}, + {end_tag, <<"script">>}], + mochiweb_html:tokens(<<"">>)), + ?assertEqual( + [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false}, + {data, <<" A= B <= C ">>, false}, + {end_tag, <<"script">>}], + mochiweb_html:tokens(<<"">>)), + ?assertEqual( + [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false}, + {data, <<" A= B <= C ">>, false}, + {end_tag, <<"script">>}], + mochiweb_html:tokens(<<"">>)), + ?assertEqual( + [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false}, + {data, <<" A= B <= C ">>, false}, + {end_tag, <<"script">>}], + mochiweb_html:tokens(<<"">>)), + ?assertEqual( + [{start_tag, <<"textarea">>, [], false}, + {data, <<"">>, false}, + {end_tag, <<"textarea">>}], + mochiweb_html:tokens(<<"">>)), + ?assertEqual( + [{start_tag, <<"textarea">>, [], false}, + {data, <<"">>, false}], + mochiweb_html:tokens(<<" + + + + + + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/queue.ejs b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/queue.ejs new file mode 100644 index 0000000..6ac46e9 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/queue.ejs @@ -0,0 +1,252 @@ +

Queue <%= fmt_string(queue.name) %>

+ +
+

Overview

+
+ <%= queue_lengths('lengths-q', queue) %> +<% if (statistics_level == 'fine') { %> + <%= message_rates('msg-rates-q', queue.message_stats) %> +<% } %> + +
+

Details

+ + + + + + + + + + + + + +
Parameters<%= fmt_parameters(queue) %>
Policy<%= fmt_string(queue.policy, '') %>
Exclusive owner + <% if (queue.owner_pid_details == undefined) { %> + None + <% } else { %> + <%= link_conn(queue.owner_pid_details.name) %> + <% } %> +
+ + + + + + + + + + + + + + + + + + +
State<%= fmt_object_state(queue) %>
Consumers<%= fmt_string(queue.consumers) %>
Consumer utilisation <%= fmt_percent(queue.consumer_utilisation) %>
Memory<%= fmt_bytes(queue.memory) %>
+ + + + + + + + + + +
+ Paging + + <% var messages_ram = queue.backing_queue_status.ram_msg_count + queue.backing_queue_status.ram_ack_count; %> + <% if (messages_ram == queue.messages) { %> + No paging + <% } else { %> + <%= fmt_num_thousands(messages_ram) %> / + <%= fmt_num_thousands(queue.messages) %> msg (in RAM / total) + <% } %> + + <% if (queue.backing_queue_status.target_ram_count == 'infinity') { %> + No limit + <% } else { %> + RAM target: <%= fmt_num_thousands(queue.backing_queue_status.target_ram_count) %> msg + <% } %> + +
+ Persistent + + <%= fmt_num_thousands(queue.backing_queue_status.persistent_count) %> msg +
+ + +<% if (vhosts_interesting) { %> + + + + +<% } %> +<% if (nodes_interesting) { %> + + + + + + + + +<% } %> +
Virtual host<%= fmt_string(queue.vhost) %>
Node<%= fmt_node(queue.node) %>
Slaves + <% + var has_unsynced_node = false; + for (var i in queue.slave_nodes) { + var node = queue.slave_nodes[i]; + %> + <% + if (jQuery.inArray(node, queue.synchronised_slave_nodes) == -1) { + has_unsynced_node = true; + %> + <%= fmt_node(node) %> (unsynchronised) + <% } else { %> + <%= fmt_node(node) %> + <% } %> +
+ <% } %> + <% if (queue.state == 'syncing') { %> + + + + + +
+ <%= fmt_sync_state(queue) %> + +
+ + + + +
+
+ <% } else if (has_unsynced_node) { %> +
+ + + + +
+ <% } %> +
+
+
+
+ +<% if (statistics_level == 'fine') { %> +
+

Message rates breakdown

+
+ + + + + +
+ <%= format('msg-detail-publishes', + {'mode': 'queue', + 'object': queue.incoming, + 'label': 'Incoming'}) %> + + + <%= format('msg-detail-deliveries', + {'mode': 'queue', + 'object': queue.deliveries}) %> +
+
+
+<% } %> + +
+

Consumers

+
+<%= format('consumers', {'mode': 'queue', 'consumers': queue.consumer_details}) %> +
+
+ +
+

Bindings

+
+
+ <%= format('bindings', {'mode': 'queue', 'bindings': bindings}) %> +

+

This queue

+ + <%= format('add-binding', {'mode': 'queue', 'parent': queue}) %> +
+
+
+ +<%= format('publish', {'mode': 'queue', 'queue': queue}) %> + +
+

Get messages

+
+

+ Warning: getting messages from a queue is a destructive action. + +

+
+ + + + + + + + + + + + + + + + +
+ +
+ + +
+ +
+
+
+
+ +
+

Delete / purge

+
+
+ + + + +
+ +
+ + + + +
+
+
diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/queues.ejs b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/queues.ejs new file mode 100644 index 0000000..a579ee9 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/queues.ejs @@ -0,0 +1,176 @@ +

Queues

+
+

All queues

+
+<%= filter_ui(queues) %> +
+<% if (queues.length > 0) { %> +<% + var col_redeliver = !is_col_empty(queues, 'redeliver'); +%> + + + + + +<% if (statistics_level == 'fine') { %> + +<% } %> + + +<% if (vhosts_interesting) { %> + +<% } %> + +<% if (nodes_interesting) { %> + +<% } %> + + + + + + + +<% if (statistics_level == 'fine') { %> + + + +<% } %> + + + +<% + for (var i = 0; i < queues.length; i++) { + var queue = queues[i]; +%> + > +<% if (vhosts_interesting) { %> + +<% } %> + +<% if (nodes_interesting) { %> + +<% } %> + + + + + + + +<% if (statistics_level == 'fine') { %> + + + +<% } %> + + <% } %> + +
OverviewMessagesMessage rates
<%= fmt_sort('Virtual host', 'vhost') %><%= fmt_sort('Name', 'name') %><%= fmt_sort('Node', 'node') %><%= fmt_sort('Exclusive', 'owner_pid_details.name') %>Parameters<%= fmt_sort('Policy', 'policy') %><%= fmt_sort('State', 'state') %><%= fmt_sort('Ready', 'messages_ready') %><%= fmt_sort('Unacked', 'messages_unacknowledged') %><%= fmt_sort('Total', 'messages') %><%= fmt_sort('incoming', 'message_stats.publish_details.rate') %><%= fmt_sort('deliver / get', 'message_stats.deliver_get_details.rate') %> + <% if (col_redeliver) { %> + <%= fmt_sort('of which redelivered', 'message_stats.redeliver_details.rate') %> + <% } %> +<%= fmt_sort('ack', 'message_stats.ack_details.rate') %>
<%= fmt_string(queue.vhost) %><%= link_queue(queue.vhost, queue.name, queue.arguments) %> + <%= fmt_node(queue.node) %> + <%= fmt_mirrors(queue) %> + <% if (queue.state == 'syncing') { %> + <%= fmt_sync_state(queue) %> + <% } %> + + <% if (queue.owner_pid_details != undefined) { %> + <%= link_conn(queue.owner_pid_details.name, "Owner") %> + <% } %> + <%= fmt_parameters_short(queue) %><%= fmt_string(queue.policy, '') %><%= fmt_object_state(queue) %><%= fmt_num_thousands(queue.messages_ready) %><%= fmt_num_thousands(queue.messages_unacknowledged) %><%= fmt_num_thousands(queue.messages) %><%= fmt_rate(queue.message_stats, 'publish') %><%= fmt_deliver_rate(queue.message_stats, col_redeliver) %><%= fmt_rate(queue.message_stats, 'ack') %>
+<% } else { %> +

... no queues ...

+<% } %> +
+
+
+ +
+

Add a new queue

+
+
+ +<% if (vhosts_interesting) { %> + + + + +<% } else { %> + +<% } %> + + + + + + + + +<% + if (nodes_interesting) { + var nodes = JSON.parse(sync_get('/nodes')); +%> + + + + +<% } %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
*
+ +
+ +
+ +
ms
ms
+ +
+
+
diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/rate-options.ejs b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/rate-options.ejs new file mode 100644 index 0000000..904eb8e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/rate-options.ejs @@ -0,0 +1,45 @@ +<% + var id = span.attr('for'); + var mode = get_pref('rate-mode-' + id); + var size = get_pref('chart-size-' + id); + var range = get_pref('chart-range-' + id); +%> + +
+ + + + + + + + + + + + + + + + + +
+

This time series

+
+ <%= fmt_radio('mode', 'Chart', 'chart', mode) %> + <%= fmt_radio('mode', 'Current value', 'curr', mode) %> + <%= fmt_radio('mode', 'Moving average', 'avg', mode) %> +
+ <%= fmt_radio('size', 'Small', 'small', size) %> + <%= fmt_radio('size', 'Medium', 'medium', size) %> + <%= fmt_radio('size', 'Large', 'large', size) %> +
+<% + for (p in CHART_PERIODS) { +%> + <%= fmt_radio('range', CHART_PERIODS[p], p, range) %> +<% + } +%> +
+
diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/registry.ejs b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/registry.ejs new file mode 100644 index 0000000..38d98e1 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/registry.ejs @@ -0,0 +1,25 @@ +<% if (node.running) { %> + + + + +<% if (show_enabled) { %> + +<% } %> + + <% + for (var i = 0; i < list.length; i++) { + var item = list[i]; + %> + > + + +<% if (show_enabled) { %> + +<% } %> + + <% } %> +
NameDescriptionEnabled
<%= fmt_string(item.name) %><%= fmt_string(item.description) %><%= fmt_boolean(item.enabled) %>
+<% } else {%> +

...node not running...

+<% } %> diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/status.ejs b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/status.ejs new file mode 100644 index 0000000..74c2a95 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/status.ejs @@ -0,0 +1 @@ +

<%= text %>

diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/user.ejs b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/user.ejs new file mode 100644 index 0000000..0201744 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/user.ejs @@ -0,0 +1,85 @@ +

User: <%= fmt_string(user.name) %>

+ +<% if (permissions.length == 0) { %> +

+ This user does not have permission to access any virtual hosts.
+ Use "Set Permission" below to grant permission to access virtual hosts. +

+<% } %> + +
+

Overview

+
+ + + + + + + + + +
Tags<%= fmt_string(user.tags) %>
Can log in with password<%= fmt_boolean(user.password_hash.length > 0) %>
+
+
+ +<%= format('permissions', {'mode': 'user', 'permissions': permissions, 'vhosts': vhosts, 'parent': user}) %> + +
+

Update this user

+
+
+ + + + + + + + + + +
+ + +
+ + *
+ + * + (confirm) +
+ +
+ + + + [Admin] + [Monitoring] + [Policymaker] + [Management] + [None] + +
+ +
+
+
+ + +
+

Delete this user

+
+
+ + +
+
+
diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/users.ejs b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/users.ejs new file mode 100644 index 0000000..1f4ba28 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/users.ejs @@ -0,0 +1,92 @@ +

Users

+
+

All users

+
+<%= filter_ui(users) %> +
+<% if (users.length > 0) { %> + + + + + + + + + + + <% + for (var i = 0; i < users.length; i++) { + var user = users[i]; + %> + > + + + + + + <% } %> + +
<%= fmt_sort('Name', 'name') %><%= fmt_sort('Tags', 'tags') %>Can access virtual hostsHas password
<%= link_user(user.name) %><%= fmt_string(user.tags) %><%= fmt_permissions(user, permissions, 'user', 'vhost', + '

No access

') %>
<%= fmt_boolean(user.password_hash.length > 0) %>
+<% } else { %> +

... no vhosts ...

+<% } %> +

+
+
+
+ +
+

Add a user

+
+
+ + + + + + + + + + + + + +
+ + * +
+ + +
+ + *
+ + * + (confirm) +
+ +
+ + + + [Admin] + [Monitoring] + [Policymaker] + [Management] + [None] + +
+ +
+
+
diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/vhost.ejs b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/vhost.ejs new file mode 100644 index 0000000..3a92f4a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/vhost.ejs @@ -0,0 +1,40 @@ +

Virtual Host: <%= fmt_string(vhost.name) %>

+ +<% if (permissions.length == 0) { %> +

+ No users have permission to access this virtual host.
+ Use "Set Permission" below to grant users permission to access this virtual host. +

+<% } %> + +
+

Overview

+
+ <%= queue_lengths('lengths-vhost', vhost) %> +<% if (statistics_level == 'fine') { %> + <%= message_rates('msg-rates-vhost', vhost.message_stats) %> +<% } %> + <%= data_rates('data-rates-vhost', vhost, 'Data rates') %> +
+

Details

+ + + + + +
Tracing enabled:<%= fmt_boolean(vhost.tracing) %>
+
+
+
+ +<%= format('permissions', {'mode': 'vhost', 'permissions': permissions, 'users': users, 'parent': vhost}) %> + +
+

Delete this vhost

+
+
+ + +
+
+
diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/vhosts.ejs b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/vhosts.ejs new file mode 100644 index 0000000..cf9b637 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/priv/www/js/tmpl/vhosts.ejs @@ -0,0 +1,75 @@ +

Virtual Hosts

+ +
+

All virtual hosts

+
+<%= filter_ui(vhosts) %> +
+<% if (vhosts.length > 0) { %> + + + + + + +<% if (statistics_level == 'fine') { %> + +<% } %> + + + + + + + + + +<% if (statistics_level == 'fine') { %> + + + + + <% + for (var i = 0; i < vhosts.length; i++) { + var vhost = vhosts[i]; + %> + > + + + + + + + +<% if (statistics_level == 'fine') { %> + + +<% } %> + + <% } %> + +
OverviewMessagesData ratesMessage rates
<%= fmt_sort('Name', 'name') %>Users <%= fmt_sort('Ready', 'messages_ready') %><%= fmt_sort('Unacked', 'messages_unacknowledged') %><%= fmt_sort('Total', 'messages') %><%= fmt_sort('From clients', 'recv_oct_details.rate') %><%= fmt_sort('To clients', 'send_oct_details.rate') %><%= fmt_sort('publish', 'message_stats.publish_details.rate') %><%= fmt_sort('deliver / get', 'message_stats.deliver_get_details.rate') %> +<% } %> +
<%= link_vhost(vhost.name) %><%= fmt_permissions(vhost, permissions, 'vhost', 'user', + '

No users

') %>
<%= fmt_string(vhost.messages_ready) %><%= fmt_string(vhost.messages_unacknowledged) %><%= fmt_string(vhost.messages) %><%= fmt_rate_bytes(vhost, 'recv_oct') %><%= fmt_rate_bytes(vhost, 'send_oct') %><%= fmt_rate(vhost.message_stats, 'publish') %><%= fmt_deliver_rate(vhost.message_stats, false) %>
+<% } else { %> +

... no vhosts ...

+<% } %> +
+
+
+ +
+

Add a new virtual host

+
+
+ + + + + +
*
+ +
+
+
diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_app.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_app.erl new file mode 100644 index 0000000..a8b0894 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_app.erl @@ -0,0 +1,104 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_app). + +-behaviour(application). +-export([start/2, stop/1]). + +-include("rabbit_mgmt.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +-define(CONTEXT, rabbit_mgmt). +-define(STATIC_PATH, "priv/www"). + +start(_Type, _StartArgs) -> + {ok, Listener} = application:get_env(rabbitmq_management, listener), + setup_wm_logging(), + register_context(Listener), + log_startup(Listener), + rabbit_mgmt_sup_sup:start_link(). + +stop(_State) -> + unregister_context(), + ok. + +register_context(Listener) -> + rabbit_web_dispatch:register_context_handler( + ?CONTEXT, Listener, "", make_loop(), "RabbitMQ Management"). + +unregister_context() -> + rabbit_web_dispatch:unregister_context(?CONTEXT). + +make_loop() -> + Dispatch = rabbit_mgmt_dispatcher:build_dispatcher(), + WMLoop = rabbit_webmachine:makeloop(Dispatch), + LocalPaths = [filename:join(module_path(M), ?STATIC_PATH) || + M <- rabbit_mgmt_dispatcher:modules()], + fun(Req) -> respond(Req, LocalPaths, WMLoop) end. + +module_path(Module) -> + {file, Here} = code:is_loaded(Module), + filename:dirname(filename:dirname(Here)). + +respond(Req, LocalPaths, WMLoop) -> + Path = Req:get(path), + Redirect = fun(L) -> {301, [{"Location", L}], ""} end, + case Path of + "/api/" ++ Rest when length(Rest) > 0 -> + WMLoop(Req); + "" -> + Req:respond(Redirect("/")); + "/mgmt/" -> + Req:respond(Redirect("/")); + "/mgmt" -> + Req:respond(Redirect("/")); + "/" ++ Stripped -> + serve_file(Req, Stripped, LocalPaths, Redirect) + end. + +serve_file(Req, Path, [LocalPath], _Redirect) -> + Req:serve_file(Path, LocalPath); +serve_file(Req, Path, [LocalPath | Others], Redirect) -> + Path1 = filename:join([LocalPath, Path]), + case filelib:is_regular(Path1) of + true -> Req:serve_file(Path, LocalPath); + false -> case filelib:is_regular(Path1 ++ "/index.html") of + true -> index(Req, Path, LocalPath, Redirect); + false -> serve_file(Req, Path, Others, Redirect) + end + end. + +index(Req, Path, LocalPath, Redirect) -> + case lists:reverse(Path) of + "" -> Req:serve_file("index.html", LocalPath); + "/" ++ _ -> Req:serve_file(Path ++ "index.html", LocalPath); + _ -> Req:respond(Redirect(Path ++ "/")) + end. + +setup_wm_logging() -> + rabbit_webmachine:setup(), + {ok, LogDir} = application:get_env(rabbitmq_management, http_log_dir), + case LogDir of + none -> ok; + _ -> webmachine_log:add_handler(webmachine_log_handler, [LogDir]) + end. + +log_startup(Listener) -> + rabbit_log:info("Management plugin started. Port: ~w~n", [port(Listener)]). + +port(Listener) -> + proplists:get_value(port, Listener). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_db.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_db.erl new file mode 100644 index 0000000..eda933f --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_db.erl @@ -0,0 +1,1083 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_db). + +-include("rabbit_mgmt.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +-behaviour(gen_server2). + +-export([start_link/0]). + +-export([augment_exchanges/3, augment_queues/3, + augment_nodes/1, augment_vhosts/2, + get_channel/2, get_connection/2, + get_all_channels/1, get_all_connections/1, + get_overview/2, get_overview/1]). + +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3, handle_pre_hibernate/1, prioritise_cast/3, + format_message_queue/2]). + +%% For testing +-export([override_lookups/1, reset_lookups/0]). + +-import(rabbit_misc, [pget/3, pset/3]). + +%% The management database listens to events broadcast via the +%% rabbit_event mechanism, and responds to queries from the various +%% rabbit_mgmt_wm_* modules. It handles several kinds of events, and +%% slices and dices them in various ways. +%% +%% There are three types of events coming in: created (when an object +%% is created, containing immutable facts about it), stats (emitted on +%% a timer, with mutable facts about the object), and deleted (just +%% containing the object's ID). In this context "objects" means +%% connections, channels, exchanges, queues, consumers, vhosts and +%% nodes. Note that we do not care about users, permissions, bindings, +%% parameters or policies. +%% +%% Connections and channels are identified by pids. Queues and +%% exchanges are identified by names (which are #resource{}s). VHosts +%% and nodes are identified by names which are binaries. And consumers +%% are identified by {ChPid, QName, CTag}. +%% +%% The management database records the "created" events for +%% connections, channels and consumers, and can thus be authoritative +%% about those objects. For queues, exchanges and nodes we go to +%% Mnesia to find out the immutable details of the objects. +%% +%% For everything other than consumers, the database can then augment +%% these immutable details with stats, as the object changes. (We +%% never emit anything very interesting about consumers). +%% +%% Stats on the inbound side are refered to as coarse- and +%% fine-grained. Fine grained statistics are the message rates +%% maintained by channels and associated with tuples: {publishing +%% channel, exchange}, {publishing channel, exchange, queue} and +%% {queue, consuming channel}. Coarse grained stats are everything +%% else and are associated with only one object, not a tuple. +%% +%% Within the management database though we rearrange things a bit: we +%% refer to basic stats, simple stats and detail stats. +%% +%% Basic stats are those coarse grained stats for which we do not +%% retain a history and do not perform any calculations - +%% e.g. connection.state or channel.prefetch_count. +%% +%% Simple stats are those for which we do history / calculations which +%% are associated with one object *after aggregation* - so these might +%% originate with coarse grained stats - e.g. connection.send_oct or +%% queue.messages_ready. But they might also originate from fine +%% grained stats which have been aggregated - e.g. the message rates +%% for a vhost or queue. +%% +%% Finally, detailed stats are those for which we do history / +%% calculations which are associated with two objects. These +%% have to have originated as fine grained stats, but can still have +%% been aggregated. +%% +%% Created events and basic stats are stored in ETS tables by object, +%% looked up in an orddict in #state.tables. Simple and detailed stats +%% (which only differ depending on how they're keyed) are stored in +%% #state.aggregated_stats. +%% +%% For detailed stats we also store an index for each object referencing +%% all the other objects that form a detailed stats key with it. This is +%% so that we can always avoid table scanning while deleting stats and +%% thus make sure that handling deleted events is O(n)-ish. +%% +%% For each key for simple and detailed stats we maintain a #stats{} +%% record, essentially a base counter for everything that happened +%% before the samples we have kept, and a gb_tree of {timestamp, +%% sample} values. +%% +%% We also have #state.old_stats to let us calculate instantaneous +%% rates, in order to apportion simple / detailed stats into time +%% slices as they come in. These instantaneous rates are not returned +%% in response to any query, the rates shown in the API are calculated +%% at query time. old_stats contains both coarse and fine +%% entries. Coarse entries are pruned when the corresponding object is +%% deleted, and fine entries are pruned when the emitting channel is +%% closed, and whenever we receive new fine stats from a channel. So +%% it's quite close to being a cache of "the previous stats we +%% received". +%% +%% We also keep a timer going, in order to prune old samples from +%% #state.aggregated_stats. +%% +%% Overall the object is to do all the aggregation when events come +%% in, and make queries be simple lookups as much as possible. One +%% area where this does not happen is the global overview - which is +%% aggregated from vhost stats at query time since we do not want to +%% reveal anything about other vhosts to unprivileged users. + +-record(state, { + %% "stats" for which no calculations are required + tables, + %% database of aggregated samples + aggregated_stats, + %% index for detailed aggregated_stats that have 2-tuple keys + aggregated_stats_index, + %% What the previous info item was for any given + %% {queue/channel/connection} + old_stats, + gc_timer, + gc_next_key, + lookups, + interval, + event_refresh_ref}). + +-define(FINE_STATS_TYPES, [channel_queue_stats, channel_exchange_stats, + channel_queue_exchange_stats]). +-define(TABLES, [queue_stats, connection_stats, channel_stats, + consumers_by_queue, consumers_by_channel, + node_stats]). + +-define(DELIVER_GET, [deliver, deliver_no_ack, get, get_no_ack]). +-define(FINE_STATS, [publish, publish_in, publish_out, + ack, deliver_get, confirm, return_unroutable, redeliver] ++ + ?DELIVER_GET). + +-define(COARSE_QUEUE_STATS, + [messages, messages_ready, messages_unacknowledged]). + +-define(COARSE_CONN_STATS, [recv_oct, send_oct]). + +-define(GC_INTERVAL, 5000). +-define(GC_MIN_ROWS, 100). +-define(GC_MIN_RATIO, 0.01). + +-define(DROP_LENGTH, 1000). + +prioritise_cast({event, #event{type = Type, + props = Props}}, Len, _State) + when (Type =:= channel_stats orelse + Type =:= queue_stats) andalso Len > ?DROP_LENGTH -> + case pget(idle_since, Props) of + unknown -> drop; + _ -> 0 + end; +prioritise_cast(_Msg, _Len, _State) -> + 0. + +%%---------------------------------------------------------------------------- +%% API +%%---------------------------------------------------------------------------- + +start_link() -> + Ref = make_ref(), + case gen_server2:start_link({global, ?MODULE}, ?MODULE, [Ref], []) of + {ok, Pid} -> register(?MODULE, Pid), %% [1] + rabbit:force_event_refresh(Ref), + {ok, Pid}; + Else -> Else + end. +%% [1] For debugging it's helpful to locally register the name too +%% since that shows up in places global names don't. + +%% R = Ranges, M = Mode +augment_exchanges(Xs, R, M) -> safe_call({augment_exchanges, Xs, R, M}, Xs). +augment_queues(Qs, R, M) -> safe_call({augment_queues, Qs, R, M}, Qs). +augment_vhosts(VHosts, R) -> safe_call({augment_vhosts, VHosts, R}, VHosts). +augment_nodes(Nodes) -> safe_call({augment_nodes, Nodes}, Nodes). + +get_channel(Name, R) -> safe_call({get_channel, Name, R}, not_found). +get_connection(Name, R) -> safe_call({get_connection, Name, R}, not_found). + +get_all_channels(R) -> safe_call({get_all_channels, R}). +get_all_connections(R) -> safe_call({get_all_connections, R}). + +get_overview(User, R) -> safe_call({get_overview, User, R}). +get_overview(R) -> safe_call({get_overview, all, R}). + +override_lookups(Lookups) -> safe_call({override_lookups, Lookups}). +reset_lookups() -> safe_call(reset_lookups). + +safe_call(Term) -> safe_call(Term, []). +safe_call(Term, Default) -> safe_call(Term, Default, 1). + +%% See rabbit_mgmt_sup_sup for a discussion of the retry logic. +safe_call(Term, Default, Retries) -> + try + gen_server2:call({global, ?MODULE}, Term, infinity) + catch exit:{noproc, _} -> + case Retries of + 0 -> Default; + _ -> rabbit_mgmt_sup_sup:start_child(), + safe_call(Term, Default, Retries - 1) + end + end. + +%%---------------------------------------------------------------------------- +%% Internal, gen_server2 callbacks +%%---------------------------------------------------------------------------- + +init([Ref]) -> + %% When Rabbit is overloaded, it's usually especially important + %% that the management plugin work. + process_flag(priority, high), + {ok, Interval} = application:get_env(rabbit, collect_statistics_interval), + rabbit_node_monitor:subscribe(self()), + rabbit_log:info("Statistics database started.~n"), + Table = fun () -> ets:new(rabbit_mgmt_db, [ordered_set]) end, + Tables = orddict:from_list([{Key, Table()} || Key <- ?TABLES]), + {ok, set_gc_timer( + reset_lookups( + #state{interval = Interval, + tables = Tables, + old_stats = Table(), + aggregated_stats = Table(), + aggregated_stats_index = Table(), + event_refresh_ref = Ref})), hibernate, + {backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE}}. + +handle_call({augment_exchanges, Xs, Ranges, basic}, _From, State) -> + reply(list_exchange_stats(Ranges, Xs, State), State); + +handle_call({augment_exchanges, Xs, Ranges, full}, _From, State) -> + reply(detail_exchange_stats(Ranges, Xs, State), State); + +handle_call({augment_queues, Qs, Ranges, basic}, _From, State) -> + reply(list_queue_stats(Ranges, Qs, State), State); + +handle_call({augment_queues, Qs, Ranges, full}, _From, State) -> + reply(detail_queue_stats(Ranges, Qs, State), State); + +handle_call({augment_vhosts, VHosts, Ranges}, _From, State) -> + reply(vhost_stats(Ranges, VHosts, State), State); + +handle_call({augment_nodes, Nodes}, _From, State) -> + {reply, node_stats(Nodes, State), State}; + +handle_call({get_channel, Name, Ranges}, _From, + State = #state{tables = Tables}) -> + case created_event(Name, channel_stats, Tables) of + not_found -> reply(not_found, State); + Ch -> [Result] = detail_channel_stats(Ranges, [Ch], State), + reply(Result, State) + end; + +handle_call({get_connection, Name, Ranges}, _From, + State = #state{tables = Tables}) -> + case created_event(Name, connection_stats, Tables) of + not_found -> reply(not_found, State); + Conn -> [Result] = connection_stats(Ranges, [Conn], State), + reply(Result, State) + end; + +handle_call({get_all_channels, Ranges}, _From, + State = #state{tables = Tables}) -> + Chans = created_events(channel_stats, Tables), + reply(list_channel_stats(Ranges, Chans, State), State); + +handle_call({get_all_connections, Ranges}, _From, + State = #state{tables = Tables}) -> + Conns = created_events(connection_stats, Tables), + reply(connection_stats(Ranges, Conns, State), State); + +handle_call({get_overview, User, Ranges}, _From, + State = #state{tables = Tables}) -> + VHosts = case User of + all -> rabbit_vhost:list(); + _ -> rabbit_mgmt_util:list_visible_vhosts(User) + end, + %% TODO: there's no reason we can't do an overview of send_oct and + %% recv_oct now! + VStats = [read_simple_stats(vhost_stats, VHost, State) || + VHost <- VHosts], + MessageStats = [overview_sum(Type, VStats) || Type <- ?FINE_STATS], + QueueStats = [overview_sum(Type, VStats) || Type <- ?COARSE_QUEUE_STATS], + F = case User of + all -> fun (L) -> length(L) end; + _ -> fun (L) -> length(rabbit_mgmt_util:filter_user(L, User)) end + end, + %% Filtering out the user's consumers would be rather expensive so let's + %% just not show it + Consumers = case User of + all -> Table = orddict:fetch(consumers_by_queue, Tables), + [{consumers, ets:info(Table, size)}]; + _ -> [] + end, + ObjectTotals = Consumers ++ + [{queues, length([Q || V <- VHosts, + Q <- rabbit_amqqueue:list(V)])}, + {exchanges, length([X || V <- VHosts, + X <- rabbit_exchange:list(V)])}, + {connections, F(created_events(connection_stats, Tables))}, + {channels, F(created_events(channel_stats, Tables))}], + reply([{message_stats, format_samples(Ranges, MessageStats, State)}, + {queue_totals, format_samples(Ranges, QueueStats, State)}, + {object_totals, ObjectTotals}], State); + +handle_call({override_lookups, Lookups}, _From, State) -> + reply(ok, State#state{lookups = Lookups}); + +handle_call(reset_lookups, _From, State) -> + reply(ok, reset_lookups(State)); + +handle_call(_Request, _From, State) -> + reply(not_understood, State). + +%% Only handle events that are real, or pertain to a force-refresh +%% that we instigated. +handle_cast({event, Event = #event{reference = none}}, State) -> + handle_event(Event, State), + noreply(State); + +handle_cast({event, Event = #event{reference = Ref}}, + State = #state{event_refresh_ref = Ref}) -> + handle_event(Event, State), + noreply(State); + +handle_cast(_Request, State) -> + noreply(State). + +handle_info(gc, State) -> + noreply(set_gc_timer(gc_batch(State))); + +handle_info({node_down, Node}, State = #state{tables = Tables}) -> + Conns = created_events(connection_stats, Tables), + Chs = created_events(channel_stats, Tables), + delete_all_from_node(connection_closed, Node, Conns, State), + delete_all_from_node(channel_closed, Node, Chs, State), + noreply(State); + +handle_info(_Info, State) -> + noreply(State). + +terminate(_Arg, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +reply(Reply, NewState) -> {reply, Reply, NewState, hibernate}. +noreply(NewState) -> {noreply, NewState, hibernate}. + +set_gc_timer(State) -> + TRef = erlang:send_after(?GC_INTERVAL, self(), gc), + State#state{gc_timer = TRef}. + +reset_lookups(State) -> + State#state{lookups = [{exchange, fun rabbit_exchange:lookup/1}, + {queue, fun rabbit_amqqueue:lookup/1}]}. + +handle_pre_hibernate(State) -> + %% rabbit_event can end up holding on to some memory after a busy + %% workout, but it's not a gen_server so we can't make it + %% hibernate. The best we can do is forcibly GC it here (if + %% rabbit_mgmt_db is hibernating the odds are rabbit_event is + %% quiescing in some way too). + rpc:multicall( + rabbit_mnesia:cluster_nodes(running), rabbit_mgmt_db_handler, gc, []), + {hibernate, State}. + +format_message_queue(Opt, MQ) -> rabbit_misc:format_message_queue(Opt, MQ). + +delete_all_from_node(Type, Node, Items, State) -> + [case node(Pid) of + Node -> handle_event(#event{type = Type, props = [{pid, Pid}]}, State); + _ -> ok + end || Item <- Items, Pid <- [pget(pid, Item)]]. + +%%---------------------------------------------------------------------------- +%% Internal, utilities +%%---------------------------------------------------------------------------- + +pget(Key, List) -> pget(Key, List, unknown). + +%% id_name() and id() are for use when handling events, id_lookup() +%% for when augmenting. The difference is that when handling events a +%% queue name will be a resource, but when augmenting we will be +%% passed a queue proplist that will already have been formatted - +%% i.e. it will have name and vhost keys. +id_name(node_stats) -> name; +id_name(vhost_stats) -> name; +id_name(queue_stats) -> name; +id_name(exchange_stats) -> name; +id_name(channel_stats) -> pid; +id_name(connection_stats) -> pid. + +id(Type, List) -> pget(id_name(Type), List). + +id_lookup(queue_stats, List) -> + rabbit_misc:r(pget(vhost, List), queue, pget(name, List)); +id_lookup(exchange_stats, List) -> + rabbit_misc:r(pget(vhost, List), exchange, pget(name, List)); +id_lookup(Type, List) -> + id(Type, List). + +lookup_element(Table, Key) -> lookup_element(Table, Key, 2). + +lookup_element(Table, Key, Pos) -> + try ets:lookup_element(Table, Key, Pos) + catch error:badarg -> [] + end. + +fine_stats_id(ChPid, {Q, X}) -> {ChPid, Q, X}; +fine_stats_id(ChPid, QorX) -> {ChPid, QorX}. + +floor(TS, #state{interval = Interval}) -> + rabbit_mgmt_util:floor(rabbit_mgmt_format:timestamp_ms(TS), Interval). +ceil(TS, #state{interval = Interval}) -> + rabbit_mgmt_util:ceil (rabbit_mgmt_format:timestamp_ms(TS), Interval). + +details_key(Key) -> list_to_atom(atom_to_list(Key) ++ "_details"). + +%%---------------------------------------------------------------------------- +%% Internal, event-receiving side +%%---------------------------------------------------------------------------- + +handle_event(#event{type = queue_stats, props = Stats, timestamp = Timestamp}, + State) -> + handle_stats(queue_stats, Stats, Timestamp, + [{fun rabbit_mgmt_format:properties/1,[backing_queue_status]}, + {fun rabbit_mgmt_format:timestamp/1, [idle_since]}, + {fun rabbit_mgmt_format:queue_state/1, [state]}], + [messages, messages_ready, messages_unacknowledged], State); + +handle_event(Event = #event{type = queue_deleted, + props = [{name, Name}], + timestamp = Timestamp}, + State = #state{old_stats = OldTable}) -> + delete_consumers(Name, consumers_by_queue, consumers_by_channel, State), + %% This is fiddly. Unlike for connections and channels, we need to + %% decrease any amalgamated coarse stats for [messages, + %% messages_ready, messages_unacknowledged] for this queue - since + %% the queue's deletion means we have really got rid of messages! + Id = {coarse, {queue_stats, Name}}, + %% This ceil must correspond to the ceil in append_samples/5 + TS = ceil(Timestamp, State), + OldStats = lookup_element(OldTable, Id), + [record_sample(Id, {Key, -pget(Key, OldStats, 0), TS, State}, State) + || Key <- ?COARSE_QUEUE_STATS], + delete_samples(channel_queue_stats, {'_', Name}, State), + delete_samples(queue_exchange_stats, {Name, '_'}, State), + delete_samples(queue_stats, Name, State), + handle_deleted(queue_stats, Event, State); + +handle_event(Event = #event{type = exchange_deleted, + props = [{name, Name}]}, State) -> + delete_samples(channel_exchange_stats, {'_', Name}, State), + delete_samples(queue_exchange_stats, {'_', Name}, State), + delete_samples(exchange_stats, Name, State), + handle_deleted(exchange_stats, Event, State); + +handle_event(#event{type = vhost_deleted, + props = [{name, Name}]}, State) -> + delete_samples(vhost_stats, Name, State), + {ok, State}; + +handle_event(#event{type = connection_created, props = Stats}, State) -> + handle_created( + connection_stats, Stats, + [{fun rabbit_mgmt_format:addr/1, [host, peer_host]}, + {fun rabbit_mgmt_format:port/1, [port, peer_port]}, + {fun rabbit_mgmt_format:protocol/1, [protocol]}, + {fun rabbit_mgmt_format:amqp_table/1, [client_properties]}], State); + +handle_event(#event{type = connection_stats, props = Stats, + timestamp = Timestamp}, + State) -> + handle_stats(connection_stats, Stats, Timestamp, [], ?COARSE_CONN_STATS, + State); + +handle_event(Event = #event{type = connection_closed, + props = [{pid, Pid}]}, State) -> + delete_samples(connection_stats, Pid, State), + handle_deleted(connection_stats, Event, State); + +handle_event(#event{type = channel_created, props = Stats}, State) -> + handle_created(channel_stats, Stats, [], State); + +handle_event(#event{type = channel_stats, props = Stats, timestamp = Timestamp}, + State = #state{old_stats = OldTable}) -> + handle_stats(channel_stats, Stats, Timestamp, + [{fun rabbit_mgmt_format:timestamp/1, [idle_since]}], + [], State), + ChPid = id(channel_stats, Stats), + AllStats = [old_fine_stats(Type, Stats, State) + || Type <- ?FINE_STATS_TYPES], + ets:match_delete(OldTable, {{fine, {ChPid, '_'}}, '_'}), + ets:match_delete(OldTable, {{fine, {ChPid, '_', '_'}}, '_'}), + [handle_fine_stats(Timestamp, AllStatsElem, State) + || AllStatsElem <- AllStats], + {ok, State}; + +handle_event(Event = #event{type = channel_closed, + props = [{pid, Pid}]}, + State = #state{old_stats = Old}) -> + delete_consumers(Pid, consumers_by_channel, consumers_by_queue, State), + delete_samples(channel_queue_stats, {Pid, '_'}, State), + delete_samples(channel_exchange_stats, {Pid, '_'}, State), + delete_samples(channel_stats, Pid, State), + handle_deleted(channel_stats, Event, State), + ets:match_delete(Old, {{fine, {Pid, '_'}}, '_'}), + ets:match_delete(Old, {{fine, {Pid, '_', '_'}}, '_'}); + +handle_event(#event{type = consumer_created, props = Props}, State) -> + Fmt = [{fun rabbit_mgmt_format:amqp_table/1, [arguments]}], + handle_consumer(fun(Table, Id, P0) -> + P = rabbit_mgmt_format:format(P0, Fmt), + ets:insert(Table, {Id, P}) + end, + Props, State); + +handle_event(#event{type = consumer_deleted, props = Props}, State) -> + handle_consumer(fun(Table, Id, _P) -> ets:delete(Table, Id) end, + Props, State); + +%% TODO: we don't clear up after dead nodes here - this is a very tiny +%% leak every time a node is permanently removed from the cluster. Do +%% we care? +handle_event(#event{type = node_stats, props = Stats, timestamp = Timestamp}, + State = #state{tables = Tables}) -> + Table = orddict:fetch(node_stats, Tables), + ets:insert(Table, {{pget(name, Stats), stats}, + proplists:delete(name, Stats), Timestamp}), + {ok, State}; + +handle_event(_Event, State) -> + {ok, State}. + +handle_created(TName, Stats, Funs, State = #state{tables = Tables}) -> + Formatted = rabbit_mgmt_format:format(Stats, Funs), + ets:insert(orddict:fetch(TName, Tables), {{id(TName, Stats), create}, + Formatted, + pget(name, Stats)}), + {ok, State}. + +handle_stats(TName, Stats, Timestamp, Funs, RatesKeys, + State = #state{tables = Tables, old_stats = OldTable}) -> + Id = id(TName, Stats), + IdSamples = {coarse, {TName, Id}}, + OldStats = lookup_element(OldTable, IdSamples), + append_samples(Stats, Timestamp, OldStats, IdSamples, RatesKeys, State), + StripKeys = [id_name(TName)] ++ RatesKeys ++ ?FINE_STATS_TYPES, + Stats1 = [{K, V} || {K, V} <- Stats, not lists:member(K, StripKeys)], + Stats2 = rabbit_mgmt_format:format(Stats1, Funs), + ets:insert(orddict:fetch(TName, Tables), {{Id, stats}, Stats2, Timestamp}), + {ok, State}. + +handle_deleted(TName, #event{props = Props}, State = #state{tables = Tables, + old_stats = Old}) -> + Id = id(TName, Props), + case orddict:find(TName, Tables) of + {ok, Table} -> ets:delete(Table, {Id, create}), + ets:delete(Table, {Id, stats}); + error -> ok + end, + ets:delete(Old, {coarse, {TName, Id}}), + {ok, State}. + +handle_consumer(Fun, Props, State = #state{tables = Tables}) -> + P = rabbit_mgmt_format:format(Props, []), + CTag = pget(consumer_tag, P), + Q = pget(queue, P), + Ch = pget(channel, P), + QTable = orddict:fetch(consumers_by_queue, Tables), + ChTable = orddict:fetch(consumers_by_channel, Tables), + Fun(QTable, {Q, Ch, CTag}, P), + Fun(ChTable, {Ch, Q, CTag}, P), + {ok, State}. + +%% The consumer_deleted event is emitted by queues themselves - +%% therefore in the event that a queue dies suddenly we may not get +%% it. The best way to handle this is to make sure we also clean up +%% consumers when we hear about any queue going down. +delete_consumers(PrimId, PrimTableName, SecTableName, + #state{tables = Tables}) -> + Table1 = orddict:fetch(PrimTableName, Tables), + Table2 = orddict:fetch(SecTableName, Tables), + SecIdCTags = ets:match(Table1, {{PrimId, '$1', '$2'}, '_'}), + ets:match_delete(Table1, {{PrimId, '_', '_'}, '_'}), + [ets:delete(Table2, {SecId, PrimId, CTag}) || [SecId, CTag] <- SecIdCTags]. + +old_fine_stats(Type, Props, #state{old_stats = Old}) -> + case pget(Type, Props) of + unknown -> ignore; + AllFineStats0 -> ChPid = id(channel_stats, Props), + [begin + Id = fine_stats_id(ChPid, Ids), + {Id, Stats, lookup_element(Old, {fine, Id})} + end || {Ids, Stats} <- AllFineStats0] + end. + +handle_fine_stats(_Timestamp, ignore, _State) -> + ok; + +handle_fine_stats(Timestamp, AllStats, State) -> + [handle_fine_stat(Id, Stats, Timestamp, OldStats, State) || + {Id, Stats, OldStats} <- AllStats]. + +handle_fine_stat(Id, Stats, Timestamp, OldStats, State) -> + Total = lists:sum([V || {K, V} <- Stats, lists:member(K, ?DELIVER_GET)]), + Stats1 = case Total of + 0 -> Stats; + _ -> [{deliver_get, Total}|Stats] + end, + append_samples(Stats1, Timestamp, OldStats, {fine, Id}, all, State). + +delete_samples(Type, {Id, '_'}, State) -> + delete_samples_with_index(Type, Id, fun forward/2, State); +delete_samples(Type, {'_', Id}, State) -> + delete_samples_with_index(Type, Id, fun reverse/2, State); +delete_samples(Type, Id, #state{aggregated_stats = ETS}) -> + ets:match_delete(ETS, delete_match(Type, Id)). + +delete_samples_with_index(Type, Id, Order, + #state{aggregated_stats = ETS, + aggregated_stats_index = ETSi}) -> + Ids2 = lists:append(ets:match(ETSi, {{Type, Id, '$1'}})), + ets:match_delete(ETSi, {{Type, Id, '_'}}), + [begin + ets:match_delete(ETS, delete_match(Type, Order(Id, Id2))), + ets:match_delete(ETSi, {{Type, Id2, Id}}) + end || Id2 <- Ids2]. + +forward(A, B) -> {A, B}. +reverse(A, B) -> {B, A}. + +delete_match(Type, Id) -> {{{Type, Id}, '_'}, '_'}. + +append_samples(Stats, TS, OldStats, Id, Keys, + State = #state{old_stats = OldTable}) -> + case ignore_coarse_sample(Id, State) of + false -> + %% This ceil must correspond to the ceil in handle_event + %% queue_deleted + NewMS = ceil(TS, State), + case Keys of + all -> [append_sample(Key, Value, NewMS, OldStats, Id, State) + || {Key, Value} <- Stats]; + _ -> [append_sample( + Key, pget(Key, Stats), NewMS, OldStats, Id, State) + || Key <- Keys] + end, + ets:insert(OldTable, {Id, Stats}); + true -> + ok + end. + +append_sample(Key, Value, NewMS, OldStats, Id, State) when is_number(Value) -> + record_sample( + Id, {Key, Value - pget(Key, OldStats, 0), NewMS, State}, State); + +append_sample(_Key, _Value, _NewMS, _OldStats, _Id, _State) -> + ok. + +ignore_coarse_sample({coarse, {queue_stats, Q}}, State) -> + not object_exists(Q, State); +ignore_coarse_sample(_, _) -> + false. + +record_sample({coarse, Id}, Args, State) -> + record_sample0(Id, Args), + record_sample0({vhost_stats, vhost(Id, State)}, Args); + +%% Deliveries / acks (Q -> Ch) +record_sample({fine, {Ch, Q = #resource{kind = queue}}}, Args, State) -> + case object_exists(Q, State) of + true -> record_sample0({channel_queue_stats, {Ch, Q}}, Args), + record_sample0({queue_stats, Q}, Args); + false -> ok + end, + record_sample0({channel_stats, Ch}, Args), + record_sample0({vhost_stats, vhost(Q)}, Args); + +%% Publishes / confirms (Ch -> X) +record_sample({fine, {Ch, X = #resource{kind = exchange}}}, Args, State) -> + case object_exists(X, State) of + true -> record_sample0({channel_exchange_stats, {Ch, X}}, Args), + record_sampleX(publish_in, X, Args); + false -> ok + end, + record_sample0({channel_stats, Ch}, Args), + record_sample0({vhost_stats, vhost(X)}, Args); + +%% Publishes (but not confirms) (Ch -> X -> Q) +record_sample({fine, {_Ch, + Q = #resource{kind = queue}, + X = #resource{kind = exchange}}}, Args, State) -> + %% TODO This one logically feels like it should be here. It would + %% correspond to "publishing channel message rates to queue" - + %% which would be nice to handle - except we don't. And just + %% uncommenting this means it gets merged in with "consuming + %% channel delivery from queue" - which is not very helpful. + %% record_sample0({channel_queue_stats, {Ch, Q}}, Args), + QExists = object_exists(Q, State), + XExists = object_exists(X, State), + case QExists of + true -> record_sample0({queue_stats, Q}, Args); + false -> ok + end, + case QExists andalso XExists of + true -> record_sample0({queue_exchange_stats, {Q, X}}, Args); + false -> ok + end, + case XExists of + true -> record_sampleX(publish_out, X, Args); + false -> ok + end. + +%% We have to check the queue and exchange objects still exist since +%% their deleted event could be overtaken by a channel stats event +%% which contains fine stats referencing them. That's also why we +%% don't need to check the channels exist - their deleted event can't +%% be overtaken by their own last stats event. +%% +%% Also, sometimes the queue_deleted event is not emitted by the queue +%% (in the nodedown case) - so it can overtake the final queue_stats +%% event (which is not *guaranteed* to be lost). So we make a similar +%% check for coarse queue stats. +%% +%% We can be sure that mnesia will be up to date by the time we receive +%% the event (even though we dirty read) since the deletions are +%% synchronous and we do not emit the deleted event until after the +%% deletion has occurred. +object_exists(Name = #resource{kind = Kind}, #state{lookups = Lookups}) -> + case (pget(Kind, Lookups))(Name) of + {ok, _} -> true; + _ -> false + end. + +vhost(#resource{virtual_host = VHost}) -> VHost. + +vhost({queue_stats, #resource{virtual_host = VHost}}, _State) -> + VHost; +vhost({TName, Pid}, #state{tables = Tables}) -> + Table = orddict:fetch(TName, Tables), + pget(vhost, lookup_element(Table, {Pid, create})). + +%% exchanges have two sets of "publish" stats, so rearrange things a touch +record_sampleX(RenamePublishTo, X, {publish, Diff, TS, State}) -> + record_sample0({exchange_stats, X}, {RenamePublishTo, Diff, TS, State}); +record_sampleX(_RenamePublishTo, X, {Type, Diff, TS, State}) -> + record_sample0({exchange_stats, X}, {Type, Diff, TS, State}). + +record_sample0(Id0, {Key, Diff, TS, #state{aggregated_stats = ETS, + aggregated_stats_index = ETSi}}) -> + Id = {Id0, Key}, + Old = case lookup_element(ETS, Id) of + [] -> case Id0 of + {Type, {Id1, Id2}} -> + ets:insert(ETSi, {{Type, Id2, Id1}}), + ets:insert(ETSi, {{Type, Id1, Id2}}); + _ -> + ok + end, + rabbit_mgmt_stats:blank(); + E -> E + end, + ets:insert(ETS, {Id, rabbit_mgmt_stats:record(TS, Diff, Old)}). + +%%---------------------------------------------------------------------------- +%% Internal, querying side +%%---------------------------------------------------------------------------- + +-define(QUEUE_DETAILS, + {queue_stats, [{incoming, queue_exchange_stats, fun first/1}, + {deliveries, channel_queue_stats, fun second/1}]}). + +-define(EXCHANGE_DETAILS, + {exchange_stats, [{incoming, channel_exchange_stats, fun second/1}, + {outgoing, queue_exchange_stats, fun second/1}]}). + +-define(CHANNEL_DETAILS, + {channel_stats, [{publishes, channel_exchange_stats, fun first/1}, + {deliveries, channel_queue_stats, fun first/1}]}). + +first(Id) -> {Id, '$1'}. +second(Id) -> {'$1', Id}. + +list_queue_stats(Ranges, Objs, State) -> + adjust_hibernated_memory_use( + merge_stats(Objs, queue_funs(Ranges, State))). + +detail_queue_stats(Ranges, Objs, State) -> + adjust_hibernated_memory_use( + merge_stats(Objs, [consumer_details_fun( + fun (Props) -> id_lookup(queue_stats, Props) end, + consumers_by_queue, State), + detail_stats_fun(Ranges, ?QUEUE_DETAILS, State) + | queue_funs(Ranges, State)])). + +queue_funs(Ranges, State) -> + [basic_stats_fun(queue_stats, State), + simple_stats_fun(Ranges, queue_stats, State), + augment_msg_stats_fun(State)]. + +list_exchange_stats(Ranges, Objs, State) -> + merge_stats(Objs, [simple_stats_fun(Ranges, exchange_stats, State), + augment_msg_stats_fun(State)]). + +detail_exchange_stats(Ranges, Objs, State) -> + merge_stats(Objs, [simple_stats_fun(Ranges, exchange_stats, State), + detail_stats_fun(Ranges, ?EXCHANGE_DETAILS, State), + augment_msg_stats_fun(State)]). + +connection_stats(Ranges, Objs, State) -> + merge_stats(Objs, [basic_stats_fun(connection_stats, State), + simple_stats_fun(Ranges, connection_stats, State), + augment_msg_stats_fun(State)]). + +list_channel_stats(Ranges, Objs, State) -> + merge_stats(Objs, [basic_stats_fun(channel_stats, State), + simple_stats_fun(Ranges, channel_stats, State), + augment_msg_stats_fun(State)]). + +detail_channel_stats(Ranges, Objs, State) -> + merge_stats(Objs, [basic_stats_fun(channel_stats, State), + simple_stats_fun(Ranges, channel_stats, State), + consumer_details_fun( + fun (Props) -> pget(pid, Props) end, + consumers_by_channel, State), + detail_stats_fun(Ranges, ?CHANNEL_DETAILS, State), + augment_msg_stats_fun(State)]). + +vhost_stats(Ranges, Objs, State) -> + merge_stats(Objs, [simple_stats_fun(Ranges, vhost_stats, State)]). + +node_stats(Objs, State) -> + merge_stats(Objs, [basic_stats_fun(node_stats, State)]). + +merge_stats(Objs, Funs) -> + [lists:foldl(fun (Fun, Props) -> Fun(Props) ++ Props end, Obj, Funs) + || Obj <- Objs]. + +%% i.e. the non-calculated stats +basic_stats_fun(Type, #state{tables = Tables}) -> + Table = orddict:fetch(Type, Tables), + fun (Props) -> + Id = id_lookup(Type, Props), + lookup_element(Table, {Id, stats}) + end. + +%% i.e. coarse stats, and fine stats aggregated up to a single number per thing +simple_stats_fun(Ranges, Type, State) -> + fun (Props) -> + Id = id_lookup(Type, Props), + extract_msg_stats( + format_samples(Ranges, read_simple_stats(Type, Id, State), State)) + end. + +%% i.e. fine stats that are broken out per sub-thing +detail_stats_fun(Ranges, {IdType, FineSpecs}, State) -> + fun (Props) -> + Id = id_lookup(IdType, Props), + [detail_stats(Ranges, Name, AggregatedStatsType, IdFun(Id), State) + || {Name, AggregatedStatsType, IdFun} <- FineSpecs] + end. + +read_simple_stats(Type, Id, #state{aggregated_stats = ETS}) -> + FromETS = ets:match(ETS, {{{Type, Id}, '$1'}, '$2'}), + [{K, V} || [K, V] <- FromETS]. + +read_detail_stats(Type, Id, #state{aggregated_stats = ETS}) -> + %% Id must contain '$1' + FromETS = ets:match(ETS, {{{Type, Id}, '$2'}, '$3'}), + %% [[G, K, V]] -> [{G, [{K, V}]}] where G is Q/X/Ch, K is from + %% ?FINE_STATS and V is a stats tree + %% TODO does this need to be optimised? + lists:foldl( + fun ([G, K, V], L) -> + case lists:keyfind(G, 1, L) of + false -> [{G, [{K, V}]} | L]; + {G, KVs} -> lists:keyreplace(G, 1, L, {G, [{K, V} | KVs]}) + end + end, [], FromETS). + +extract_msg_stats(Stats) -> + FineStats = lists:append([[K, details_key(K)] || K <- ?FINE_STATS]), + {MsgStats, Other} = + lists:partition(fun({K, _}) -> lists:member(K, FineStats) end, Stats), + case MsgStats of + [] -> Other; + _ -> [{message_stats, MsgStats} | Other] + end. + +detail_stats(Ranges, Name, AggregatedStatsType, Id, State) -> + {Name, + [[{stats, format_samples(Ranges, KVs, State)} | format_detail_id(G, State)] + || {G, KVs} <- read_detail_stats(AggregatedStatsType, Id, State)]}. + +format_detail_id(ChPid, State) when is_pid(ChPid) -> + augment_msg_stats([{channel, ChPid}], State); +format_detail_id(#resource{name = Name, virtual_host = Vhost, kind = Kind}, + _State) -> + [{Kind, [{name, Name}, {vhost, Vhost}]}]. + +format_samples(Ranges, ManyStats, #state{interval = Interval}) -> + lists:append( + [case rabbit_mgmt_stats:is_blank(Stats) andalso + not lists:member(K, ?COARSE_QUEUE_STATS) of + true -> []; + false -> {Details, Counter} = rabbit_mgmt_stats:format( + pick_range(K, Ranges), + Stats, Interval), + [{K, Counter}, + {details_key(K), Details}] + end || {K, Stats} <- ManyStats]). + +pick_range(K, {RangeL, RangeM, RangeD}) -> + case {lists:member(K, ?COARSE_QUEUE_STATS), + lists:member(K, ?FINE_STATS), + lists:member(K, ?COARSE_CONN_STATS)} of + {true, false, false} -> RangeL; + {false, true, false} -> RangeM; + {false, false, true} -> RangeD + end. + +%% We do this when retrieving the queue record rather than when +%% storing it since the memory use will drop *after* we find out about +%% hibernation, so to do it when we receive a queue stats event would +%% be fiddly and racy. This should be quite cheap though. +adjust_hibernated_memory_use(Qs) -> + Pids = [pget(pid, Q) || + Q <- Qs, pget(idle_since, Q, not_idle) =/= not_idle], + %% We use delegate here not for ordering reasons but because we + %% want to get the right amount of parallelism and minimise + %% cross-cluster communication. + {Mem, _BadNodes} = delegate:invoke(Pids, {erlang, process_info, [memory]}), + MemDict = dict:from_list([{P, M} || {P, M = {memory, _}} <- Mem]), + [case dict:find(pget(pid, Q), MemDict) of + error -> Q; + {ok, Memory} -> [Memory|proplists:delete(memory, Q)] + end || Q <- Qs]. + +created_event(Name, Type, Tables) -> + Table = orddict:fetch(Type, Tables), + case ets:match(Table, {{'$1', create}, '_', Name}) of + [] -> not_found; + [[Id]] -> lookup_element(Table, {Id, create}) + end. + +created_events(Type, Tables) -> + [Facts || {{_, create}, Facts, _Name} + <- ets:tab2list(orddict:fetch(Type, Tables))]. + +consumer_details_fun(KeyFun, TableName, State = #state{tables = Tables}) -> + Table = orddict:fetch(TableName, Tables), + fun ([]) -> []; + (Props) -> Pattern = {KeyFun(Props), '_', '_'}, + [{consumer_details, + [augment_msg_stats(augment_consumer(Obj), State) + || Obj <- lists:append( + ets:match(Table, {Pattern, '$1'}))]}] + end. + +augment_consumer(Obj) -> + [{queue, rabbit_mgmt_format:resource(pget(queue, Obj))} | + proplists:delete(queue, Obj)]. + +%%---------------------------------------------------------------------------- +%% Internal, query-time summing for overview +%%---------------------------------------------------------------------------- + +overview_sum(Type, VHostStats) -> + Stats = [pget(Type, VHost, rabbit_mgmt_stats:blank()) + || VHost <- VHostStats], + {Type, rabbit_mgmt_stats:sum(Stats)}. + +%%---------------------------------------------------------------------------- +%% Internal, query-time augmentation +%%---------------------------------------------------------------------------- + +augment_msg_stats(Props, State) -> + rabbit_mgmt_format:strip_pids( + (augment_msg_stats_fun(State))(Props) ++ Props). + +augment_msg_stats_fun(State) -> + Funs = [{connection, fun augment_connection_pid/2}, + {channel, fun augment_channel_pid/2}, + {owner_pid, fun augment_connection_pid/2}], + fun (Props) -> augment(Props, Funs, State) end. + +augment(Items, Funs, State) -> + Augmented = [augment(K, Items, Fun, State) || {K, Fun} <- Funs], + [{K, V} || {K, V} <- Augmented, V =/= unknown]. + +augment(K, Items, Fun, State) -> + Key = details_key(K), + case pget(K, Items) of + none -> {Key, unknown}; + unknown -> {Key, unknown}; + Id -> {Key, Fun(Id, State)} + end. + +augment_channel_pid(Pid, #state{tables = Tables}) -> + Ch = lookup_element(orddict:fetch(channel_stats, Tables), + {Pid, create}), + Conn = lookup_element(orddict:fetch(connection_stats, Tables), + {pget(connection, Ch), create}), + [{name, pget(name, Ch)}, + {number, pget(number, Ch)}, + {connection_name, pget(name, Conn)}, + {peer_port, pget(peer_port, Conn)}, + {peer_host, pget(peer_host, Conn)}]. + +augment_connection_pid(Pid, #state{tables = Tables}) -> + Conn = lookup_element(orddict:fetch(connection_stats, Tables), + {Pid, create}), + [{name, pget(name, Conn)}, + {peer_port, pget(peer_port, Conn)}, + {peer_host, pget(peer_host, Conn)}]. + +%%---------------------------------------------------------------------------- +%% Internal, event-GCing +%%---------------------------------------------------------------------------- + +gc_batch(State = #state{aggregated_stats = ETS}) -> + {ok, Policies} = application:get_env( + rabbitmq_management, sample_retention_policies), + Rows = erlang:max(?GC_MIN_ROWS, + round(?GC_MIN_RATIO * ets:info(ETS, size))), + gc_batch(Rows, Policies, State). + +gc_batch(0, _Policies, State) -> + State; +gc_batch(Rows, Policies, State = #state{aggregated_stats = ETS, + gc_next_key = Key0}) -> + Key = case Key0 of + undefined -> ets:first(ETS); + _ -> ets:next(ETS, Key0) + end, + Key1 = case Key of + '$end_of_table' -> undefined; + _ -> Now = floor(erlang:now(), State), + Stats = ets:lookup_element(ETS, Key, 2), + gc(Key, Stats, Policies, Now, ETS), + Key + end, + gc_batch(Rows - 1, Policies, State#state{gc_next_key = Key1}). + +gc({{Type, Id}, Key}, Stats, Policies, Now, ETS) -> + Policy = pget(retention_policy(Type), Policies), + case rabbit_mgmt_stats:gc({Policy, Now}, Stats) of + Stats -> ok; + Stats2 -> ets:insert(ETS, {{{Type, Id}, Key}, Stats2}) + end. + +retention_policy(vhost_stats) -> global; +retention_policy(queue_stats) -> basic; +retention_policy(exchange_stats) -> basic; +retention_policy(connection_stats) -> basic; +retention_policy(channel_stats) -> basic; +retention_policy(queue_exchange_stats) -> detailed; +retention_policy(channel_exchange_stats) -> detailed; +retention_policy(channel_queue_stats) -> detailed. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_dispatcher.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_dispatcher.erl new file mode 100644 index 0000000..bcfcfc6 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_dispatcher.erl @@ -0,0 +1,85 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_dispatcher). + +-export([modules/0, build_dispatcher/0]). + +-behaviour(rabbit_mgmt_extension). +-export([dispatcher/0, web_ui/0]). + +build_dispatcher() -> + [{["api" | Path], Mod, Args} || + {Path, Mod, Args} <- + lists:append([Module:dispatcher() || Module <- modules()])]. + +modules() -> + [Module || {Module, Behaviours} <- + rabbit_misc:all_module_attributes(behaviour), + lists:member(rabbit_mgmt_extension, Behaviours)]. + +%%---------------------------------------------------------------------------- + +web_ui() -> [{javascript, <<"dispatcher.js">>}]. + +dispatcher() -> + [{["overview"], rabbit_mgmt_wm_overview, []}, + {["cluster-name"], rabbit_mgmt_wm_cluster_name, []}, + {["nodes"], rabbit_mgmt_wm_nodes, []}, + {["nodes", node], rabbit_mgmt_wm_node, []}, + {["extensions"], rabbit_mgmt_wm_extensions, []}, + {["all-configuration"], rabbit_mgmt_wm_definitions, []}, %% This was the old name, let's not break things gratuitously. + {["definitions"], rabbit_mgmt_wm_definitions, []}, + {["parameters"], rabbit_mgmt_wm_parameters, []}, + {["parameters", component], rabbit_mgmt_wm_parameters, []}, + {["parameters", component, vhost], rabbit_mgmt_wm_parameters, []}, + {["parameters", component, vhost, name], rabbit_mgmt_wm_parameter, []}, + {["policies"], rabbit_mgmt_wm_policies, []}, + {["policies", vhost], rabbit_mgmt_wm_policies, []}, + {["policies", vhost, name], rabbit_mgmt_wm_policy, []}, + {["connections"], rabbit_mgmt_wm_connections, []}, + {["connections", connection], rabbit_mgmt_wm_connection, []}, + {["connections", connection, "channels"], rabbit_mgmt_wm_connection_channels, []}, + {["channels"], rabbit_mgmt_wm_channels, []}, + {["channels", channel], rabbit_mgmt_wm_channel, []}, + {["exchanges"], rabbit_mgmt_wm_exchanges, []}, + {["exchanges", vhost], rabbit_mgmt_wm_exchanges, []}, + {["exchanges", vhost, exchange], rabbit_mgmt_wm_exchange, []}, + {["exchanges", vhost, exchange, "publish"], rabbit_mgmt_wm_exchange_publish, []}, + {["exchanges", vhost, exchange, "bindings", "source"], rabbit_mgmt_wm_bindings, [exchange_source]}, + {["exchanges", vhost, exchange, "bindings", "destination"], rabbit_mgmt_wm_bindings, [exchange_destination]}, + {["queues"], rabbit_mgmt_wm_queues, []}, + {["queues", vhost], rabbit_mgmt_wm_queues, []}, + {["queues", vhost, queue], rabbit_mgmt_wm_queue, []}, + {["queues", vhost, destination, "bindings"], rabbit_mgmt_wm_bindings, [queue]}, + {["queues", vhost, queue, "contents"], rabbit_mgmt_wm_queue_purge, []}, + {["queues", vhost, queue, "get"], rabbit_mgmt_wm_queue_get, []}, + {["queues", vhost, queue, "actions"], rabbit_mgmt_wm_queue_actions, []}, + {["bindings"], rabbit_mgmt_wm_bindings, [all]}, + {["bindings", vhost], rabbit_mgmt_wm_bindings, [all]}, + {["bindings", vhost, "e", source, dtype, destination], rabbit_mgmt_wm_bindings, [source_destination]}, + {["bindings", vhost, "e", source, dtype, destination, props], rabbit_mgmt_wm_binding, []}, + {["vhosts"], rabbit_mgmt_wm_vhosts, []}, + {["vhosts", vhost], rabbit_mgmt_wm_vhost, []}, + {["vhosts", vhost, "permissions"], rabbit_mgmt_wm_permissions_vhost, []}, + {["users"], rabbit_mgmt_wm_users, []}, + {["users", user], rabbit_mgmt_wm_user, []}, + {["users", user, "permissions"], rabbit_mgmt_wm_permissions_user, []}, + {["whoami"], rabbit_mgmt_wm_whoami, []}, + {["permissions"], rabbit_mgmt_wm_permissions, []}, + {["permissions", vhost, user], rabbit_mgmt_wm_permission, []}, + {["aliveness-test", vhost], rabbit_mgmt_wm_aliveness_test, []} + ]. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_extension.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_extension.erl new file mode 100644 index 0000000..5ecc38d --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_extension.erl @@ -0,0 +1,32 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2011-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_extension). + +-export([behaviour_info/1]). + +behaviour_info(callbacks) -> + [ + %% Return a webmachine dispatcher table to integrate + {dispatcher, 0}, + + %% Return a proplist of information for the web UI to integrate + %% this extension. Currently the proplist should have one key, + %% 'javascript', the name of a javascript file to load and run. + {web_ui, 0} + ]; +behaviour_info(_Other) -> + undefined. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_format.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_format.erl new file mode 100644 index 0000000..487ff86 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_format.erl @@ -0,0 +1,315 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_format). + +-export([format/2, print/2, remove/1, ip/1, ipb/1, amqp_table/1, tuple/1]). +-export([parameter/1, timestamp/1, timestamp_ms/1, strip_pids/1]). +-export([node_from_pid/1, protocol/1, resource/1, queue/1, queue_state/1]). +-export([exchange/1, user/1, internal_user/1, binding/1, url/2]). +-export([pack_binding_props/2, tokenise/1]). +-export([to_amqp_table/1, listener/1, properties/1, basic_properties/1]). +-export([record/2, to_basic_properties/1]). +-export([addr/1, port/1]). + +-import(rabbit_misc, [pget/2, pset/3]). + +-include_lib("rabbit_common/include/rabbit.hrl"). +-include_lib("rabbit_common/include/rabbit_framing.hrl"). + +-define(PIDS_TO_STRIP, [connection, owner_pid, channel, + exclusive_consumer_pid]). + +%%-------------------------------------------------------------------- + +format(Stats, Fs) -> + lists:concat([format_item(Stat, Fs) || {_Name, Value} = Stat <- Stats, + Value =/= unknown]). + +format_item(Stat, []) -> + [Stat]; +format_item({Name, Value}, [{Fun, Names} | Fs]) -> + case lists:member(Name, Names) of + true -> case Fun(Value) of + List when is_list(List) -> List; + Formatted -> [{Name, Formatted}] + end; + false -> format_item({Name, Value}, Fs) + end. + +print(Fmt, Val) when is_list(Val) -> + list_to_binary(lists:flatten(io_lib:format(Fmt, Val))); +print(Fmt, Val) -> + print(Fmt, [Val]). + +%% TODO - can we remove all these "unknown" cases? Coverage never hits them. + +remove(_) -> []. + +node_from_pid(Pid) when is_pid(Pid) -> [{node, node(Pid)}]; +node_from_pid('') -> []; +node_from_pid(unknown) -> []; +node_from_pid(none) -> []. + +nodes_from_pids(Name) -> + fun('') -> []; + (Pids) -> [{Name, [node(Pid) || Pid <- Pids]}] + end. + +ip(unknown) -> unknown; +ip(IP) -> list_to_binary(rabbit_misc:ntoa(IP)). + +ipb(unknown) -> unknown; +ipb(IP) -> list_to_binary(rabbit_misc:ntoab(IP)). + +addr(S) when is_list(S); is_atom(S); is_binary(S) -> print("~s", S); +addr(Addr) when is_tuple(Addr) -> ip(Addr). + +port(Port) when is_number(Port) -> Port; +port(Port) -> print("~w", Port). + +properties(unknown) -> unknown; +properties(Table) -> {struct, [{Name, tuple(Value)} || + {Name, Value} <- Table]}. + +amqp_table(unknown) -> unknown; +amqp_table(undefined) -> amqp_table([]); +amqp_table(Table) -> {struct, [{Name, amqp_value(Type, Value)} || + {Name, Type, Value} <- Table]}. + +amqp_value(array, Vs) -> [amqp_value(T, V) || {T, V} <- Vs]; +amqp_value(table, V) -> amqp_table(V); +amqp_value(_Type, V) when is_binary(V) -> utf8_safe(V); +amqp_value(_Type, V) -> V. + +utf8_safe(V) -> + try + xmerl_ucs:from_utf8(V), + V + catch exit:{ucs, _} -> + Enc = base64:encode(V), + <<"Invalid UTF-8, base64 is: ", Enc/binary>> + end. + +parameter(P) -> pset(value, rabbit_misc:term_to_json(pget(value, P)), P). + +tuple(unknown) -> unknown; +tuple(Tuple) when is_tuple(Tuple) -> [tuple(E) || E <- tuple_to_list(Tuple)]; +tuple(Term) -> Term. + +protocol(unknown) -> + unknown; +protocol(Version = {_Major, _Minor, _Revision}) -> + protocol({'AMQP', Version}); +protocol({Family, Version}) -> + print("~s ~s", [Family, protocol_version(Version)]). + +protocol_version(Arbitrary) + when is_list(Arbitrary) -> Arbitrary; +protocol_version({Major, Minor}) -> io_lib:format("~B-~B", [Major, Minor]); +protocol_version({Major, Minor, 0}) -> protocol_version({Major, Minor}); +protocol_version({Major, Minor, Revision}) -> io_lib:format("~B-~B-~B", + [Major, Minor, Revision]). + +timestamp_ms(unknown) -> + unknown; +timestamp_ms(Timestamp) -> + timer:now_diff(Timestamp, {0,0,0}) div 1000. + +timestamp(unknown) -> + unknown; +timestamp(Timestamp) -> + {{Y, M, D}, {H, Min, S}} = calendar:now_to_local_time(Timestamp), + print("~w-~2.2.0w-~2.2.0w ~w:~2.2.0w:~2.2.0w", [Y, M, D, H, Min, S]). + +resource(unknown) -> unknown; +resource(Res) -> resource(name, Res). + +resource(_, unknown) -> + unknown; +resource(NameAs, #resource{name = Name, virtual_host = VHost}) -> + [{NameAs, Name}, {vhost, VHost}]. + +policy('') -> []; +policy(Policy) -> [{policy, Policy}]. + +internal_user(User) -> + [{name, User#internal_user.username}, + {password_hash, base64:encode(User#internal_user.password_hash)}, + {tags, tags(User#internal_user.tags)}]. + +user(User) -> + [{name, User#user.username}, + {tags, tags(User#user.tags)}, + {auth_backend, User#user.auth_backend}]. + +tags(Tags) -> + list_to_binary(string:join([atom_to_list(T) || T <- Tags], ",")). + +listener(#listener{node = Node, protocol = Protocol, + ip_address = IPAddress, port = Port}) -> + [{node, Node}, + {protocol, Protocol}, + {ip_address, ip(IPAddress)}, + {port, Port}]. + +pack_binding_props(<<"">>, []) -> + <<"~">>; +pack_binding_props(Key, []) -> + list_to_binary(quote_binding(Key)); +pack_binding_props(Key, Args) -> + ArgsEnc = rabbit_mgmt_wm_binding:args_hash(Args), + list_to_binary(quote_binding(Key) ++ "~" ++ quote_binding(ArgsEnc)). + +quote_binding(Name) -> + re:replace(mochiweb_util:quote_plus(Name), "~", "%7E", [global]). + +%% Unfortunately string:tokens("foo~~bar", "~"). -> ["foo","bar"], we lose +%% the fact that there's a double ~. +tokenise("") -> + []; +tokenise(Str) -> + Count = string:cspan(Str, "~"), + case length(Str) of + Count -> [Str]; + _ -> [string:sub_string(Str, 1, Count) | + tokenise(string:sub_string(Str, Count + 2))] + end. + +to_amqp_table({struct, T}) -> + to_amqp_table(T); +to_amqp_table(T) -> + [to_amqp_table_row(K, V) || {K, V} <- T]. + +to_amqp_table_row(K, V) -> + {T, V2} = type_val(V), + {K, T, V2}. + +to_amqp_array(L) -> + [type_val(I) || I <- L]. + +type_val({struct, M}) -> {table, to_amqp_table(M)}; +type_val(L) when is_list(L) -> {array, to_amqp_array(L)}; +type_val(X) when is_binary(X) -> {longstr, X}; +type_val(X) when is_integer(X) -> {long, X}; +type_val(X) when is_number(X) -> {double, X}; +type_val(true) -> {bool, true}; +type_val(false) -> {bool, false}; +type_val(null) -> throw({error, null_not_allowed}); +type_val(X) -> throw({error, {unhandled_type, X}}). + +url(Fmt, Vals) -> + print(Fmt, [mochiweb_util:quote_plus(V) || V <- Vals]). + +exchange(X) -> + format(X, [{fun resource/1, [name]}, + {fun amqp_table/1, [arguments]}, + {fun policy/1, [policy]}]). + +%% We get queues using rabbit_amqqueue:list/1 rather than :info_all/1 since +%% the latter wakes up each queue. Therefore we have a record rather than a +%% proplist to deal with. +queue(#amqqueue{name = Name, + durable = Durable, + auto_delete = AutoDelete, + exclusive_owner = ExclusiveOwner, + arguments = Arguments, + pid = Pid}) -> + format( + [{name, Name}, + {durable, Durable}, + {auto_delete, AutoDelete}, + {owner_pid, ExclusiveOwner}, + {arguments, Arguments}, + {pid, Pid}], + [{fun resource/1, [name]}, + {fun amqp_table/1, [arguments]}, + {fun policy/1, [policy]}]). + +queue_state({syncing, Msgs}) -> [{state, syncing}, + {sync_messages, Msgs}]; +queue_state(Status) -> [{state, Status}]. + +%% We get bindings using rabbit_binding:list_*/1 rather than :info_all/1 since +%% there are no per-exchange / queue / etc variants for the latter. Therefore +%% we have a record rather than a proplist to deal with. +binding(#binding{source = S, + key = Key, + destination = D, + args = Args}) -> + format( + [{source, S}, + {destination, D#resource.name}, + {destination_type, D#resource.kind}, + {routing_key, Key}, + {arguments, Args}, + {properties_key, pack_binding_props(Key, Args)}], + [{fun (Res) -> resource(source, Res) end, [source]}, + {fun amqp_table/1, [arguments]}]). + +basic_properties(Props = #'P_basic'{}) -> + Res = record(Props, record_info(fields, 'P_basic')), + format(Res, [{fun amqp_table/1, [headers]}]). + +record(Record, Fields) -> + {Res, _Ix} = lists:foldl(fun (K, {L, Ix}) -> + {case element(Ix, Record) of + undefined -> L; + V -> [{K, V}|L] + end, Ix + 1} + end, {[], 2}, Fields), + Res. + +to_basic_properties({struct, P}) -> + to_basic_properties(P); + +to_basic_properties(Props) -> + E = fun (A, B) -> throw({error, {A, B}}) end, + Fmt = fun (headers, H) -> to_amqp_table(H); + (delivery_mode, V) when is_integer(V) -> V; + (delivery_mode, _V) -> E(not_int,delivery_mode); + (priority, V) when is_integer(V) -> V; + (priority, _V) -> E(not_int, priority); + (timestamp, V) when is_integer(V) -> V; + (timestamp, _V) -> E(not_int, timestamp); + (_, V) when is_binary(V) -> V; + (K, _V) -> E(not_string, K) + end, + {Res, _Ix} = lists:foldl( + fun (K, {P, Ix}) -> + {case proplists:get_value(a2b(K), Props) of + undefined -> P; + V -> setelement(Ix, P, Fmt(K, V)) + end, Ix + 1} + end, {#'P_basic'{}, 2}, + record_info(fields, 'P_basic')), + Res. + +a2b(A) -> + list_to_binary(atom_to_list(A)). + +%% Items can be connections, channels, consumers or queues, hence remove takes +%% various items. +strip_pids(Item = [T | _]) when is_tuple(T) -> + format(Item, + [{fun node_from_pid/1, [pid]}, + {fun remove/1, ?PIDS_TO_STRIP}, + {nodes_from_pids(slave_nodes), [slave_pids]}, + {nodes_from_pids(synchronised_slave_nodes), + [synchronised_slave_pids]} + ]); + +strip_pids(Items) -> [strip_pids(I) || I <- Items]. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_load_definitions.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_load_definitions.erl new file mode 100644 index 0000000..95fadfe --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_load_definitions.erl @@ -0,0 +1,48 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_load_definitions). + +-export([maybe_load_definitions/0]). + +%% We want to A) make sure we apply defintions before being open for +%% business (hence why we don't do this in the mgmt app startup) and +%% B) in fact do it before empty_db_check (so the defaults will not +%% get created if we don't need 'em). + +-rabbit_boot_step({load_definitions, + [{description, "configured definitions"}, + {mfa, {rabbit_mgmt_load_definitions, + maybe_load_definitions, + []}}, + {requires, recovery}, + {enables, empty_db_check}]}). + +maybe_load_definitions() -> + {ok, File} = application:get_env(rabbitmq_management, load_definitions), + case File of + none -> ok; + _ -> case file:read_file(File) of + {ok, Body} -> rabbit_log:info( + "Applying definitions from: ~s~n", [File]), + load_definitions(Body); + {error, E} -> {error, {could_not_read_defs, {File, E}}} + end + end. + +load_definitions(Body) -> + rabbit_mgmt_wm_definitions:apply_defs( + Body, fun () -> ok end, fun (E) -> {error, E} end). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_stats.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_stats.erl new file mode 100644 index 0000000..74a4cc9 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_stats.erl @@ -0,0 +1,201 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2012 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_stats). + +-include("rabbit_mgmt.hrl"). + +-export([blank/0, is_blank/1, record/3, format/3, sum/1, gc/2]). + +-import(rabbit_misc, [pget/2]). + +%%---------------------------------------------------------------------------- + +blank() -> #stats{diffs = gb_trees:empty(), base = 0}. + +is_blank(#stats{diffs = Diffs, base = 0}) -> gb_trees:is_empty(Diffs); +is_blank(#stats{}) -> false. + +%%---------------------------------------------------------------------------- +%% Event-time +%%---------------------------------------------------------------------------- + +record(TS, Diff, Stats = #stats{diffs = Diffs}) -> + Diffs2 = case gb_trees:lookup(TS, Diffs) of + {value, Total} -> gb_trees:update(TS, Diff + Total, Diffs); + none -> gb_trees:insert(TS, Diff, Diffs) + end, + Stats#stats{diffs = Diffs2}. + +%%---------------------------------------------------------------------------- +%% Query-time +%%---------------------------------------------------------------------------- + +format(no_range, #stats{diffs = Diffs, base = Base}, Interval) -> + Now = rabbit_mgmt_format:timestamp_ms(erlang:now()), + RangePoint = ((Now div Interval) * Interval) - Interval, + Count = sum_entire_tree(gb_trees:iterator(Diffs), Base), + {[{rate, format_rate( + Diffs, RangePoint, Interval, Interval)}], Count}; + +format(Range, #stats{diffs = Diffs, base = Base}, Interval) -> + RangePoint = Range#range.last - Interval, + {Samples, Count} = extract_samples( + Range, Base, gb_trees:iterator(Diffs), []), + Part1 = [{rate, format_rate( + Diffs, RangePoint, Range#range.incr, Interval)}, + {samples, Samples}], + Length = length(Samples), + Part2 = case Length > 1 of + true -> [{sample, S2}, {timestamp, T2}] = hd(Samples), + [{sample, S1}, {timestamp, T1}] = lists:last(Samples), + Total = lists:sum([pget(sample, I) || I <- Samples]), + [{avg_rate, (S2 - S1) * 1000 / (T2 - T1)}, + {avg, Total / Length}]; + false -> [] + end, + {Part1 ++ Part2, Count}. + +format_rate(Diffs, RangePoint, Incr, Interval) -> + case nth_largest(Diffs, 2) of + false -> 0.0; + {TS, S} -> case TS - RangePoint of %% [0] + D when D =< Incr andalso D >= 0 -> S * 1000 / Interval; + _ -> 0.0 + end + end. + +%% [0] Only display the rate if it's live - i.e. ((the end of the +%% range) - interval) corresponds to the second to last data point we +%% have. If the end of the range is earlier we have gone silent, if +%% it's later we have been asked for a range back in time (in which +%% case showing the correct instantaneous rate would be quite a faff, +%% and probably unwanted). Why the second to last? Because data is +%% still arriving for the last... +nth_largest(Tree, N) -> + case gb_trees:is_empty(Tree) of + true -> false; + false when N == 1 -> gb_trees:largest(Tree); + false -> {_, _, Tree2} = gb_trees:take_largest(Tree), + nth_largest(Tree2, N - 1) + end. + +sum_entire_tree(Iter, Acc) -> + case gb_trees:next(Iter) of + none -> Acc; + {_TS, S, Iter2} -> sum_entire_tree(Iter2, Acc + S) + end. + +%% What we want to do here is: given the #range{}, provide a set of +%% samples such that we definitely provide a set of samples which +%% covers the exact range requested, despite the fact that we might +%% not have it. We need to spin up over the entire range of the +%% samples we *do* have since they are diff-based (and we convert to +%% absolute values here). +extract_samples(Range = #range{first = Next}, Base, It, Samples) -> + case gb_trees:next(It) of + {TS, S, It2} -> extract_samples1(Range, Base, TS, S, It2, Samples); + none -> extract_samples1(Range, Base, Next, 0, It, Samples) + end. + +extract_samples1(Range = #range{first = Next, last = Last, incr = Incr}, + Base, TS, S, It, Samples) -> + if + %% We've gone over the range. Terminate. + Next > Last -> + {Samples, Base}; + %% We've hit bang on a sample. Record it and move to the next. + Next =:= TS -> + extract_samples(Range#range{first = Next + Incr}, Base + S, It, + append(Base + S, Next, Samples)); + %% We haven't yet hit the beginning of our range. + Next > TS -> + extract_samples(Range, Base + S, It, Samples); + %% We have a valid sample, but we haven't used it up + %% yet. Append it and loop around. + Next < TS -> + extract_samples1(Range#range{first = Next + Incr}, Base, TS, S, It, + append(Base, Next, Samples)) + end. + +append(S, TS, Samples) -> [[{sample, S}, {timestamp, TS}] | Samples]. + +sum([]) -> blank(); + +sum([Stats | StatsN]) -> + lists:foldl( + fun (#stats{diffs = D1, base = B1}, #stats{diffs = D2, base = B2}) -> + #stats{diffs = add_trees(D1, gb_trees:iterator(D2)), + base = B1 + B2} + end, Stats, StatsN). + +add_trees(Tree, It) -> + case gb_trees:next(It) of + none -> Tree; + {K, V, It2} -> add_trees( + case gb_trees:lookup(K, Tree) of + {value, V2} -> gb_trees:update(K, V + V2, Tree); + none -> gb_trees:insert(K, V, Tree) + end, It2) + end. + +%%---------------------------------------------------------------------------- +%% Event-GCing +%%---------------------------------------------------------------------------- + +gc(Cutoff, #stats{diffs = Diffs, base = Base}) -> + List = lists:reverse(gb_trees:to_list(Diffs)), + gc(Cutoff, List, [], Base). + +%% Go through the list, amalgamating all too-old samples with the next +%% newest keepable one [0] (we move samples forward in time since the +%% semantics of a sample is "we had this many x by this time"). If the +%% sample is too old, but would not be too old if moved to a rounder +%% timestamp which does not exist then invent one and move it there +%% [1]. But if it's just outright too old, move it to the base [2]. +gc(_Cutoff, [], Keep, Base) -> + #stats{diffs = gb_trees:from_orddict(Keep), base = Base}; +gc(Cutoff, [H = {TS, S} | T], Keep, Base) -> + {NewKeep, NewBase} = + case keep(Cutoff, TS) of + keep -> {[H | Keep], Base}; + drop -> {Keep, S + Base}; %% [2] + {move, D} when Keep =:= [] -> {[{TS + D, S}], Base}; %% [1] + {move, _} -> [{KTS, KS} | KT] = Keep, + {[{KTS, KS + S} | KT], Base} %% [0] + end, + gc(Cutoff, T, NewKeep, NewBase). + +keep({Policy, Now}, TS) -> + lists:foldl(fun ({AgeSec, DivisorSec}, Action) -> + prefer_action( + Action, + case (Now - TS) =< (AgeSec * 1000) of + true -> DivisorMillis = DivisorSec * 1000, + case TS rem DivisorMillis of + 0 -> keep; + Rem -> {move, DivisorMillis - Rem} + end; + false -> drop + end) + end, drop, Policy). + +prefer_action(keep, _) -> keep; +prefer_action(_, keep) -> keep; +prefer_action({move, A}, {move, B}) -> {move, lists:min([A, B])}; +prefer_action({move, A}, drop) -> {move, A}; +prefer_action(drop, {move, A}) -> {move, A}; +prefer_action(drop, drop) -> drop. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_sup.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_sup.erl new file mode 100644 index 0000000..008dd58 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_sup.erl @@ -0,0 +1,34 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Console. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2011-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_sup). + +-behaviour(mirrored_supervisor). + +-export([init/1]). +-export([start_link/0]). + +-include_lib("rabbit_common/include/rabbit.hrl"). + +init([]) -> + DB = {rabbit_mgmt_db, {rabbit_mgmt_db, start_link, []}, + permanent, ?MAX_WAIT, worker, [rabbit_mgmt_db]}, + {ok, {{one_for_one, 10, 10}, [DB]}}. + +start_link() -> + mirrored_supervisor:start_link( + {local, ?MODULE}, ?MODULE, fun rabbit_misc:execute_mnesia_transaction/1, + ?MODULE, []). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_sup_sup.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_sup_sup.erl new file mode 100644 index 0000000..d83fb3b --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_sup_sup.erl @@ -0,0 +1,64 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Console. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2011-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_sup_sup). + +%% We want there to be one management database in the cluster, with a +%% globally registered name. So we use mirrored_supervisor for +%% failover (in rabbit_mgmt_sup) and register a global name for the +%% database. +%% +%% Unfortunately it's more complicated than using these things +%% naively. The first problem is that on failover the mirrored +%% supervisor might move the DB to a new node before the global name +%% database notices and removes the old record. In that case starting +%% the new database will fail. +%% +%% The second problem is that after a network partition things get +%% worse. Since mirrored_supervisor uses Mnesia for global shared +%% state, we have effectively two (or more) mirrored_supervisors. But +%% the global name database does not do this, so at least one of them +%% cannot start the management database; so the mirrored supervisor +%% has to die. But what if the admin restarts the partition which +%% contains the management DB? In that case we need to start a new +%% management DB in the winning partition. +%% +%% Rather than try to get mirrored_supervisor to handle this +%% post-partition state we go for a simpler approach: allow the whole +%% mirrored_supervisor to die in the two edge cases above, and +%% whenever we want to call into the mgmt DB we will start it up if it +%% appears not to be there. See rabbit_mgmt_db:safe_call/3 for the +%% code which restarts the DB if necessary. + +-behaviour(supervisor2). + +-export([start_link/0, start_child/0]). +-export([init/1]). + +-include_lib("rabbit_common/include/rabbit.hrl"). + +start_link() -> supervisor2:start_link({local, ?MODULE}, ?MODULE, []). + +start_child() -> supervisor2:start_child( ?MODULE, sup()). + +%%---------------------------------------------------------------------------- + +init([]) -> + {ok, {{one_for_one, 0, 1}, [sup()]}}. + +sup() -> + {rabbit_mgmt_sup, {rabbit_mgmt_sup, start_link, []}, + temporary, ?MAX_WAIT, supervisor, [rabbit_mgmt_sup]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_util.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_util.erl new file mode 100644 index 0000000..ad45092 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_util.erl @@ -0,0 +1,589 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_util). + +%% TODO sort all this out; maybe there's scope for rabbit_mgmt_request? + +-export([is_authorized/2, is_authorized_admin/2, is_authorized_admin/4, + vhost/1]). +-export([is_authorized_vhost/2, is_authorized_user/3, + is_authorized_monitor/2, is_authorized_policies/2]). +-export([bad_request/3, bad_request_exception/4, id/2, parse_bool/1, + parse_int/1]). +-export([with_decode/4, not_found/3, amqp_request/4]). +-export([with_channel/4, with_channel/5]). +-export([props_to_method/2, props_to_method/4]). +-export([all_or_one_vhost/2, http_to_amqp/5, reply/3, filter_vhost/3]). +-export([filter_conn_ch_list/3, filter_user/2, list_login_vhosts/1]). +-export([with_decode/5, decode/1, decode/2, redirect/2, args/1]). +-export([reply_list/3, reply_list/4, sort_list/2, destination_type/1]). +-export([post_respond/1, columns/1, is_monitor/1]). +-export([list_visible_vhosts/1, b64decode_or_throw/1, no_range/0, range/1, + range_ceil/1, floor/2, ceil/2]). + +-import(rabbit_misc, [pget/2, pget/3]). + +-include("rabbit_mgmt.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +-define(FRAMING, rabbit_framing_amqp_0_9_1). + +%%-------------------------------------------------------------------- + +is_authorized(ReqData, Context) -> + is_authorized(ReqData, Context, '', fun(_) -> true end). + +is_authorized_admin(ReqData, Context) -> + is_authorized(ReqData, Context, + <<"Not administrator user">>, + fun(#user{tags = Tags}) -> is_admin(Tags) end). + +is_authorized_admin(ReqData, Context, Username, Password) -> + is_authorized(ReqData, Context, Username, Password, + <<"Not administrator user">>, + fun(#user{tags = Tags}) -> is_admin(Tags) end). + +is_authorized_monitor(ReqData, Context) -> + is_authorized(ReqData, Context, + <<"Not monitor user">>, + fun(#user{tags = Tags}) -> is_monitor(Tags) end). + +is_authorized_vhost(ReqData, Context) -> + is_authorized(ReqData, Context, + <<"User not authorised to access virtual host">>, + fun(User) -> + user_matches_vhost(ReqData, User) + end). + +user_matches_vhost(ReqData, User) -> + case vhost(ReqData) of + not_found -> true; + none -> true; + V -> lists:member(V, list_login_vhosts(User)) + end. + +%% Used for connections / channels. A normal user can only see / delete +%% their own stuff. Monitors can see other users' and delete their +%% own. Admins can do it all. +is_authorized_user(ReqData, Context, Item) -> + is_authorized(ReqData, Context, + <<"User not authorised to access object">>, + fun(#user{username = Username, tags = Tags}) -> + case wrq:method(ReqData) of + 'DELETE' -> is_admin(Tags); + _ -> is_monitor(Tags) + end orelse Username == pget(user, Item) + end). + +%% For policies / parameters. Like is_authorized_vhost but you have to +%% be a policymaker. +is_authorized_policies(ReqData, Context) -> + is_authorized(ReqData, Context, + <<"User not authorised to access object">>, + fun(User = #user{tags = Tags}) -> + is_policymaker(Tags) andalso + user_matches_vhost(ReqData, User) + end). + +is_authorized(ReqData, Context, ErrorMsg, Fun) -> + case rabbit_web_dispatch_util:parse_auth_header( + wrq:get_req_header("authorization", ReqData)) of + [Username, Password] -> + is_authorized(ReqData, Context, Username, Password, ErrorMsg, Fun); + _ -> + {?AUTH_REALM, ReqData, Context} + end. + +is_authorized(ReqData, Context, Username, Password, ErrorMsg, Fun) -> + ErrFun = fun (Msg) -> + rabbit_log:warning("HTTP access denied: user '~s' - ~s~n", + [Username, Msg]), + not_authorised(Msg, ReqData, Context) + end, + case rabbit_access_control:check_user_pass_login(Username, Password) of + {ok, User = #user{tags = Tags}} -> + IPStr = wrq:peer(ReqData), + %% inet_parse:address/1 is an undocumented function but + %% exists in old versions of Erlang. inet:parse_address/1 + %% is a documented wrapper round it but introduced in R16B. + {ok, IP} = inet_parse:address(IPStr), + case rabbit_access_control:check_user_loopback(Username, IP) of + ok -> + case is_mgmt_user(Tags) of + true -> + case Fun(User) of + true -> {true, ReqData, + Context#context{user = User, + password = Password}}; + false -> ErrFun(ErrorMsg) + end; + false -> + ErrFun(<<"Not management user">>) + end; + not_allowed -> + ErrFun(<<"User can only log in via localhost">>) + end; + {refused, Msg, Args} -> + rabbit_log:warning("HTTP access denied: ~s~n", + [rabbit_misc:format(Msg, Args)]), + not_authorised(<<"Login failed">>, ReqData, Context) + end. + +vhost(ReqData) -> + case id(vhost, ReqData) of + none -> none; + VHost -> case rabbit_vhost:exists(VHost) of + true -> VHost; + false -> not_found + end + end. + +destination_type(ReqData) -> + case id(dtype, ReqData) of + <<"e">> -> exchange; + <<"q">> -> queue + end. + +reply(Facts, ReqData, Context) -> + reply0(extract_columns(Facts, ReqData), ReqData, Context). + +reply0(Facts, ReqData, Context) -> + ReqData1 = wrq:set_resp_header("Cache-Control", "no-cache", ReqData), + try + {mochijson2:encode(Facts), ReqData1, Context} + catch exit:{json_encode, E} -> + Error = iolist_to_binary( + io_lib:format("JSON encode error: ~p", [E])), + Reason = iolist_to_binary( + io_lib:format("While encoding:~n~p", [Facts])), + internal_server_error(Error, Reason, ReqData1, Context) + end. + +reply_list(Facts, ReqData, Context) -> + reply_list(Facts, ["vhost", "name"], ReqData, Context). + +reply_list(Facts, DefaultSorts, ReqData, Context) -> + reply(sort_list( + extract_columns_list(Facts, ReqData), + DefaultSorts, + wrq:get_qs_value("sort", ReqData), + wrq:get_qs_value("sort_reverse", ReqData)), + ReqData, Context). + +sort_list(Facts, Sorts) -> sort_list(Facts, Sorts, undefined, false). + +sort_list(Facts, DefaultSorts, Sort, Reverse) -> + SortList = case Sort of + undefined -> DefaultSorts; + Extra -> [Extra | DefaultSorts] + end, + %% lists:sort/2 is much more expensive than lists:sort/1 + Sorted = [V || {_K, V} <- lists:sort( + [{sort_key(F, SortList), F} || F <- Facts])], + case Reverse of + "true" -> lists:reverse(Sorted); + _ -> Sorted + end. + +sort_key(_Item, []) -> + []; +sort_key(Item, [Sort | Sorts]) -> + [get_dotted_value(Sort, Item) | sort_key(Item, Sorts)]. + +get_dotted_value(Key, Item) -> + Keys = string:tokens(Key, "."), + get_dotted_value0(Keys, Item). + +get_dotted_value0([Key], Item) -> + %% Put "nothing" before everything else, in number terms it usually + %% means 0. + pget_bin(list_to_binary(Key), Item, 0); +get_dotted_value0([Key | Keys], Item) -> + get_dotted_value0(Keys, pget_bin(list_to_binary(Key), Item, [])). + +pget_bin(Key, List, Default) -> + case lists:partition(fun ({K, _V}) -> a2b(K) =:= Key end, List) of + {[{_K, V}], _} -> V; + {[], _} -> Default + end. + +extract_columns(Item, ReqData) -> + extract_column_items(Item, columns(ReqData)). + +extract_columns_list(Items, ReqData) -> + Cols = columns(ReqData), + [extract_column_items(Item, Cols) || Item <- Items]. + +columns(ReqData) -> + case wrq:get_qs_value("columns", ReqData) of + undefined -> all; + Str -> [[list_to_binary(T) || T <- string:tokens(C, ".")] + || C <- string:tokens(Str, ",")] + end. + +extract_column_items(Item, all) -> + Item; +extract_column_items({struct, L}, Cols) -> + extract_column_items(L, Cols); +extract_column_items(Item = [T | _], Cols) when is_tuple(T) -> + [{K, extract_column_items(V, descend_columns(a2b(K), Cols))} || + {K, V} <- Item, want_column(a2b(K), Cols)]; +extract_column_items(L, Cols) when is_list(L) -> + [extract_column_items(I, Cols) || I <- L]; +extract_column_items(O, _Cols) -> + O. + +want_column(_Col, all) -> true; +want_column(Col, Cols) -> lists:any(fun([C|_]) -> C == Col end, Cols). + +descend_columns(_K, []) -> []; +descend_columns( K, [[K] | _Rest]) -> all; +descend_columns( K, [[K | K2] | Rest]) -> [K2 | descend_columns(K, Rest)]; +descend_columns( K, [[_K2 | _ ] | Rest]) -> descend_columns(K, Rest). + +a2b(A) when is_atom(A) -> list_to_binary(atom_to_list(A)); +a2b(B) -> B. + +bad_request(Reason, ReqData, Context) -> + halt_response(400, bad_request, Reason, ReqData, Context). + +not_authorised(Reason, ReqData, Context) -> + halt_response(401, not_authorised, Reason, ReqData, Context). + +not_found(Reason, ReqData, Context) -> + halt_response(404, not_found, Reason, ReqData, Context). + +internal_server_error(Error, Reason, ReqData, Context) -> + rabbit_log:error("~s~n~s~n", [Error, Reason]), + halt_response(500, Error, Reason, ReqData, Context). + +halt_response(Code, Type, Reason, ReqData, Context) -> + Json = {struct, [{error, Type}, + {reason, rabbit_mgmt_format:tuple(Reason)}]}, + ReqData1 = wrq:append_to_response_body(mochijson2:encode(Json), ReqData), + {{halt, Code}, ReqData1, Context}. + +id(Key, ReqData) when Key =:= exchange; + Key =:= source; + Key =:= destination -> + case id0(Key, ReqData) of + <<"amq.default">> -> <<"">>; + Name -> Name + end; +id(Key, ReqData) -> + id0(Key, ReqData). + +id0(Key, ReqData) -> + case orddict:find(Key, wrq:path_info(ReqData)) of + {ok, Id} -> list_to_binary(mochiweb_util:unquote(Id)); + error -> none + end. + +with_decode(Keys, ReqData, Context, Fun) -> + with_decode(Keys, wrq:req_body(ReqData), ReqData, Context, Fun). + +with_decode(Keys, Body, ReqData, Context, Fun) -> + case decode(Keys, Body) of + {error, Reason} -> bad_request(Reason, ReqData, Context); + {ok, Values, JSON} -> try + Fun(Values, JSON) + catch {error, Error} -> + bad_request(Error, ReqData, Context) + end + end. + +decode(Keys, Body) -> + case decode(Body) of + {ok, J0} -> J = [{list_to_atom(binary_to_list(K)), V} || {K, V} <- J0], + Results = [get_or_missing(K, J) || K <- Keys], + case [E || E = {key_missing, _} <- Results] of + [] -> {ok, Results, J}; + Errors -> {error, Errors} + end; + Else -> Else + end. + +decode(<<"">>) -> + {ok, []}; + +decode(Body) -> + try + {struct, J} = mochijson2:decode(Body), + {ok, J} + catch error:_ -> {error, not_json} + end. + +get_or_missing(K, L) -> + case pget(K, L) of + undefined -> {key_missing, K}; + V -> V + end. + +http_to_amqp(MethodName, ReqData, Context, Transformers, Extra) -> + case vhost(ReqData) of + not_found -> + not_found(vhost_not_found, ReqData, Context); + VHost -> + case decode(wrq:req_body(ReqData)) of + {ok, Props} -> + try + Node = case pget(<<"node">>, Props) of + undefined -> node(); + N -> rabbit_nodes:make( + binary_to_list(N)) + end, + amqp_request(VHost, ReqData, Context, Node, + props_to_method( + MethodName, Props, Transformers, Extra)) + catch {error, Error} -> + bad_request(Error, ReqData, Context) + end; + {error, Reason} -> + bad_request(Reason, ReqData, Context) + end + end. + +props_to_method(MethodName, Props, Transformers, Extra) -> + Props1 = [{list_to_atom(binary_to_list(K)), V} || {K, V} <- Props], + props_to_method( + MethodName, rabbit_mgmt_format:format(Props1 ++ Extra, Transformers)). + +props_to_method(MethodName, Props) -> + Props1 = rabbit_mgmt_format:format( + Props, + [{fun (Args) -> [{arguments, args(Args)}] end, [arguments]}]), + FieldNames = ?FRAMING:method_fieldnames(MethodName), + {Res, _Idx} = lists:foldl( + fun (K, {R, Idx}) -> + NewR = case pget(K, Props1) of + undefined -> R; + V -> setelement(Idx, R, V) + end, + {NewR, Idx + 1} + end, {?FRAMING:method_record(MethodName), 2}, + FieldNames), + Res. + +parse_bool(<<"true">>) -> true; +parse_bool(<<"false">>) -> false; +parse_bool(true) -> true; +parse_bool(false) -> false; +parse_bool(undefined) -> undefined; +parse_bool(V) -> throw({error, {not_boolean, V}}). + +parse_int(I) when is_integer(I) -> I; +parse_int(F) when is_number(F) -> trunc(F); +parse_int(S) -> try + list_to_integer(binary_to_list(S)) + catch error:badarg -> + throw({error, {not_integer, S}}) + end. + +amqp_request(VHost, ReqData, Context, Method) -> + amqp_request(VHost, ReqData, Context, node(), Method). + +amqp_request(VHost, ReqData, Context, Node, Method) -> + with_channel(VHost, ReqData, Context, Node, + fun (Ch) -> + amqp_channel:call(Ch, Method), + {true, ReqData, Context} + end). + +with_channel(VHost, ReqData, Context, Fun) -> + with_channel(VHost, ReqData, Context, node(), Fun). + +with_channel(VHost, ReqData, + Context = #context{user = #user {username = Username}, + password = Password}, + Node, Fun) -> + Params = #amqp_params_direct{username = Username, + password = Password, + node = Node, + virtual_host = VHost}, + case amqp_connection:start(Params) of + {ok, Conn} -> + {ok, Ch} = amqp_connection:open_channel(Conn), + try + Fun(Ch) + catch + exit:{{shutdown, + {server_initiated_close, ?NOT_FOUND, Reason}}, _} -> + not_found(Reason, ReqData, Context); + exit:{{shutdown, + {server_initiated_close, ?ACCESS_REFUSED, Reason}}, _} -> + not_authorised(Reason, ReqData, Context); + exit:{{shutdown, {ServerClose, Code, Reason}}, _} + when ServerClose =:= server_initiated_close; + ServerClose =:= server_initiated_hard_close -> + bad_request_exception(Code, Reason, ReqData, Context); + exit:{{shutdown, {connection_closing, + {ServerClose, Code, Reason}}}, _} + when ServerClose =:= server_initiated_close; + ServerClose =:= server_initiated_hard_close -> + bad_request_exception(Code, Reason, ReqData, Context) + after + catch amqp_channel:close(Ch), + catch amqp_connection:close(Conn) + end; + {error, {auth_failure, Msg}} -> + not_authorised(Msg, ReqData, Context); + {error, {nodedown, N}} -> + bad_request( + list_to_binary( + io_lib:format("Node ~s could not be contacted", [N])), + ReqData, Context) + end. + +bad_request_exception(Code, Reason, ReqData, Context) -> + bad_request(list_to_binary(io_lib:format("~p ~s", [Code, Reason])), + ReqData, Context). + +all_or_one_vhost(ReqData, Fun) -> + case rabbit_mgmt_util:vhost(ReqData) of + none -> lists:append([Fun(V) || V <- rabbit_vhost:list()]); + not_found -> vhost_not_found; + VHost -> Fun(VHost) + end. + +filter_vhost(List, _ReqData, Context) -> + VHosts = list_login_vhosts(Context#context.user), + [I || I <- List, lists:member(pget(vhost, I), VHosts)]. + +filter_user(List, _ReqData, #context{user = User}) -> + filter_user(List, User). + +filter_user(List, #user{username = Username, tags = Tags}) -> + case is_monitor(Tags) of + true -> List; + false -> [I || I <- List, pget(user, I) == Username] + end. + +filter_conn_ch_list(List, ReqData, Context) -> + rabbit_mgmt_format:strip_pids( + filter_user( + case vhost(ReqData) of + none -> List; + VHost -> [I || I <- List, pget(vhost, I) =:= VHost] + end, ReqData, Context)). + +redirect(Location, ReqData) -> + wrq:do_redirect(true, + wrq:set_resp_header("Location", + binary_to_list(Location), ReqData)). + +args({struct, L}) -> args(L); +args(L) -> rabbit_mgmt_format:to_amqp_table(L). + +%% Make replying to a post look like anything else... +post_respond({true, ReqData, Context}) -> + {true, ReqData, Context}; +post_respond({{halt, Code}, ReqData, Context}) -> + {{halt, Code}, ReqData, Context}; +post_respond({JSON, ReqData, Context}) -> + {true, wrq:set_resp_header( + "content-type", "application/json", + wrq:append_to_response_body(JSON, ReqData)), Context}. + +is_admin(T) -> intersects(T, [administrator]). +is_policymaker(T) -> intersects(T, [administrator, policymaker]). +is_monitor(T) -> intersects(T, [administrator, monitoring]). +is_mgmt_user(T) -> intersects(T, [administrator, monitoring, policymaker, + management]). + +intersects(A, B) -> lists:any(fun(I) -> lists:member(I, B) end, A). + +%% The distinction between list_visible_vhosts and list_login_vhosts +%% is there to ensure that admins / monitors can always learn of the +%% existence of all vhosts, and can always see their contribution to +%% global stats. However, if an admin / monitor does not have any +%% permissions for a vhost, it's probably less confusing to make that +%% prevent them from seeing "into" it, than letting them see stuff +%% that they then can't touch. + +list_visible_vhosts(User = #user{tags = Tags}) -> + case is_monitor(Tags) of + true -> rabbit_vhost:list(); + false -> list_login_vhosts(User) + end. + +list_login_vhosts(User) -> + [V || V <- rabbit_vhost:list(), + case catch rabbit_access_control:check_vhost_access(User, V) of + ok -> true; + _ -> false + end]. + +%% Wow, base64:decode throws lots of weird errors. Catch and convert to one +%% that will cause a bad_request. +b64decode_or_throw(B64) -> + try + base64:decode(B64) + catch error:_ -> + throw({error, {not_base64, B64}}) + end. + +no_range() -> {no_range, no_range, no_range}. + +%% Take floor on queries so we make sure we only return samples +%% for which we've finished receiving events. Fixes the "drop at +%% the end" problem. +range(ReqData) -> {range("lengths", fun floor/2, ReqData), + range("msg_rates", fun floor/2, ReqData), + range("data_rates", fun floor/2, ReqData)}. + +%% ...but if we know only one event could have contributed towards +%% what we are interested in, then let's take the ceiling instead and +%% get slightly fresher data. +%% +%% Why does msg_rates still use floor/2? Because in the cases where we +%% call this function (for connections and queues) the msg_rates are still +%% aggregated even though the lengths and data rates aren't. +range_ceil(ReqData) -> {range("lengths", fun ceil/2, ReqData), + range("msg_rates", fun floor/2, ReqData), + range("data_rates", fun ceil/2, ReqData)}. + +range(Prefix, Round, ReqData) -> + Age0 = int(Prefix ++ "_age", ReqData), + Incr0 = int(Prefix ++ "_incr", ReqData), + if + is_integer(Age0) andalso is_integer(Incr0) -> + Age = Age0 * 1000, + Incr = Incr0 * 1000, + Now = rabbit_mgmt_format:timestamp_ms(erlang:now()), + Last = Round(Now, Incr), + #range{first = (Last - Age), + last = Last, + incr = Incr}; + true -> + no_range + end. + +floor(TS, Interval) -> (TS div Interval) * Interval. + +ceil(TS, Interval) -> case floor(TS, Interval) of + TS -> TS; + Floor -> Floor + Interval + end. + +int(Name, ReqData) -> + case wrq:get_qs_value(Name, ReqData) of + undefined -> undefined; + Str -> case catch list_to_integer(Str) of + {'EXIT', _} -> undefined; + Integer -> Integer + end + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_aliveness_test.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_aliveness_test.erl new file mode 100644 index 0000000..445eee5 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_aliveness_test.erl @@ -0,0 +1,59 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_aliveness_test). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2]). +-export([resource_exists/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +-define(QUEUE, <<"aliveness-test">>). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +resource_exists(ReqData, Context) -> + {case rabbit_mgmt_util:vhost(ReqData) of + not_found -> false; + _ -> true + end, ReqData, Context}. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:with_channel( + rabbit_mgmt_util:vhost(ReqData), ReqData, Context, + fun(Ch) -> + amqp_channel:call(Ch, #'queue.declare'{queue = ?QUEUE}), + amqp_channel:call(Ch, #'basic.publish'{routing_key = ?QUEUE}, + #amqp_msg{payload = <<"test_message">>}), + {#'basic.get_ok'{}, _} = + amqp_channel:call(Ch, #'basic.get'{queue = ?QUEUE, + no_ack = true}), + %% Don't delete the queue. If this is pinged every few + %% seconds we don't want to create a mnesia transaction + %% each time. + rabbit_mgmt_util:reply([{status, ok}], ReqData, Context) + end). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_vhost(ReqData, Context). + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_binding.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_binding.erl new file mode 100644 index 0000000..d794558 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_binding.erl @@ -0,0 +1,137 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_binding). + +-export([init/1, resource_exists/2, to_json/2, + content_types_provided/2, content_types_accepted/2, + is_authorized/2, allowed_methods/2, delete_resource/2, + args_hash/1]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +%%-------------------------------------------------------------------- +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +content_types_accepted(ReqData, Context) -> + {[{"application/json", accept_content}], ReqData, Context}. + +allowed_methods(ReqData, Context) -> + {['HEAD', 'GET', 'DELETE'], ReqData, Context}. + +resource_exists(ReqData, Context) -> + Binding = binding(ReqData), + {case Binding of + not_found -> false; + {bad_request, _} -> false; + _ -> case rabbit_binding:exists(Binding) of + true -> true; + _ -> false + end + end, ReqData, Context}. + +to_json(ReqData, Context) -> + with_binding(ReqData, Context, + fun(Binding) -> + rabbit_mgmt_util:reply( + rabbit_mgmt_format:binding(Binding), + ReqData, Context) + end). + +delete_resource(ReqData, Context) -> + MethodName = case rabbit_mgmt_util:destination_type(ReqData) of + exchange -> 'exchange.unbind'; + queue -> 'queue.unbind' + end, + sync_resource(MethodName, ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_vhost(ReqData, Context). + +%%-------------------------------------------------------------------- + +binding(ReqData) -> + case rabbit_mgmt_util:vhost(ReqData) of + not_found -> not_found; + VHost -> Source = rabbit_mgmt_util:id(source, ReqData), + Dest = rabbit_mgmt_util:id(destination, ReqData), + DestType = rabbit_mgmt_util:destination_type(ReqData), + Props = rabbit_mgmt_util:id(props, ReqData), + SName = rabbit_misc:r(VHost, exchange, Source), + DName = rabbit_misc:r(VHost, DestType, Dest), + case unpack(SName, DName, Props) of + {bad_request, Str} -> + {bad_request, Str}; + {Key, Args} -> + #binding{ source = SName, + destination = DName, + key = Key, + args = Args } + end + end. + +unpack(Src, Dst, Props) -> + case rabbit_mgmt_format:tokenise(binary_to_list(Props)) of + ["~"] -> {<<>>, []}; + [Key] -> {unquote(Key), []}; + ["~", ArgsEnc] -> lookup(<<>>, ArgsEnc, Src, Dst); + [Key, ArgsEnc] -> lookup(unquote(Key), ArgsEnc, Src, Dst); + _ -> {bad_request, {too_many_tokens, Props}} + end. + +lookup(RoutingKey, ArgsEnc, Src, Dst) -> + lookup(RoutingKey, unquote(ArgsEnc), + rabbit_binding:list_for_source_and_destination(Src, Dst)). + +lookup(_RoutingKey, _Hash, []) -> + {bad_request, "binding not found"}; +lookup(RoutingKey, Hash, [#binding{args = Args} | Rest]) -> + case args_hash(Args) =:= Hash of + true -> {RoutingKey, Args}; + false -> lookup(RoutingKey, Hash, Rest) + end. + +args_hash(Args) -> + list_to_binary(rabbit_misc:base64url(erlang:md5(term_to_binary(Args)))). + +unquote(Name) -> + list_to_binary(mochiweb_util:unquote(Name)). + +with_binding(ReqData, Context, Fun) -> + case binding(ReqData) of + {bad_request, Reason} -> + rabbit_mgmt_util:bad_request(Reason, ReqData, Context); + Binding -> + Fun(Binding) + end. + +sync_resource(MethodName, ReqData, Context) -> + with_binding( + ReqData, Context, + fun(Binding) -> + Props0 = rabbit_mgmt_format:binding(Binding), + Props = Props0 ++ + [{exchange, proplists:get_value(source, Props0)}, + {queue, proplists:get_value(destination, Props0)}], + rabbit_mgmt_util:amqp_request( + rabbit_mgmt_util:vhost(ReqData), ReqData, Context, + rabbit_mgmt_util:props_to_method(MethodName, Props)) + end). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_bindings.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_bindings.erl new file mode 100644 index 0000000..c452038 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_bindings.erl @@ -0,0 +1,136 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_bindings). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2]). +-export([allowed_methods/2, post_is_create/2, create_path/2]). +-export([content_types_accepted/2, accept_content/2, resource_exists/2]). +-export([basic/1, augmented/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +%%-------------------------------------------------------------------- + +init([Mode]) -> + {ok, {Mode, #context{}}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +resource_exists(ReqData, {Mode, Context}) -> + {case list_bindings(Mode, ReqData) of + vhost_not_found -> false; + _ -> true + end, ReqData, {Mode, Context}}. + +content_types_accepted(ReqData, Context) -> + {[{"application/json", accept_content}], ReqData, Context}. + +allowed_methods(ReqData, {Mode, Context}) -> + {case Mode of + source_destination -> ['HEAD', 'GET', 'POST']; + _ -> ['HEAD', 'GET'] + end, ReqData, {Mode, Context}}. + +post_is_create(ReqData, Context) -> + {true, ReqData, Context}. + +to_json(ReqData, {Mode, Context}) -> + Bs = [rabbit_mgmt_format:binding(B) || B <- list_bindings(Mode, ReqData)], + rabbit_mgmt_util:reply_list( + rabbit_mgmt_util:filter_vhost(Bs, ReqData, Context), + ["vhost", "source", "type", "destination", + "routing_key", "properties_key"], + ReqData, {Mode, Context}). + +create_path(ReqData, Context) -> + {"dummy", ReqData, Context}. + +accept_content(ReqData, {_Mode, Context}) -> + Source = rabbit_mgmt_util:id(source, ReqData), + Dest = rabbit_mgmt_util:id(destination, ReqData), + DestType = rabbit_mgmt_util:id(dtype, ReqData), + VHost = rabbit_mgmt_util:vhost(ReqData), + {ok, Props} = rabbit_mgmt_util:decode(wrq:req_body(ReqData)), + {Method, Key, Args} = method_key_args(DestType, Source, Dest, Props), + Response = rabbit_mgmt_util:amqp_request(VHost, ReqData, Context, Method), + case Response of + {{halt, _}, _, _} = Res -> + Res; + {true, ReqData, Context2} -> + Loc = rabbit_web_dispatch_util:relativise( + wrq:path(ReqData), + binary_to_list( + rabbit_mgmt_format:url( + "/api/bindings/~s/e/~s/~s/~s/~s", + [VHost, Source, DestType, Dest, + rabbit_mgmt_format:pack_binding_props(Key, Args)]))), + ReqData2 = wrq:set_resp_header("Location", Loc, ReqData), + {true, ReqData2, Context2} + end. + +is_authorized(ReqData, {Mode, Context}) -> + {Res, RD2, C2} = rabbit_mgmt_util:is_authorized_vhost(ReqData, Context), + {Res, RD2, {Mode, C2}}. + +%%-------------------------------------------------------------------- + +basic(ReqData) -> + [rabbit_mgmt_format:binding(B) || + B <- list_bindings(all, ReqData)]. + +augmented(ReqData, Context) -> + rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData, Context). + +method_key_args(<<"q">>, Source, Dest, Props) -> + M = #'queue.bind'{routing_key = K, arguments = A} = + rabbit_mgmt_util:props_to_method( + 'queue.bind', Props, + [], [{exchange, Source}, {queue, Dest}]), + {M, K, A}; + +method_key_args(<<"e">>, Source, Dest, Props) -> + M = #'exchange.bind'{routing_key = K, arguments = A} = + rabbit_mgmt_util:props_to_method( + 'exchange.bind', Props, + [], [{source, Source}, {destination, Dest}]), + {M, K, A}. + +%%-------------------------------------------------------------------- + +list_bindings(all, ReqData) -> + rabbit_mgmt_util:all_or_one_vhost(ReqData, + fun (VHost) -> + rabbit_binding:list(VHost) + end); +list_bindings(exchange_source, ReqData) -> + rabbit_binding:list_for_source(r(exchange, exchange, ReqData)); +list_bindings(exchange_destination, ReqData) -> + rabbit_binding:list_for_destination(r(exchange, exchange, ReqData)); +list_bindings(queue, ReqData) -> + rabbit_binding:list_for_destination(r(queue, destination, ReqData)); +list_bindings(source_destination, ReqData) -> + DestType = rabbit_mgmt_util:destination_type(ReqData), + rabbit_binding:list_for_source_and_destination( + r(exchange, source, ReqData), + r(DestType, destination, ReqData)). + +r(Type, Name, ReqData) -> + rabbit_misc:r(rabbit_mgmt_util:vhost(ReqData), Type, + rabbit_mgmt_util:id(Name, ReqData)). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_channel.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_channel.erl new file mode 100644 index 0000000..67e584f --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_channel.erl @@ -0,0 +1,51 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_channel). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2]). +-export([resource_exists/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +resource_exists(ReqData, Context) -> + case channel(ReqData) of + not_found -> {false, ReqData, Context}; + _Conn -> {true, ReqData, Context} + end. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply( + {struct, rabbit_mgmt_format:strip_pids(channel(ReqData))}, + ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_user(ReqData, Context, channel(ReqData)). + +%%-------------------------------------------------------------------- + +channel(ReqData) -> + rabbit_mgmt_db:get_channel(rabbit_mgmt_util:id(channel, ReqData), + rabbit_mgmt_util:range(ReqData)). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_channels.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_channels.erl new file mode 100644 index 0000000..0274c8d --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_channels.erl @@ -0,0 +1,44 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_channels). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2, + augmented/2]). + +-import(rabbit_misc, [pget/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply_list(augmented(ReqData, Context), ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized(ReqData, Context). + +augmented(ReqData, Context) -> + rabbit_mgmt_util:filter_conn_ch_list( + rabbit_mgmt_db:get_all_channels( + rabbit_mgmt_util:range(ReqData)), ReqData, Context). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_cluster_name.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_cluster_name.erl new file mode 100644 index 0000000..e3d2c43 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_cluster_name.erl @@ -0,0 +1,57 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_cluster_name). + +-export([init/1, resource_exists/2, to_json/2, + content_types_provided/2, content_types_accepted/2, + is_authorized/2, allowed_methods/2, accept_content/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +%%-------------------------------------------------------------------- +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +content_types_accepted(ReqData, Context) -> + {[{"application/json", accept_content}], ReqData, Context}. + +allowed_methods(ReqData, Context) -> + {['HEAD', 'GET', 'PUT'], ReqData, Context}. + +resource_exists(ReqData, Context) -> + {true, ReqData, Context}. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply( + [{name, rabbit_nodes:cluster_name()}], ReqData, Context). + +accept_content(ReqData, Context) -> + rabbit_mgmt_util:with_decode( + [name], ReqData, Context, fun([Name], _) -> + rabbit_nodes:set_cluster_name(Name), + {true, ReqData, Context} + end). + +is_authorized(ReqData, Context) -> + case wrq:method(ReqData) of + 'PUT' -> rabbit_mgmt_util:is_authorized_admin(ReqData, Context); + _ -> rabbit_mgmt_util:is_authorized(ReqData, Context) + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_connection.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_connection.erl new file mode 100644 index 0000000..7f918b1 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_connection.erl @@ -0,0 +1,66 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_connection). + +-export([init/1, resource_exists/2, to_json/2, content_types_provided/2, + is_authorized/2, allowed_methods/2, delete_resource/2, conn/1]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +allowed_methods(ReqData, Context) -> + {['HEAD', 'GET', 'DELETE'], ReqData, Context}. + +resource_exists(ReqData, Context) -> + case conn(ReqData) of + not_found -> {false, ReqData, Context}; + _Conn -> {true, ReqData, Context} + end. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply( + {struct, rabbit_mgmt_format:strip_pids(conn(ReqData))}, ReqData, Context). + +delete_resource(ReqData, Context) -> + Conn = conn(ReqData), + Pid = proplists:get_value(pid, Conn), + Reason = case wrq:get_req_header(<<"X-Reason">>, ReqData) of + undefined -> "Closed via management plugin"; + V -> V + end, + case proplists:get_value(type, Conn) of + direct -> amqp_direct_connection:server_close(Pid, 320, Reason); + network -> rabbit_networking:close_connection(Pid, Reason) + end, + {true, ReqData, Context}. + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_user(ReqData, Context, conn(ReqData)). + +%%-------------------------------------------------------------------- + +conn(ReqData) -> + rabbit_mgmt_db:get_connection(rabbit_mgmt_util:id(connection, ReqData), + rabbit_mgmt_util:range_ceil(ReqData)). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_connection_channels.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_connection_channels.erl new file mode 100644 index 0000000..c15b977 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_connection_channels.erl @@ -0,0 +1,54 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_connection_channels). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2]). +-export([resource_exists/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +resource_exists(ReqData, Context) -> + case rabbit_mgmt_wm_connection:conn(ReqData) of + error -> {false, ReqData, Context}; + _Conn -> {true, ReqData, Context} + end. + +to_json(ReqData, Context) -> + Name = proplists:get_value(name, rabbit_mgmt_wm_connection:conn(ReqData)), + Chs = rabbit_mgmt_db:get_all_channels(rabbit_mgmt_util:range(ReqData)), + rabbit_mgmt_util:reply_list( + [Ch || Ch <- rabbit_mgmt_util:filter_conn_ch_list(Chs, ReqData, Context), + conn_name(Ch) =:= Name], + ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_user( + ReqData, Context, rabbit_mgmt_wm_connection:conn(ReqData)). + +%%-------------------------------------------------------------------- + +conn_name(Ch) -> + proplists:get_value(name, proplists:get_value(connection_details, Ch)). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_connections.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_connections.erl new file mode 100644 index 0000000..85689eb --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_connections.erl @@ -0,0 +1,44 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_connections). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2, + augmented/2]). + +-import(rabbit_misc, [pget/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply_list(augmented(ReqData, Context), ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized(ReqData, Context). + +augmented(ReqData, Context) -> + rabbit_mgmt_util:filter_conn_ch_list( + rabbit_mgmt_db:get_all_connections( + rabbit_mgmt_util:range_ceil(ReqData)), ReqData, Context). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_definitions.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_definitions.erl new file mode 100644 index 0000000..d62f7c8 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_definitions.erl @@ -0,0 +1,281 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_definitions). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2]). +-export([content_types_accepted/2, allowed_methods/2, accept_json/2]). +-export([post_is_create/2, create_path/2, accept_multipart/2]). + +-export([apply_defs/3]). + +-import(rabbit_misc, [pget/2, pget/3]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +%%-------------------------------------------------------------------- +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +content_types_accepted(ReqData, Context) -> + {[{"application/json", accept_json}, + {"multipart/form-data", accept_multipart}], ReqData, Context}. + +allowed_methods(ReqData, Context) -> + {['HEAD', 'GET', 'POST'], ReqData, Context}. + +post_is_create(ReqData, Context) -> + {true, ReqData, Context}. + +create_path(ReqData, Context) -> + {"dummy", ReqData, Context}. + +to_json(ReqData, Context) -> + Xs = [X || X <- rabbit_mgmt_wm_exchanges:basic(ReqData), + export_exchange(X)], + Qs = [Q || Q <- rabbit_mgmt_wm_queues:basic(ReqData), + export_queue(Q)], + QNames = [{pget(name, Q), pget(vhost, Q)} || Q <- Qs], + Bs = [B || B <- rabbit_mgmt_wm_bindings:basic(ReqData), + export_binding(B, QNames)], + {ok, Vsn} = application:get_key(rabbit, vsn), + rabbit_mgmt_util:reply( + [{rabbit_version, list_to_binary(Vsn)}] ++ + filter( + [{users, rabbit_mgmt_wm_users:users()}, + {vhosts, rabbit_mgmt_wm_vhosts:basic()}, + {permissions, rabbit_mgmt_wm_permissions:permissions()}, + {parameters, rabbit_mgmt_wm_parameters:basic(ReqData)}, + {policies, rabbit_mgmt_wm_policies:basic(ReqData)}, + {queues, Qs}, + {exchanges, Xs}, + {bindings, Bs}]), + case wrq:get_qs_value("download", ReqData) of + undefined -> ReqData; + Filename -> wrq:set_resp_header( + "Content-Disposition", + "attachment; filename=" ++ + mochiweb_util:unquote(Filename), ReqData) + end, + Context). + +accept_json(ReqData, Context) -> + accept(wrq:req_body(ReqData), ReqData, Context). + +accept_multipart(ReqData, Context) -> + Parts = webmachine_multipart:get_all_parts( + wrq:req_body(ReqData), + webmachine_multipart:find_boundary(ReqData)), + Redirect = get_part("redirect", Parts), + Json = get_part("file", Parts), + Resp = {Res, _, _} = accept(Json, ReqData, Context), + case Res of + true -> + ReqData1 = + case Redirect of + unknown -> ReqData; + _ -> rabbit_mgmt_util:redirect(Redirect, ReqData) + end, + {true, ReqData1, Context}; + _ -> + Resp + end. + +is_authorized(ReqData, Context) -> + case wrq:get_qs_value("auth", ReqData) of + undefined -> rabbit_mgmt_util:is_authorized_admin(ReqData, Context); + Auth -> is_authorized_qs(ReqData, Context, Auth) + end. + +%% Support for the web UI - it can't add a normal "authorization" +%% header for a file download. +is_authorized_qs(ReqData, Context, Auth) -> + case rabbit_web_dispatch_util:parse_auth_header("Basic " ++ Auth) of + [Username, Password] -> rabbit_mgmt_util:is_authorized_admin( + ReqData, Context, Username, Password); + _ -> {?AUTH_REALM, ReqData, Context} + end. + +%%-------------------------------------------------------------------- + +accept(Body, ReqData, Context) -> + apply_defs(Body, fun() -> {true, ReqData, Context} end, + fun(E) -> rabbit_mgmt_util:bad_request(E, ReqData, Context) end). + +apply_defs(Body, SuccessFun, ErrorFun) -> + case rabbit_mgmt_util:decode([], Body) of + {error, E} -> + ErrorFun(E); + {ok, _, All} -> + try + for_all(users, All, fun add_user/1), + for_all(vhosts, All, fun add_vhost/1), + for_all(permissions, All, fun add_permission/1), + for_all(parameters, All, fun add_parameter/1), + for_all(policies, All, fun add_policy/1), + for_all(queues, All, fun add_queue/1), + for_all(exchanges, All, fun add_exchange/1), + for_all(bindings, All, fun add_binding/1), + SuccessFun() + catch {error, E} -> ErrorFun(format(E)); + exit:E -> ErrorFun(format(E)) + end + end. + +format(#amqp_error{name = Name, explanation = Explanation}) -> + list_to_binary(rabbit_misc:format("~s: ~s", [Name, Explanation])); +format(E) -> + list_to_binary(rabbit_misc:format("~p", [E])). + +get_part(Name, Parts) -> + %% TODO any reason not to use lists:keyfind instead? + Filtered = [Value || {N, _Meta, Value} <- Parts, N == Name], + case Filtered of + [] -> unknown; + [F] -> F + end. + +export_queue(Queue) -> + pget(owner_pid, Queue) == none. + +export_binding(Binding, Qs) -> + Src = pget(source, Binding), + Dest = pget(destination, Binding), + DestType = pget(destination_type, Binding), + VHost = pget(vhost, Binding), + Src =/= <<"">> + andalso + ( (DestType =:= queue andalso lists:member({Dest, VHost}, Qs)) + orelse (DestType =:= exchange andalso Dest =/= <<"">>) ). + +export_exchange(Exchange) -> + export_name(pget(name, Exchange)). + +export_name(<<>>) -> false; +export_name(<<"amq.", _/binary>>) -> false; +export_name(_Name) -> true. + +%%-------------------------------------------------------------------- + +rw_state() -> + [{users, [name, password_hash, tags]}, + {vhosts, [name]}, + {permissions, [user, vhost, configure, write, read]}, + {parameters, [vhost, component, name, value]}, + {policies, [vhost, name, pattern, definition, priority, 'apply-to']}, + {queues, [name, vhost, durable, auto_delete, arguments]}, + {exchanges, [name, vhost, type, durable, auto_delete, internal, + arguments]}, + {bindings, [source, vhost, destination, destination_type, routing_key, + arguments]}]. + +filter(Items) -> + [filter_items(N, V, proplists:get_value(N, rw_state())) || {N, V} <- Items]. + +filter_items(Name, List, Allowed) -> + {Name, [filter_item(I, Allowed) || I <- List]}. + +filter_item(Item, Allowed) -> + [{K, Fact} || {K, Fact} <- Item, lists:member(K, Allowed)]. + +%%-------------------------------------------------------------------- + +for_all(Name, All, Fun) -> + case pget(Name, All) of + undefined -> ok; + List -> [Fun([{atomise_name(K), V} || {K, V} <- I]) || + {struct, I} <- List] + end. + +atomise_name(N) -> list_to_atom(binary_to_list(N)). + +%%-------------------------------------------------------------------- + +add_parameter(Param) -> + VHost = pget(vhost, Param), + Comp = pget(component, Param), + Key = pget(name, Param), + Term = rabbit_misc:json_to_term(pget(value, Param)), + case rabbit_runtime_parameters:set(VHost, Comp, Key, Term, none) of + ok -> ok; + {error_string, E} -> S = rabbit_misc:format(" (~s/~s/~s)", + [VHost, Comp, Key]), + exit(list_to_binary(E ++ S)) + end. + +add_policy(Param) -> + VHost = pget(vhost, Param), + Key = pget(name, Param), + case rabbit_policy:set( + VHost, Key, pget(pattern, Param), + rabbit_misc:json_to_term(pget(definition, Param)), + pget(priority, Param), + pget('apply-to', Param, <<"all">>)) of + ok -> ok; + {error_string, E} -> S = rabbit_misc:format(" (~s/~s)", [VHost, Key]), + exit(list_to_binary(E ++ S)) + end. + +add_user(User) -> + rabbit_mgmt_wm_user:put_user(User). + +add_vhost(VHost) -> + VHostName = pget(name, VHost), + VHostTrace = pget(tracing, VHost), + rabbit_mgmt_wm_vhost:put_vhost(VHostName, VHostTrace). + +add_permission(Permission) -> + rabbit_auth_backend_internal:set_permissions(pget(user, Permission), + pget(vhost, Permission), + pget(configure, Permission), + pget(write, Permission), + pget(read, Permission)). + +add_queue(Queue) -> + rabbit_amqqueue:declare(r(queue, Queue), + pget(durable, Queue), + pget(auto_delete, Queue), + rabbit_mgmt_util:args(pget(arguments, Queue)), + none). + +add_exchange(Exchange) -> + Internal = case pget(internal, Exchange) of + undefined -> false; %% =< 2.2.0 + I -> I + end, + rabbit_exchange:declare(r(exchange, Exchange), + rabbit_exchange:check_type(pget(type, Exchange)), + pget(durable, Exchange), + pget(auto_delete, Exchange), + Internal, + rabbit_mgmt_util:args(pget(arguments, Exchange))). + +add_binding(Binding) -> + DestType = list_to_atom(binary_to_list(pget(destination_type, Binding))), + rabbit_binding:add( + #binding{source = r(exchange, source, Binding), + destination = r(DestType, destination, Binding), + key = pget(routing_key, Binding), + args = rabbit_mgmt_util:args(pget(arguments, Binding))}). + +r(Type, Props) -> r(Type, name, Props). + +r(Type, Name, Props) -> + rabbit_misc:r(pget(vhost, Props), Type, pget(Name, Props)). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_exchange.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_exchange.erl new file mode 100644 index 0000000..711756e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_exchange.erl @@ -0,0 +1,82 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_exchange). + +-export([init/1, resource_exists/2, to_json/2, + content_types_provided/2, content_types_accepted/2, + is_authorized/2, allowed_methods/2, accept_content/2, + delete_resource/2, exchange/1, exchange/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +%%-------------------------------------------------------------------- +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +content_types_accepted(ReqData, Context) -> + {[{"application/json", accept_content}], ReqData, Context}. + +allowed_methods(ReqData, Context) -> + {['HEAD', 'GET', 'PUT', 'DELETE'], ReqData, Context}. + +resource_exists(ReqData, Context) -> + {case exchange(ReqData) of + not_found -> false; + _ -> true + end, ReqData, Context}. + +to_json(ReqData, Context) -> + [X] = rabbit_mgmt_db:augment_exchanges( + [exchange(ReqData)], rabbit_mgmt_util:range(ReqData), full), + rabbit_mgmt_util:reply(X, ReqData, Context). + +accept_content(ReqData, Context) -> + rabbit_mgmt_util:http_to_amqp( + 'exchange.declare', ReqData, Context, + [{fun rabbit_mgmt_util:parse_bool/1, [durable, auto_delete, internal]}], + [{exchange, rabbit_mgmt_util:id(exchange, ReqData)}]). + +delete_resource(ReqData, Context) -> + rabbit_mgmt_util:amqp_request( + rabbit_mgmt_util:vhost(ReqData), ReqData, Context, + #'exchange.delete'{ exchange = id(ReqData) }). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_vhost(ReqData, Context). + +%%-------------------------------------------------------------------- + +exchange(ReqData) -> + case rabbit_mgmt_util:vhost(ReqData) of + not_found -> not_found; + VHost -> exchange(VHost, id(ReqData)) + end. + +exchange(VHost, XName) -> + Name = rabbit_misc:r(VHost, exchange, XName), + case rabbit_exchange:lookup(Name) of + {ok, X} -> rabbit_mgmt_format:exchange( + rabbit_exchange:info(X)); + {error, not_found} -> not_found + end. + +id(ReqData) -> + rabbit_mgmt_util:id(exchange, ReqData). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_exchange_publish.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_exchange_publish.erl new file mode 100644 index 0000000..e1cc29e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_exchange_publish.erl @@ -0,0 +1,99 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2011-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_exchange_publish). + +-export([init/1, resource_exists/2, post_is_create/2, is_authorized/2, + allowed_methods/2, process_post/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +%%-------------------------------------------------------------------- +init(_Config) -> {ok, #context{}}. + +allowed_methods(ReqData, Context) -> + {['POST'], ReqData, Context}. + +resource_exists(ReqData, Context) -> + {case rabbit_mgmt_wm_exchange:exchange(ReqData) of + not_found -> false; + _ -> true + end, ReqData, Context}. + +post_is_create(ReqData, Context) -> + {false, ReqData, Context}. + +process_post(ReqData, Context) -> + rabbit_mgmt_util:post_respond(do_it(ReqData, Context)). + +do_it(ReqData, Context) -> + VHost = rabbit_mgmt_util:vhost(ReqData), + X = rabbit_mgmt_util:id(exchange, ReqData), + rabbit_mgmt_util:with_decode( + [routing_key, properties, payload, payload_encoding], ReqData, Context, + fun ([RoutingKey, Props0, Payload0, Enc], _) when is_binary(Payload0) -> + rabbit_mgmt_util:with_channel( + VHost, ReqData, Context, + fun (Ch) -> + MRef = erlang:monitor(process, Ch), + amqp_channel:register_confirm_handler(Ch, self()), + amqp_channel:register_return_handler(Ch, self()), + amqp_channel:call(Ch, #'confirm.select'{}), + Props = rabbit_mgmt_format:to_basic_properties(Props0), + Payload = decode(Payload0, Enc), + amqp_channel:cast(Ch, #'basic.publish'{ + exchange = X, + routing_key = RoutingKey, + mandatory = true}, + #amqp_msg{props = Props, + payload = Payload}), + receive + {#'basic.return'{}, _} -> + receive + #'basic.ack'{} -> ok + end, + good(MRef, false, ReqData, Context); + #'basic.ack'{} -> + good(MRef, true, ReqData, Context); + {'DOWN', _, _, _, Err} -> + bad(Err, ReqData, Context) + end + end); + ([_RoutingKey, _Props, _Payload, _Enc], _) -> + throw({error, payload_not_string}) + end). + +good(MRef, Routed, ReqData, Context) -> + erlang:demonitor(MRef), + rabbit_mgmt_util:reply([{routed, Routed}], ReqData, Context). + +bad({shutdown, {connection_closing, + {server_initiated_close, Code, Reason}}}, ReqData, Context) -> + rabbit_mgmt_util:bad_request_exception(Code, Reason, ReqData, Context); + +bad({shutdown, {server_initiated_close, Code, Reason}}, ReqData, Context) -> + rabbit_mgmt_util:bad_request_exception(Code, Reason, ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_vhost(ReqData, Context). + +%%-------------------------------------------------------------------- + +decode(Payload, <<"string">>) -> Payload; +decode(Payload, <<"base64">>) -> rabbit_mgmt_util:b64decode_or_throw(Payload); +decode(_Payload, Enc) -> throw({error, {unsupported_encoding, Enc}}). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_exchanges.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_exchanges.erl new file mode 100644 index 0000000..f5cdf7d --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_exchanges.erl @@ -0,0 +1,56 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_exchanges). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2, + resource_exists/2, basic/1, augmented/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +resource_exists(ReqData, Context) -> + {case exchanges0(ReqData) of + vhost_not_found -> false; + _ -> true + end, ReqData, Context}. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply_list(augmented(ReqData, Context), ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_vhost(ReqData, Context). + +%%-------------------------------------------------------------------- + +augmented(ReqData, Context) -> + rabbit_mgmt_db:augment_exchanges( + rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData, Context), + rabbit_mgmt_util:range(ReqData), basic). + +basic(ReqData) -> + [rabbit_mgmt_format:exchange(X) || X <- exchanges0(ReqData)]. + +exchanges0(ReqData) -> + rabbit_mgmt_util:all_or_one_vhost(ReqData, fun rabbit_exchange:info_all/1). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_extensions.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_extensions.erl new file mode 100644 index 0000000..bb0f15e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_extensions.erl @@ -0,0 +1,37 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2011-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_extensions). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +to_json(ReqData, Context) -> + Modules = rabbit_mgmt_dispatcher:modules(), + rabbit_mgmt_util:reply( + [Module:web_ui() || Module <- Modules], ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized(ReqData, Context). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_node.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_node.erl new file mode 100644 index 0000000..27522f6 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_node.erl @@ -0,0 +1,63 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Console. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_node). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2]). +-export([resource_exists/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +resource_exists(ReqData, Context) -> + {case node0(ReqData) of + not_found -> false; + _ -> true + end, ReqData, Context}. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply(node0(ReqData), ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_monitor(ReqData, Context). + +%%-------------------------------------------------------------------- + +node0(ReqData) -> + Name = list_to_atom(binary_to_list(rabbit_mgmt_util:id(node, ReqData))), + case [N || N <- rabbit_mgmt_wm_nodes:all_nodes(), + proplists:get_value(name, N) == Name] of + [] -> not_found; + [Node] -> augment(ReqData, Name, Node) + end. + +augment(ReqData, Name, Node) -> + case wrq:get_qs_value("memory", ReqData) of + "true" -> Mem = case rpc:call(Name, rabbit_vm, memory, [], infinity) of + {badrpc, _} -> not_available; + Memory -> Memory + end, + [{memory, Mem} | Node]; + _ -> Node + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_nodes.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_nodes.erl new file mode 100644 index 0000000..0f5b532 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_nodes.erl @@ -0,0 +1,48 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Console. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_nodes). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2]). +-export([all_nodes/0]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply_list(all_nodes(), ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_monitor(ReqData, Context). + +%%-------------------------------------------------------------------- + +all_nodes() -> + S = rabbit_mnesia:status(), + Nodes = proplists:get_value(nodes, S), + Types = proplists:get_keys(Nodes), + Running = proplists:get_value(running_nodes, S), + rabbit_mgmt_db:augment_nodes( + [[{name, Node}, {type, Type}, {running, lists:member(Node, Running)}] || + Type <- Types, Node <- proplists:get_value(Type, Nodes)]). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_overview.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_overview.erl new file mode 100644 index 0000000..db8f336 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_overview.erl @@ -0,0 +1,97 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_overview). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2]). + +-import(rabbit_misc, [pget/2, pget/3]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +to_json(ReqData, Context = #context{user = User = #user{tags = Tags}}) -> + {ok, StatsLevel} = application:get_env(rabbit, collect_statistics), + %% NB: this duplicates what's in /nodes but we want a global idea + %% of this. And /nodes is not accessible to non-monitor users. + ExchangeTypes = rabbit_mgmt_external_stats:list_registry_plugins(exchange), + Overview0 = [{management_version, version(rabbitmq_management)}, + {statistics_level, StatsLevel}, + {exchange_types, ExchangeTypes}, + {rabbitmq_version, version(rabbit)}, + {cluster_name, rabbit_nodes:cluster_name()}, + {erlang_version, erl_version(otp_release)}, + {erlang_full_version, erl_version(system_version)}], + Range = rabbit_mgmt_util:range(ReqData), + Overview = + case rabbit_mgmt_util:is_monitor(Tags) of + true -> + Overview0 ++ + [{K, {struct, V}} || + {K, V} <- rabbit_mgmt_db:get_overview(Range)] ++ + [{node, node()}, + {statistics_db_node, stats_db_node()}, + {listeners, listeners()}, + {contexts, rabbit_web_dispatch_contexts()}]; + _ -> + Overview0 ++ + [{K, {struct, V}} || + {K, V} <- rabbit_mgmt_db:get_overview(User, Range)] + end, + rabbit_mgmt_util:reply(Overview, ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized(ReqData, Context). + +%%-------------------------------------------------------------------- + +stats_db_node() -> + case global:whereis_name(rabbit_mgmt_db) of + undefined -> not_running; + Pid -> node(Pid) + end. + +version(App) -> + {ok, V} = application:get_key(App, vsn), + list_to_binary(V). + +listeners() -> + rabbit_mgmt_util:sort_list( + [rabbit_mgmt_format:listener(L) + || L <- rabbit_networking:active_listeners()], + ["protocol", "port", "node"] ). + +%%-------------------------------------------------------------------- + +rabbit_web_dispatch_contexts() -> + rabbit_mgmt_util:sort_list( + lists:append( + [rabbit_web_dispatch_contexts(N) || N <- rabbit_mgmt_wm_nodes:all_nodes()]), + ["description", "port", "node"]). + +rabbit_web_dispatch_contexts(N) -> + [[{node, pget(name, N)} | C] || C <- pget(contexts, N, [])]. + +erl_version(K) -> + list_to_binary(string:strip(erlang:system_info(K), both, $\n)). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_parameter.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_parameter.erl new file mode 100644 index 0000000..b266542 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_parameter.erl @@ -0,0 +1,88 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_parameter). + +-export([init/1, resource_exists/2, to_json/2, + content_types_provided/2, content_types_accepted/2, + is_authorized/2, allowed_methods/2, accept_content/2, + delete_resource/2]). + +-import(rabbit_misc, [pget/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +content_types_accepted(ReqData, Context) -> + {[{"application/json", accept_content}], ReqData, Context}. + +allowed_methods(ReqData, Context) -> + {['HEAD', 'GET', 'PUT', 'DELETE'], ReqData, Context}. + +resource_exists(ReqData, Context) -> + {case parameter(ReqData) of + not_found -> false; + _ -> true + end, ReqData, Context}. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply(rabbit_mgmt_format:parameter(parameter(ReqData)), + ReqData, Context). + +accept_content(ReqData, Context = #context{user = User}) -> + case rabbit_mgmt_util:vhost(ReqData) of + not_found -> + rabbit_mgmt_util:not_found(vhost_not_found, ReqData, Context); + VHost -> + rabbit_mgmt_util:with_decode( + [value], ReqData, Context, + fun([Value], _) -> + case rabbit_runtime_parameters:set( + VHost, component(ReqData), name(ReqData), + rabbit_misc:json_to_term(Value), User) of + ok -> + {true, ReqData, Context}; + {error_string, Reason} -> + rabbit_mgmt_util:bad_request( + list_to_binary(Reason), ReqData, Context) + end + end) + end. + +delete_resource(ReqData, Context) -> + ok = rabbit_runtime_parameters:clear( + rabbit_mgmt_util:vhost(ReqData), component(ReqData), name(ReqData)), + {true, ReqData, Context}. + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_policies(ReqData, Context). + +%%-------------------------------------------------------------------- + +parameter(ReqData) -> + rabbit_runtime_parameters:lookup( + rabbit_mgmt_util:vhost(ReqData), component(ReqData), name(ReqData)). + +component(ReqData) -> rabbit_mgmt_util:id(component, ReqData). +name(ReqData) -> rabbit_mgmt_util:id(name, ReqData). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_parameters.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_parameters.erl new file mode 100644 index 0000000..c2f1e1b --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_parameters.erl @@ -0,0 +1,63 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_parameters). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2, + resource_exists/2, basic/1]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +resource_exists(ReqData, Context) -> + {case basic(ReqData) of + not_found -> false; + _ -> true + end, ReqData, Context}. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply_list( + rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData, Context), + ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_policies(ReqData, Context). + +%%-------------------------------------------------------------------- + +basic(ReqData) -> + Raw = case rabbit_mgmt_util:id(component, ReqData) of + none -> rabbit_runtime_parameters:list(); + Name -> case rabbit_mgmt_util:vhost(ReqData) of + none -> rabbit_runtime_parameters:list_component( + Name); + not_found -> not_found; + VHost -> rabbit_runtime_parameters:list( + VHost, Name) + end + end, + case Raw of + not_found -> not_found; + _ -> [rabbit_mgmt_format:parameter(P) || P <- Raw] + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_permission.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_permission.erl new file mode 100644 index 0000000..55eca61 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_permission.erl @@ -0,0 +1,97 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_permission). + +-export([init/1, resource_exists/2, to_json/2, + content_types_provided/2, content_types_accepted/2, + is_authorized/2, allowed_methods/2, accept_content/2, + delete_resource/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +content_types_accepted(ReqData, Context) -> + {[{"application/json", accept_content}], ReqData, Context}. + +allowed_methods(ReqData, Context) -> + {['HEAD', 'GET', 'PUT', 'DELETE'], ReqData, Context}. + +resource_exists(ReqData, Context) -> + {case perms(ReqData) of + none -> false; + not_found -> false; + _ -> true + end, ReqData, Context}. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply(perms(ReqData), ReqData, Context). + +accept_content(ReqData, Context) -> + case perms(ReqData) of + not_found -> + rabbit_mgmt_util:bad_request(vhost_or_user_not_found, + ReqData, Context); + _ -> + User = rabbit_mgmt_util:id(user, ReqData), + VHost = rabbit_mgmt_util:id(vhost, ReqData), + rabbit_mgmt_util:with_decode( + [configure, write, read], ReqData, Context, + fun([Conf, Write, Read], _) -> + rabbit_auth_backend_internal:set_permissions( + User, VHost, Conf, Write, Read), + {true, ReqData, Context} + end) + end. + +delete_resource(ReqData, Context) -> + User = rabbit_mgmt_util:id(user, ReqData), + VHost = rabbit_mgmt_util:id(vhost, ReqData), + rabbit_auth_backend_internal:clear_permissions(User, VHost), + {true, ReqData, Context}. + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_admin(ReqData, Context). + +%%-------------------------------------------------------------------- + +perms(ReqData) -> + User = rabbit_mgmt_util:id(user, ReqData), + case rabbit_auth_backend_internal:lookup_user(User) of + {ok, _} -> + case rabbit_mgmt_util:vhost(ReqData) of + not_found -> + not_found; + VHost -> + Perms = + rabbit_auth_backend_internal:list_user_vhost_permissions( + User, VHost), + case Perms of + [Rest] -> [{user, User}, + {vhost, VHost} | Rest]; + [] -> none + end + end; + {error, _} -> + not_found + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_permissions.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_permissions.erl new file mode 100644 index 0000000..456bbfb --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_permissions.erl @@ -0,0 +1,43 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_permissions). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2]). +-export([permissions/0]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply_list(permissions(), ["vhost", "user"], + ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_admin(ReqData, Context). + +%%-------------------------------------------------------------------- + +permissions() -> + rabbit_auth_backend_internal:list_permissions(). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_permissions_user.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_permissions_user.erl new file mode 100644 index 0000000..3428033 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_permissions_user.erl @@ -0,0 +1,39 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_permissions_user). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +to_json(ReqData, Context) -> + User = rabbit_mgmt_util:id(user, ReqData), + Perms = rabbit_auth_backend_internal:list_user_permissions(User), + rabbit_mgmt_util:reply_list([[{user, User} | Rest] || Rest <- Perms], + ["vhost", "user"], ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_admin(ReqData, Context). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_permissions_vhost.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_permissions_vhost.erl new file mode 100644 index 0000000..7472e96 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_permissions_vhost.erl @@ -0,0 +1,39 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_permissions_vhost). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +to_json(ReqData, Context) -> + VHost = rabbit_mgmt_util:id(vhost, ReqData), + Perms = rabbit_auth_backend_internal:list_vhost_permissions(VHost), + rabbit_mgmt_util:reply_list([[{vhost, VHost} | Rest] || Rest <- Perms], + ["vhost", "user"], ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_admin(ReqData, Context). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_policies.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_policies.erl new file mode 100644 index 0000000..74c9ee2 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_policies.erl @@ -0,0 +1,54 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_policies). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2, + resource_exists/2, basic/1]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +resource_exists(ReqData, Context) -> + {case basic(ReqData) of + not_found -> false; + _ -> true + end, ReqData, Context}. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply_list( + rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData, Context), + ["priority"], ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_policies(ReqData, Context). + +%%-------------------------------------------------------------------- + +basic(ReqData) -> + case rabbit_mgmt_util:vhost(ReqData) of + not_found -> not_found; + none -> rabbit_policy:list(); + VHost -> rabbit_policy:list(VHost) + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_policy.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_policy.erl new file mode 100644 index 0000000..b22c369 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_policy.erl @@ -0,0 +1,88 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_policy). + +-export([init/1, resource_exists/2, to_json/2, + content_types_provided/2, content_types_accepted/2, + is_authorized/2, allowed_methods/2, accept_content/2, + delete_resource/2]). + +-import(rabbit_misc, [pget/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +content_types_accepted(ReqData, Context) -> + {[{"application/json", accept_content}], ReqData, Context}. + +allowed_methods(ReqData, Context) -> + {['HEAD', 'GET', 'PUT', 'DELETE'], ReqData, Context}. + +resource_exists(ReqData, Context) -> + {case policy(ReqData) of + not_found -> false; + _ -> true + end, ReqData, Context}. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply(policy(ReqData), ReqData, Context). + +accept_content(ReqData, Context) -> + case rabbit_mgmt_util:vhost(ReqData) of + not_found -> + rabbit_mgmt_util:not_found(vhost_not_found, ReqData, Context); + VHost -> + rabbit_mgmt_util:with_decode( + [pattern, definition], ReqData, Context, + fun([Pattern, Definition], Body) -> + case rabbit_policy:set( + VHost, name(ReqData), Pattern, + rabbit_misc:json_to_term(Definition), + proplists:get_value(priority, Body), + proplists:get_value('apply-to', Body)) of + ok -> + {true, ReqData, Context}; + {error_string, Reason} -> + rabbit_mgmt_util:bad_request( + list_to_binary(Reason), ReqData, Context) + end + end) + end. + +delete_resource(ReqData, Context) -> + ok = rabbit_policy:delete( + rabbit_mgmt_util:vhost(ReqData), name(ReqData)), + {true, ReqData, Context}. + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_policies(ReqData, Context). + +%%-------------------------------------------------------------------- + +policy(ReqData) -> + rabbit_policy:lookup( + rabbit_mgmt_util:vhost(ReqData), name(ReqData)). + +name(ReqData) -> rabbit_mgmt_util:id(name, ReqData). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_queue.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_queue.erl new file mode 100644 index 0000000..4c5a5a8 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_queue.erl @@ -0,0 +1,80 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_queue). + +-export([init/1, resource_exists/2, to_json/2, + content_types_provided/2, content_types_accepted/2, + is_authorized/2, allowed_methods/2, accept_content/2, + delete_resource/2, queue/1, queue/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +%%-------------------------------------------------------------------- +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +content_types_accepted(ReqData, Context) -> + {[{"application/json", accept_content}], ReqData, Context}. + +allowed_methods(ReqData, Context) -> + {['HEAD', 'GET', 'PUT', 'DELETE'], ReqData, Context}. + +resource_exists(ReqData, Context) -> + {case queue(ReqData) of + not_found -> false; + _ -> true + end, ReqData, Context}. + +to_json(ReqData, Context) -> + [Q] = rabbit_mgmt_db:augment_queues( + [queue(ReqData)], rabbit_mgmt_util:range_ceil(ReqData), full), + rabbit_mgmt_util:reply(rabbit_mgmt_format:strip_pids(Q), ReqData, Context). + +accept_content(ReqData, Context) -> + rabbit_mgmt_util:http_to_amqp( + 'queue.declare', ReqData, Context, + [{fun rabbit_mgmt_util:parse_bool/1, [durable, auto_delete]}], + [{queue, rabbit_mgmt_util:id(queue, ReqData)}]). + +delete_resource(ReqData, Context) -> + rabbit_mgmt_util:amqp_request( + rabbit_mgmt_util:vhost(ReqData), + ReqData, Context, + #'queue.delete'{ queue = rabbit_mgmt_util:id(queue, ReqData) }). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_vhost(ReqData, Context). + +%%-------------------------------------------------------------------- + +queue(ReqData) -> + case rabbit_mgmt_util:vhost(ReqData) of + not_found -> not_found; + VHost -> queue(VHost, rabbit_mgmt_util:id(queue, ReqData)) + end. + + +queue(VHost, QName) -> + Name = rabbit_misc:r(VHost, queue, QName), + case rabbit_amqqueue:lookup(Name) of + {ok, Q} -> rabbit_mgmt_format:queue(Q); + {error, not_found} -> not_found + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_queue_actions.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_queue_actions.erl new file mode 100644 index 0000000..a601764 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_queue_actions.erl @@ -0,0 +1,70 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2011-2012 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_queue_actions). + +-export([init/1, resource_exists/2, post_is_create/2, is_authorized/2, + allowed_methods/2, process_post/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +allowed_methods(ReqData, Context) -> + {['POST'], ReqData, Context}. + +resource_exists(ReqData, Context) -> + {case rabbit_mgmt_wm_queue:queue(ReqData) of + not_found -> false; + _ -> true + end, ReqData, Context}. + +post_is_create(ReqData, Context) -> + {false, ReqData, Context}. + +process_post(ReqData, Context) -> + rabbit_mgmt_util:post_respond(do_it(ReqData, Context)). + +do_it(ReqData, Context) -> + VHost = rabbit_mgmt_util:vhost(ReqData), + QName = rabbit_mgmt_util:id(queue, ReqData), + rabbit_mgmt_util:with_decode( + [action], ReqData, Context, + fun([Action], _Body) -> + rabbit_amqqueue:with( + rabbit_misc:r(VHost, queue, QName), + fun(Q) -> action(Action, Q, ReqData, Context) end) + end). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_admin(ReqData, Context). + +%%-------------------------------------------------------------------- + +action(<<"sync">>, #amqqueue{pid = QPid}, ReqData, Context) -> + spawn(fun() -> rabbit_amqqueue:sync_mirrors(QPid) end), + {true, ReqData, Context}; + +action(<<"cancel_sync">>, #amqqueue{pid = QPid}, ReqData, Context) -> + rabbit_amqqueue:cancel_sync_mirrors(QPid), + {true, ReqData, Context}; + +action(Else, _Q, ReqData, Context) -> + rabbit_mgmt_util:bad_request({unknown, Else}, ReqData, Context). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_queue_get.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_queue_get.erl new file mode 100644 index 0000000..1602457 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_queue_get.erl @@ -0,0 +1,124 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2011-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_queue_get). + +-export([init/1, resource_exists/2, post_is_create/2, is_authorized/2, + allowed_methods/2, process_post/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +allowed_methods(ReqData, Context) -> + {['POST'], ReqData, Context}. + +resource_exists(ReqData, Context) -> + {case rabbit_mgmt_wm_queue:queue(ReqData) of + not_found -> false; + _ -> true + end, ReqData, Context}. + +post_is_create(ReqData, Context) -> + {false, ReqData, Context}. + +process_post(ReqData, Context) -> + rabbit_mgmt_util:post_respond(do_it(ReqData, Context)). + +do_it(ReqData, Context) -> + VHost = rabbit_mgmt_util:vhost(ReqData), + Q = rabbit_mgmt_util:id(queue, ReqData), + rabbit_mgmt_util:with_decode( + [requeue, count, encoding], ReqData, Context, + fun([RequeueBin, CountBin, EncBin], Body) -> + rabbit_mgmt_util:with_channel( + VHost, ReqData, Context, + fun (Ch) -> + NoAck = not rabbit_mgmt_util:parse_bool(RequeueBin), + Count = rabbit_mgmt_util:parse_int(CountBin), + Enc = case EncBin of + <<"auto">> -> auto; + <<"base64">> -> base64; + _ -> throw({error, + {bad_encoding, + EncBin}}) + end, + Trunc = case proplists:get_value(truncate, Body) of + undefined -> none; + TruncBin -> rabbit_mgmt_util:parse_int( + TruncBin) + end, + rabbit_mgmt_util:reply( + basic_gets(Count, Ch, Q, NoAck, Enc, Trunc), + ReqData, Context) + end) + end). + +basic_gets(0, _, _, _, _, _) -> + []; + +basic_gets(Count, Ch, Q, NoAck, Enc, Trunc) -> + case basic_get(Ch, Q, NoAck, Enc, Trunc) of + none -> []; + M -> [M | basic_gets(Count - 1, Ch, Q, NoAck, Enc, Trunc)] + end. + +basic_get(Ch, Q, NoAck, Enc, Trunc) -> + case amqp_channel:call(Ch, #'basic.get'{queue = Q, + no_ack = NoAck}) of + {#'basic.get_ok'{redelivered = Redelivered, + exchange = Exchange, + routing_key = RoutingKey, + message_count = MessageCount}, + #amqp_msg{props = Props, payload = Payload}} -> + [{payload_bytes, size(Payload)}, + {redelivered, Redelivered}, + {exchange, Exchange}, + {routing_key, RoutingKey}, + {message_count, MessageCount}, + {properties, rabbit_mgmt_format:basic_properties(Props)}] ++ + payload_part(maybe_truncate(Payload, Trunc), Enc); + #'basic.get_empty'{} -> + none + end. + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_vhost(ReqData, Context). + +%%-------------------------------------------------------------------- + +maybe_truncate(Payload, none) -> Payload; +maybe_truncate(Payload, Len) when size(Payload) < Len -> Payload; +maybe_truncate(Payload, Len) -> + <> = Payload, + Start. + +payload_part(Payload, Enc) -> + {PL, E} = case Enc of + auto -> try + %% TODO mochijson does this but is it safe? + xmerl_ucs:from_utf8(Payload), + {Payload, string} + catch exit:{ucs, _} -> + {base64:encode(Payload), base64} + end; + _ -> {base64:encode(Payload), base64} + end, + [{payload, PL}, {payload_encoding, E}]. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_queue_purge.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_queue_purge.erl new file mode 100644 index 0000000..0d35113 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_queue_purge.erl @@ -0,0 +1,45 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_queue_purge). + +-export([init/1, resource_exists/2, is_authorized/2, allowed_methods/2, + delete_resource/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +%%-------------------------------------------------------------------- +init(_Config) -> {ok, #context{}}. + +allowed_methods(ReqData, Context) -> + {['DELETE'], ReqData, Context}. + +resource_exists(ReqData, Context) -> + {case rabbit_mgmt_wm_queue:queue(ReqData) of + not_found -> false; + _ -> true + end, ReqData, Context}. + +delete_resource(ReqData, Context) -> + rabbit_mgmt_util:amqp_request( + rabbit_mgmt_util:vhost(ReqData), + ReqData, Context, + #'queue.purge'{ queue = rabbit_mgmt_util:id(queue, ReqData) }). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_vhost(ReqData, Context). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_queues.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_queues.erl new file mode 100644 index 0000000..58c3da0 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_queues.erl @@ -0,0 +1,57 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_queues). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2, + resource_exists/2, basic/1, augmented/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +resource_exists(ReqData, Context) -> + {case queues0(ReqData) of + vhost_not_found -> false; + _ -> true + end, ReqData, Context}. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply_list(augmented(ReqData, Context), ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_vhost(ReqData, Context). + +%%-------------------------------------------------------------------- + +augmented(ReqData, Context) -> + rabbit_mgmt_format:strip_pids( + rabbit_mgmt_db:augment_queues( + rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData, Context), + rabbit_mgmt_util:range_ceil(ReqData), basic)). + +basic(ReqData) -> + [rabbit_mgmt_format:queue(Q) || Q <- queues0(ReqData)]. + +queues0(ReqData) -> + rabbit_mgmt_util:all_or_one_vhost(ReqData, fun rabbit_amqqueue:list/1). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_user.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_user.erl new file mode 100644 index 0000000..bf1c557 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_user.erl @@ -0,0 +1,109 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_user). + +-export([init/1, resource_exists/2, to_json/2, + content_types_provided/2, content_types_accepted/2, + is_authorized/2, allowed_methods/2, accept_content/2, + delete_resource/2, put_user/1]). + +-import(rabbit_misc, [pget/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +content_types_accepted(ReqData, Context) -> + {[{"application/json", accept_content}], ReqData, Context}. + +allowed_methods(ReqData, Context) -> + {['HEAD', 'GET', 'PUT', 'DELETE'], ReqData, Context}. + +resource_exists(ReqData, Context) -> + {case user(ReqData) of + {ok, _} -> true; + {error, _} -> false + end, ReqData, Context}. + +to_json(ReqData, Context) -> + {ok, User} = user(ReqData), + rabbit_mgmt_util:reply(rabbit_mgmt_format:internal_user(User), + ReqData, Context). + +accept_content(ReqData, Context) -> + Username = rabbit_mgmt_util:id(user, ReqData), + rabbit_mgmt_util:with_decode( + [], ReqData, Context, + fun(_, User) -> + put_user([{name, Username} | User]), + {true, ReqData, Context} + end). + +delete_resource(ReqData, Context) -> + User = rabbit_mgmt_util:id(user, ReqData), + rabbit_auth_backend_internal:delete_user(User), + {true, ReqData, Context}. + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_admin(ReqData, Context). + +%%-------------------------------------------------------------------- + +user(ReqData) -> + rabbit_auth_backend_internal:lookup_user(rabbit_mgmt_util:id(user, ReqData)). + +put_user(User) -> + CP = fun rabbit_auth_backend_internal:change_password/2, + CPH = fun rabbit_auth_backend_internal:change_password_hash/2, + case {proplists:is_defined(password, User), + proplists:is_defined(password_hash, User)} of + {true, _} -> put_user(User, pget(password, User), CP); + {_, true} -> Hash = rabbit_mgmt_util:b64decode_or_throw( + pget(password_hash, User)), + put_user(User, Hash, CPH); + _ -> put_user(User, <<>>, CPH) + end. + +put_user(User, PWArg, PWFun) -> + Username = pget(name, User), + Tags = case {pget(tags, User), pget(administrator, User)} of + {undefined, undefined} -> + throw({error, tags_not_present}); + {undefined, AdminS} -> + case rabbit_mgmt_util:parse_bool(AdminS) of + true -> [administrator]; + false -> [] + end; + {TagsS, _} -> + [list_to_atom(string:strip(T)) || + T <- string:tokens(binary_to_list(TagsS), ",")] + end, + case rabbit_auth_backend_internal:lookup_user(Username) of + {error, not_found} -> + rabbit_auth_backend_internal:add_user( + Username, rabbit_guid:binary(rabbit_guid:gen_secure(), "tmp")); + _ -> + ok + end, + PWFun(Username, PWArg), + ok = rabbit_auth_backend_internal:set_tags(Username, Tags). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_users.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_users.erl new file mode 100644 index 0000000..013561b --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_users.erl @@ -0,0 +1,47 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_users). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2]). +-export([users/0]). + +-import(rabbit_misc, [pget/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply_list(users(), ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_admin(ReqData, Context). + +%%-------------------------------------------------------------------- + +users() -> + [begin + {ok, User} = rabbit_auth_backend_internal:lookup_user(pget(user, U)), + rabbit_mgmt_format:internal_user(User) + end || U <- rabbit_auth_backend_internal:list_users()]. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_vhost.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_vhost.erl new file mode 100644 index 0000000..6c79064 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_vhost.erl @@ -0,0 +1,83 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_vhost). + +-export([init/1, resource_exists/2, to_json/2, + content_types_provided/2, content_types_accepted/2, + is_authorized/2, allowed_methods/2, accept_content/2, + delete_resource/2, put_vhost/2]). + +-import(rabbit_misc, [pget/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +content_types_accepted(ReqData, Context) -> + {[{"application/json", accept_content}], ReqData, Context}. + +allowed_methods(ReqData, Context) -> + {['HEAD', 'GET', 'PUT', 'DELETE'], ReqData, Context}. + +resource_exists(ReqData, Context) -> + {rabbit_vhost:exists(id(ReqData)), ReqData, Context}. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply( + hd(rabbit_mgmt_db:augment_vhosts( + [rabbit_vhost:info(id(ReqData))], rabbit_mgmt_util:range(ReqData))), + ReqData, Context). + +accept_content(ReqData, Context) -> + Name = id(ReqData), + rabbit_mgmt_util:with_decode( + [], ReqData, Context, + fun(_, VHost) -> + put_vhost(Name, rabbit_mgmt_util:parse_bool( + pget(tracing, VHost))), + {true, ReqData, Context} + end). + +delete_resource(ReqData, Context) -> + VHost = id(ReqData), + rabbit_vhost:delete(VHost), + {true, ReqData, Context}. + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_admin(ReqData, Context). + +%%-------------------------------------------------------------------- + +id(ReqData) -> + rabbit_mgmt_util:id(vhost, ReqData). + +put_vhost(Name, Trace) -> + case rabbit_vhost:exists(Name) of + true -> ok; + false -> rabbit_vhost:add(Name) + end, + case Trace of + true -> rabbit_trace:start(Name); + false -> rabbit_trace:stop(Name); + undefined -> ok + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_vhosts.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_vhosts.erl new file mode 100644 index 0000000..54bf9ff --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_vhosts.erl @@ -0,0 +1,47 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_vhosts). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2]). +-export([basic/0, augmented/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply_list(augmented(ReqData, Context), ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized(ReqData, Context). + +%%-------------------------------------------------------------------- + +augmented(ReqData, #context{user = User}) -> + rabbit_mgmt_db:augment_vhosts( + [rabbit_vhost:info(V) || V <- rabbit_mgmt_util:list_visible_vhosts(User)], + rabbit_mgmt_util:range(ReqData)). + +basic() -> + rabbit_vhost:info_all([name]). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_whoami.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_whoami.erl new file mode 100644 index 0000000..0d75b04 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbit_mgmt_wm_whoami.erl @@ -0,0 +1,35 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_whoami). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2]). + +-include("rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +to_json(ReqData, Context = #context{user = User}) -> + rabbit_mgmt_util:reply(rabbit_mgmt_format:user(User), ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized(ReqData, Context). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbitmq_management.app.src b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbitmq_management.app.src new file mode 100644 index 0000000..37831f8 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/src/rabbitmq_management.app.src @@ -0,0 +1,17 @@ +{application, rabbitmq_management, + [{description, "RabbitMQ Management Console"}, + {vsn, "%%VSN%%"}, + {modules, []}, + {registered, []}, + {mod, {rabbit_mgmt_app, []}}, + {env, [{listener, [{port, 15672}]}, + {http_log_dir, none}, + {load_definitions, none}, + {sample_retention_policies, + %% List of {MaxAgeSecs, IfTimestampDivisibleBySecs} + [{global, [{605, 5}, {3660, 60}, {29400, 600}, {86400, 1800}]}, + {basic, [{605, 5}, {3600, 60}]}, + {detailed, [{10, 5}]}]} + ]}, + {applications, [kernel, stdlib, rabbit, xmerl, rabbitmq_web_dispatch, + amqp_client, rabbitmq_management_agent]}]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/default-config b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/default-config new file mode 100644 index 0000000..b76eba8 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/default-config @@ -0,0 +1,15 @@ +# rabbitmqadmin.conf.example START + +[non_default] +hostname = localhost +port = 25672 +username = guest +password = guest +declare_vhost = / # Used as default for declare / delete only +vhost = / # Used as default for declare / delete / list + +[bad_host] +hostname = rabbit.acme.com +port = 15672 +username = guest +password = guest diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_clustering.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_clustering.erl new file mode 100644 index 0000000..642b427 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_clustering.erl @@ -0,0 +1,98 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Console. +%% +%% The Initial Developers of the Original Code are Rabbit Technologies Ltd. +%% +%% Copyright (C) 2010 Rabbit Technologies Ltd. +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% +-module(rabbit_mgmt_test_clustering). + +-compile(export_all). +-include("rabbit_mgmt_test.hrl"). + +-import(rabbit_mgmt_test_http, [http_get/1, http_put/3, http_delete/2]). +-import(rabbit_misc, [pget/2]). + +%%---------------------------------------------------------------------------- + +cluster_nodes_with() -> cluster_ab. +cluster_nodes([_A, _B]) -> + ?assertEqual(2, length(http_get("/nodes"))), + ok. + +ha_with() -> cluster_ab. +ha([RabbitCfg, HareCfg]) -> + Rabbit = pget(nodename, RabbitCfg), + Hare = pget(nodename, HareCfg), + Policy = [{pattern, <<".*">>}, + {definition, [{'ha-mode', <<"all">>}]}], + http_put("/policies/%2f/HA", Policy, ?NO_CONTENT), + QArgs = [{node, list_to_binary(atom_to_list(Hare))}], + http_put("/queues/%2f/ha-queue", QArgs, ?NO_CONTENT), + Q = wait_for("/queues/%2f/ha-queue"), + assert_node(Hare, pget(node, Q)), + assert_single_node(Rabbit, pget(slave_nodes, Q)), + assert_single_node(Rabbit, pget(synchronised_slave_nodes, Q)), + _HareCfg2 = rabbit_test_configs:restart_node(HareCfg), + + Q2 = wait_for("/queues/%2f/ha-queue"), + assert_node(Rabbit, pget(node, Q2)), + assert_single_node(Hare, pget(slave_nodes, Q2)), + assert_single_node(Hare, pget(synchronised_slave_nodes, Q2)), + http_delete("/queues/%2f/ha-queue", ?NO_CONTENT), + http_delete("/policies/%2f/HA", ?NO_CONTENT), + ok. + +%%---------------------------------------------------------------------------- + +wait_for(Path) -> + wait_for(Path, [slave_nodes, synchronised_slave_nodes]). + +wait_for(Path, Keys) -> + wait_for(Path, Keys, 1000). + +wait_for(Path, Keys, 0) -> + exit({timeout, {Path, Keys}}); + +wait_for(Path, Keys, Count) -> + Res = http_get(Path), + case present(Keys, Res) of + false -> timer:sleep(10), + wait_for(Path, Keys, Count - 1); + true -> Res + end. + +present(Keys, Res) -> + lists:all(fun (Key) -> + X = pget(Key, Res), + X =/= [] andalso X =/= undefined + end, Keys). + +assert_single_node(Exp, Act) -> + ?assertEqual(1, length(Act)), + assert_node(Exp, hd(Act)). + +assert_nodes(Exp, Act0) -> + Act = [read_node(A) || A <- Act0], + ?debugVal({Exp, Act}), + ?assertEqual(length(Exp), length(Act)), + [?assert(lists:member(E, Act)) || E <- Exp]. + +assert_node(Exp, Act) -> + ?assertEqual(Exp, read_node(Act)). + +read_node(N) -> + list_to_atom(hd(string:tokens(binary_to_list(N), "@"))). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_db.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_db.erl new file mode 100644 index 0000000..ef2f5be --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_db.erl @@ -0,0 +1,296 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Console. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_test_db). + +-include("rabbit_mgmt.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +-import(rabbit_misc, [pget/2]). +-import(rabbit_mgmt_test_util, [assert_list/2, assert_item/2, test_item/2]). + +-define(debugVal2(E), + ((fun (__V) -> + ?debugFmt(<<"~s = ~p">>, [(??E), __V]), + __V + end)(E))). + +%%---------------------------------------------------------------------------- +%% Tests +%%---------------------------------------------------------------------------- + +queue_coarse_test() -> + rabbit_mgmt_db:override_lookups([{exchange, fun dummy_lookup/1}, + {queue, fun dummy_lookup/1}]), + create_q(test, 0), + create_q(test2, 0), + stats_q(test, 0, 10), + stats_q(test2, 0, 1), + R = range(0, 1, 1), + Exp = fun(N) -> simple_details(messages, N, R) end, + assert_item(Exp(10), get_q(test, R)), + assert_item(Exp(11), get_vhost(R)), + assert_item(Exp(11), get_overview_q(R)), + delete_q(test, 0), + assert_item(Exp(1), get_vhost(R)), + assert_item(Exp(1), get_overview_q(R)), + delete_q(test2, 0), + assert_item(Exp(0), get_vhost(R)), + assert_item(Exp(0), get_overview_q(R)), + rabbit_mgmt_db:reset_lookups(), + ok. + +connection_coarse_test() -> + create_conn(test, 0), + create_conn(test2, 0), + stats_conn(test, 0, 10), + stats_conn(test2, 0, 1), + R = range(0, 1, 1), + Exp = fun(N) -> simple_details(recv_oct, N, R) end, + assert_item(Exp(10), get_conn(test, R)), + assert_item(Exp(1), get_conn(test2, R)), + delete_conn(test, 1), + delete_conn(test2, 1), + assert_list([], rabbit_mgmt_db:get_all_connections(R)), + ok. + +fine_stats_aggregation_test() -> + rabbit_mgmt_db:override_lookups([{exchange, fun dummy_lookup/1}, + {queue, fun dummy_lookup/1}]), + create_ch(ch1, 0), + create_ch(ch2, 0), + stats_ch(ch1, 0, [{x, 100}], [{q1, x, 100}, + {q2, x, 10}], [{q1, 2}, + {q2, 1}]), + stats_ch(ch2, 0, [{x, 10}], [{q1, x, 50}, + {q2, x, 5}], []), + fine_stats_aggregation_test0(true), + delete_q(q2, 0), + fine_stats_aggregation_test0(false), + delete_ch(ch1, 1), + delete_ch(ch2, 1), + rabbit_mgmt_db:reset_lookups(), + ok. + +fine_stats_aggregation_test0(Q2Exists) -> + R = range(0, 1, 1), + Ch1 = get_ch(ch1, R), + Ch2 = get_ch(ch2, R), + X = get_x(x, R), + Q1 = get_q(q1, R), + V = get_vhost(R), + O = get_overview(R), + assert_fine_stats(m, publish, 100, Ch1, R), + assert_fine_stats(m, publish, 10, Ch2, R), + assert_fine_stats(m, publish_in, 110, X, R), + assert_fine_stats(m, publish_out, 165, X, R), + assert_fine_stats(m, publish, 150, Q1, R), + assert_fine_stats(m, deliver_get, 2, Q1, R), + assert_fine_stats(m, deliver_get, 3, Ch1, R), + assert_fine_stats(m, publish, 110, V, R), + assert_fine_stats(m, deliver_get, 3, V, R), + assert_fine_stats(m, publish, 110, O, R), + assert_fine_stats(m, deliver_get, 3, O, R), + assert_fine_stats({pub, x}, publish, 100, Ch1, R), + assert_fine_stats({pub, x}, publish, 10, Ch2, R), + assert_fine_stats({in, ch1}, publish, 100, X, R), + assert_fine_stats({in, ch2}, publish, 10, X, R), + assert_fine_stats({out, q1}, publish, 150, X, R), + assert_fine_stats({in, x}, publish, 150, Q1, R), + assert_fine_stats({del, ch1}, deliver_get, 2, Q1, R), + assert_fine_stats({del, q1}, deliver_get, 2, Ch1, R), + case Q2Exists of + true -> Q2 = get_q(q2, R), + assert_fine_stats(m, publish, 15, Q2, R), + assert_fine_stats(m, deliver_get, 1, Q2, R), + assert_fine_stats({out, q2}, publish, 15, X, R), + assert_fine_stats({in, x}, publish, 15, Q2, R), + assert_fine_stats({del, ch1}, deliver_get, 1, Q2, R), + assert_fine_stats({del, q2}, deliver_get, 1, Ch1, R); + false -> assert_fine_stats_neg({out, q2}, X), + assert_fine_stats_neg({del, q2}, Ch1) + end, + ok. + +fine_stats_aggregation_time_test() -> + rabbit_mgmt_db:override_lookups([{exchange, fun dummy_lookup/1}, + {queue, fun dummy_lookup/1}]), + create_ch(ch, 0), + stats_ch(ch, 0, [{x, 100}], [{q, x, 50}], [{q, 20}]), + stats_ch(ch, 5, [{x, 110}], [{q, x, 55}], [{q, 22}]), + + R1 = range(0, 1, 1), + assert_fine_stats(m, publish, 100, get_ch(ch, R1), R1), + assert_fine_stats(m, publish, 50, get_q(q, R1), R1), + assert_fine_stats(m, deliver_get, 20, get_q(q, R1), R1), + + R2 = range(5, 6, 1), + assert_fine_stats(m, publish, 110, get_ch(ch, R2), R2), + assert_fine_stats(m, publish, 55, get_q(q, R2), R2), + assert_fine_stats(m, deliver_get, 22, get_q(q, R2), R2), + + delete_q(q, 0), + delete_ch(ch, 1), + rabbit_mgmt_db:reset_lookups(), + ok. + +assert_fine_stats(m, Type, N, Obj, R) -> + Act = pget(message_stats, Obj), + assert_item(simple_details(Type, N, R), Act); +assert_fine_stats({T2, Name}, Type, N, Obj, R) -> + Act = find_detailed_stats(Name, pget(expand(T2), Obj)), + assert_item(simple_details(Type, N, R), Act). + +assert_fine_stats_neg({T2, Name}, Obj) -> + detailed_stats_absent(Name, pget(expand(T2), Obj)). + +%%---------------------------------------------------------------------------- +%% Events in +%%---------------------------------------------------------------------------- + +create_q(Name, Timestamp) -> + %% Technically we do not need this, the DB ignores it, but let's + %% be symmetrical... + event(queue_created, [{name, q(Name)}], Timestamp). + +create_conn(Name, Timestamp) -> + event(connection_created, [{pid, pid(Name)}, + {name, a2b(Name)}], Timestamp). + +create_ch(Name, Timestamp) -> + event(channel_created, [{pid, pid(Name)}, + {name, a2b(Name)}], Timestamp). + +stats_q(Name, Timestamp, Msgs) -> + event(queue_stats, [{name, q(Name)}, + {messages, Msgs}], Timestamp). + +stats_conn(Name, Timestamp, Oct) -> + event(connection_stats, [{pid , pid(Name)}, + {recv_oct, Oct}], Timestamp). + +stats_ch(Name, Timestamp, XStats, QXStats, QStats) -> + XStats1 = [{x(XName), [{publish, N}]} || {XName, N} <- XStats], + QXStats1 = [{{q(QName), x(XName)}, [{publish, N}]} + || {QName, XName, N} <- QXStats], + QStats1 = [{q(QName), [{deliver_no_ack, N}]} || {QName, N} <- QStats], + event(channel_stats, + [{pid, pid(Name)}, + {channel_exchange_stats, XStats1}, + {channel_queue_exchange_stats, QXStats1}, + {channel_queue_stats, QStats1}], Timestamp). + +delete_q(Name, Timestamp) -> + event(queue_deleted, [{name, q(Name)}], Timestamp). + +delete_conn(Name, Timestamp) -> + event(connection_closed, [{pid, pid_del(Name)}], Timestamp). + +delete_ch(Name, Timestamp) -> + event(channel_closed, [{pid, pid_del(Name)}], Timestamp). + +event(Type, Stats, Timestamp) -> + gen_server:cast({global, rabbit_mgmt_db}, + {event, #event{type = Type, + props = Stats, + reference = none, + timestamp = sec_to_triple(Timestamp)}}). + +sec_to_triple(Sec) -> {Sec div 1000000, Sec rem 1000000, 0}. + +%%---------------------------------------------------------------------------- +%% Events out +%%---------------------------------------------------------------------------- + +range(F, L, I) -> + R = #range{first = F * 1000, last = L * 1000, incr = I * 1000}, + {R, R, R}. + +get_x(Name, Range) -> + [X] = rabbit_mgmt_db:augment_exchanges([x2(Name)], Range, full), + X. + +get_q(Name, Range) -> + [Q] = rabbit_mgmt_db:augment_queues([q2(Name)], Range, full), + Q. + +get_vhost(Range) -> + [VHost] = rabbit_mgmt_db:augment_vhosts([[{name, <<"/">>}]], Range), + VHost. + +get_conn(Name, Range) -> rabbit_mgmt_db:get_connection(a2b(Name), Range). +get_ch(Name, Range) -> rabbit_mgmt_db:get_channel(a2b(Name), Range). + +get_overview(Range) -> rabbit_mgmt_db:get_overview(Range). +get_overview_q(Range) -> pget(queue_totals, get_overview(Range)). + +details0(R, AR, A, L) -> + [{rate, R}, + {samples, [[{sample, S}, {timestamp, T}] || {T, S} <- L]}, + {avg_rate, AR}, + {avg, A}]. + +simple_details(Thing, N, {#range{first = First, last = Last}, _, _}) -> + [{Thing, N}, + {atom_suffix(Thing, "_details"), + details0(0.0, 0.0, N * 1.0, [{Last, N}, {First, N}])}]. + +atom_suffix(Atom, Suffix) -> + list_to_atom(atom_to_list(Atom) ++ Suffix). + +find_detailed_stats(Name, List) -> + [S] = filter_detailed_stats(Name, List), + S. + +detailed_stats_absent(Name, List) -> + [] = filter_detailed_stats(Name, List). + +filter_detailed_stats(Name, List) -> + [Stats || [{stats, Stats}, {_, Details}] <- List, + pget(name, Details) =:= a2b(Name)]. + +expand(in) -> incoming; +expand(out) -> outgoing; +expand(del) -> deliveries; +expand(pub) -> publishes. + +%%---------------------------------------------------------------------------- +%% Util +%%---------------------------------------------------------------------------- + +x(Name) -> rabbit_misc:r(<<"/">>, exchange, a2b(Name)). +x2(Name) -> q2(Name). +q(Name) -> rabbit_misc:r(<<"/">>, queue, a2b(Name)). +q2(Name) -> [{name, a2b(Name)}, + {vhost, <<"/">>}]. + +pid(Name) -> + case get({pid, Name}) of + undefined -> P = spawn(fun() -> ok end), + put({pid, Name}, P), + P; + Pid -> Pid + end. + +pid_del(Name) -> + Pid = pid(Name), + erase({pid, Name}), + Pid. + +a2b(A) -> list_to_binary(atom_to_list(A)). + +dummy_lookup(_Thing) -> {ok, ignore_this}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_db_unit.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_db_unit.erl new file mode 100644 index 0000000..80af615 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_db_unit.erl @@ -0,0 +1,135 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Console. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2012 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_test_db_unit). + +-include("rabbit_mgmt.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +gc_test() -> + T = fun (Before, After) -> + ?assertEqual(After, unstats( + rabbit_mgmt_stats:gc( + cutoff(), stats(Before)))) + end, + %% Cut off old sample, move to base + T({[{8999, 123}, {9000, 456}], 0}, + {[{9000, 456}], 123}), + %% Amalgamate old samples to rounder one + T({[{9001, 100}, {9010, 020}, {10000, 003}], 0}, + {[{10000, 123}], 0}), + %% The same, but a bit less + T({[{9000, 100}, {9901, 020}, {9910, 003}], 0}, + {[{9000, 100}, {9910, 023}], 0}), + %% Nothing needs to be done + T({[{9000, 100}, {9990, 020}, {9991, 003}], 0}, + {[{9000, 100}, {9990, 020}, {9991, 003}], 0}), + %% Invent a newer sample that's acceptable + T({[{9001, 10}, {9010, 02}], 0}, + {[{9100, 12}], 0}), + %% ...but don't if it's too old + T({[{8001, 10}, {8010, 02}], 0}, + {[], 12}), + ok. + +format_test() -> + Interval = 10, + T = fun ({First, Last, Incr}, Stats, Results) -> + ?assertEqual(format(Results), + rabbit_mgmt_stats:format( + #range{first = First * 1000, + last = Last * 1000, + incr = Incr * 1000}, + stats(Stats), + Interval * 1000)) + end, + + %% Just three samples, all of which we format. Note the + %% instantaneous rate is taken from the penultimate sample. + T({10, 30, 10}, {[{10, 10}, {20, 20}, {30, 30}], 1}, + {[{30, 61}, {20, 31}, {10, 11}], 2.0, 2.5, 103/3, 61}), + + %% Skip over the second (and ditto). + T({10, 30, 20}, {[{10, 10}, {20, 20}, {30, 30}], 1}, + {[{30, 61}, {10, 11}], 2.0, 2.5, 36.0, 61}), + + %% Skip over some and invent some. Note that the instantaneous + %% rate drops to 0 since the last event is now in the past. + T({0, 40, 20}, {[{10, 10}, {20, 20}, {30, 30}], 1}, + {[{40, 61}, {20, 31}, {0, 1}], 0.0, 1.5, 31.0, 61}), + + %% And a case where the range starts after the samples + T({20, 40, 10}, {[{10, 10}, {20, 20}, {30, 30}], 1}, + {[{40, 61}, {30, 61}, {20, 31}], 0.0, 1.5, 51.0, 61}), + + %% A single sample - which should lead to some bits not getting generated + T({10, 10, 10}, {[{10, 10}, {20, 20}, {30, 30}], 1}, + {[{10, 11}], 0.0, 11}), + + %% No samples - which should also lead to some bits not getting generated + T({10, 0, 10}, {[{10, 10}, {20, 20}, {30, 30}], 1}, + {[], 0.0, 1}), + + %% TODO more? + ok. + +format_no_range_test() -> + Interval = 10, + T = fun (Stats, Results) -> + ?assertEqual(format(Results), + rabbit_mgmt_stats:format( + no_range, stats(Stats), Interval * 1000)) + end, + + %% Just three samples + T({[{10, 10}, {20, 20}, {30, 30}], 1}, + {0.0, 61}), + ok. + + +%%-------------------------------------------------------------------- + +cutoff() -> + {[{10, 1}, {100, 10}, {1000, 100}], %% Sec + 10000000}. %% Millis + +stats({Diffs, Base}) -> + #stats{diffs = gb_trees:from_orddict(secs_to_millis(Diffs)), base = Base}. + +unstats(#stats{diffs = Diffs, base = Base}) -> + {millis_to_secs(gb_trees:to_list(Diffs)), Base}. + +secs_to_millis(L) -> [{TS * 1000, S} || {TS, S} <- L]. +millis_to_secs(L) -> [{TS div 1000, S} || {TS, S} <- L]. + +format({Rate, Count}) -> + {[{rate, Rate}], + Count}; + +format({Samples, Rate, Count}) -> + {[{rate, Rate}, + {samples, format_samples(Samples)}], + Count}; + +format({Samples, Rate, AvgRate, Avg, Count}) -> + {[{rate, Rate}, + {samples, format_samples(Samples)}, + {avg_rate, AvgRate}, + {avg, Avg}], + Count}. + +format_samples(Samples) -> + [[{sample, S}, {timestamp, TS * 1000}] || {TS, S} <- Samples]. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_http.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_http.erl new file mode 100644 index 0000000..d794909 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_http.erl @@ -0,0 +1,1248 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Console. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_test_http). + +-include("rabbit_mgmt_test.hrl"). + +-export([http_get/1, http_put/3, http_delete/2]). + +-import(rabbit_mgmt_test_util, [assert_list/2, assert_item/2, test_item/2]). +-import(rabbit_misc, [pget/2]). + +overview_test() -> + %% Rather crude, but this req doesn't say much and at least this means it + %% didn't blow up. + true = 0 < length(pget(listeners, http_get("/overview"))), + http_put("/users/myuser", [{password, <<"myuser">>}, + {tags, <<"management">>}], ?NO_CONTENT), + http_get("/overview", "myuser", "myuser", ?OK), + http_delete("/users/myuser", ?NO_CONTENT), + %% TODO uncomment when priv works in test + %%http_get(""), + ok. + +cluster_name_test() -> + http_put("/users/myuser", [{password, <<"myuser">>}, + {tags, <<"management">>}], ?NO_CONTENT), + http_put("/cluster-name", [{name, "foo"}], "myuser", "myuser", ?NOT_AUTHORISED), + http_put("/cluster-name", [{name, "foo"}], ?NO_CONTENT), + [{name, "foo"}] = http_get("/cluster-name", "myuser", "myuser", ?OK), + http_delete("/users/myuser", ?NO_CONTENT), + ok. + +nodes_test() -> + http_put("/users/user", [{password, <<"user">>}, + {tags, <<"management">>}], ?NO_CONTENT), + http_put("/users/monitor", [{password, <<"monitor">>}, + {tags, <<"monitoring">>}], ?NO_CONTENT), + DiscNode = [{type, <<"disc">>}, {running, true}], + assert_list([DiscNode], http_get("/nodes")), + assert_list([DiscNode], http_get("/nodes", "monitor", "monitor", ?OK)), + http_get("/nodes", "user", "user", ?NOT_AUTHORISED), + [Node] = http_get("/nodes"), + Path = "/nodes/" ++ binary_to_list(pget(name, Node)), + assert_item(DiscNode, http_get(Path, ?OK)), + assert_item(DiscNode, http_get(Path, "monitor", "monitor", ?OK)), + http_get(Path, "user", "user", ?NOT_AUTHORISED), + http_delete("/users/user", ?NO_CONTENT), + http_delete("/users/monitor", ?NO_CONTENT), + ok. + +auth_test() -> + http_put("/users/user", [{password, <<"user">>}, + {tags, <<"">>}], ?NO_CONTENT), + test_auth(?NOT_AUTHORISED, []), + test_auth(?NOT_AUTHORISED, [auth_header("user", "user")]), + test_auth(?NOT_AUTHORISED, [auth_header("guest", "gust")]), + test_auth(?OK, [auth_header("guest", "guest")]), + http_delete("/users/user", ?NO_CONTENT), + ok. + +%% This test is rather over-verbose as we're trying to test understanding of +%% Webmachine +vhosts_test() -> + assert_list([[{name, <<"/">>}]], http_get("/vhosts")), + %% Create a new one + http_put("/vhosts/myvhost", none, ?NO_CONTENT), + %% PUT should be idempotent + http_put("/vhosts/myvhost", none, ?NO_CONTENT), + %% Check it's there + assert_list([[{name, <<"/">>}], [{name, <<"myvhost">>}]], + http_get("/vhosts")), + %% Check individually + assert_item([{name, <<"/">>}], http_get("/vhosts/%2f", ?OK)), + assert_item([{name, <<"myvhost">>}],http_get("/vhosts/myvhost")), + %% Delete it + http_delete("/vhosts/myvhost", ?NO_CONTENT), + %% It's not there + http_get("/vhosts/myvhost", ?NOT_FOUND), + http_delete("/vhosts/myvhost", ?NOT_FOUND). + +vhosts_trace_test() -> + http_put("/vhosts/myvhost", none, ?NO_CONTENT), + Disabled = [{name, <<"myvhost">>}, {tracing, false}], + Enabled = [{name, <<"myvhost">>}, {tracing, true}], + Disabled = http_get("/vhosts/myvhost"), + http_put("/vhosts/myvhost", [{tracing, true}], ?NO_CONTENT), + Enabled = http_get("/vhosts/myvhost"), + http_put("/vhosts/myvhost", [{tracing, true}], ?NO_CONTENT), + Enabled = http_get("/vhosts/myvhost"), + http_put("/vhosts/myvhost", [{tracing, false}], ?NO_CONTENT), + Disabled = http_get("/vhosts/myvhost"), + http_delete("/vhosts/myvhost", ?NO_CONTENT). + +users_test() -> + assert_item([{name, <<"guest">>}, {tags, <<"administrator">>}], + http_get("/whoami")), + http_get("/users/myuser", ?NOT_FOUND), + http_put_raw("/users/myuser", "Something not JSON", ?BAD_REQUEST), + http_put("/users/myuser", [{flim, <<"flam">>}], ?BAD_REQUEST), + http_put("/users/myuser", [{tags, <<"management">>}], ?NO_CONTENT), + http_put("/users/myuser", [{password_hash, <<"not_hash">>}], ?BAD_REQUEST), + http_put("/users/myuser", [{password_hash, + <<"IECV6PZI/Invh0DL187KFpkO5Jc=">>}, + {tags, <<"management">>}], ?NO_CONTENT), + http_put("/users/myuser", [{password, <<"password">>}, + {tags, <<"administrator, foo">>}], ?NO_CONTENT), + assert_item([{name, <<"myuser">>}, {tags, <<"administrator,foo">>}], + http_get("/users/myuser")), + assert_list([[{name, <<"myuser">>}, {tags, <<"administrator,foo">>}], + [{name, <<"guest">>}, {tags, <<"administrator">>}]], + http_get("/users")), + test_auth(?OK, [auth_header("myuser", "password")]), + http_delete("/users/myuser", ?NO_CONTENT), + test_auth(?NOT_AUTHORISED, [auth_header("myuser", "password")]), + http_get("/users/myuser", ?NOT_FOUND), + ok. + +users_legacy_administrator_test() -> + http_put("/users/myuser1", [{administrator, <<"true">>}], ?NO_CONTENT), + http_put("/users/myuser2", [{administrator, <<"false">>}], ?NO_CONTENT), + assert_item([{name, <<"myuser1">>}, {tags, <<"administrator">>}], + http_get("/users/myuser1")), + assert_item([{name, <<"myuser2">>}, {tags, <<"">>}], + http_get("/users/myuser2")), + http_delete("/users/myuser1", ?NO_CONTENT), + http_delete("/users/myuser2", ?NO_CONTENT), + ok. + +permissions_validation_test() -> + Good = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}], + http_put("/permissions/wrong/guest", Good, ?BAD_REQUEST), + http_put("/permissions/%2f/wrong", Good, ?BAD_REQUEST), + http_put("/permissions/%2f/guest", + [{configure, <<"[">>}, {write, <<".*">>}, {read, <<".*">>}], + ?BAD_REQUEST), + http_put("/permissions/%2f/guest", Good, ?NO_CONTENT), + ok. + +permissions_list_test() -> + [[{user,<<"guest">>}, + {vhost,<<"/">>}, + {configure,<<".*">>}, + {write,<<".*">>}, + {read,<<".*">>}]] = + http_get("/permissions"), + + http_put("/users/myuser1", [{password, <<"">>}, {tags, <<"administrator">>}], + ?NO_CONTENT), + http_put("/users/myuser2", [{password, <<"">>}, {tags, <<"administrator">>}], + ?NO_CONTENT), + http_put("/vhosts/myvhost1", none, ?NO_CONTENT), + http_put("/vhosts/myvhost2", none, ?NO_CONTENT), + + Perms = [{configure, <<"foo">>}, {write, <<"foo">>}, {read, <<"foo">>}], + http_put("/permissions/myvhost1/myuser1", Perms, ?NO_CONTENT), + http_put("/permissions/myvhost2/myuser1", Perms, ?NO_CONTENT), + http_put("/permissions/myvhost1/myuser2", Perms, ?NO_CONTENT), + + 4 = length(http_get("/permissions")), + 2 = length(http_get("/users/myuser1/permissions")), + 1 = length(http_get("/users/myuser2/permissions")), + + http_delete("/users/myuser1", ?NO_CONTENT), + http_delete("/users/myuser2", ?NO_CONTENT), + http_delete("/vhosts/myvhost1", ?NO_CONTENT), + http_delete("/vhosts/myvhost2", ?NO_CONTENT), + ok. + +permissions_test() -> + http_put("/users/myuser", [{password, <<"myuser">>}, {tags, <<"administrator">>}], + ?NO_CONTENT), + http_put("/vhosts/myvhost", none, ?NO_CONTENT), + + http_put("/permissions/myvhost/myuser", + [{configure, <<"foo">>}, {write, <<"foo">>}, {read, <<"foo">>}], + ?NO_CONTENT), + + Permission = [{user,<<"myuser">>}, + {vhost,<<"myvhost">>}, + {configure,<<"foo">>}, + {write,<<"foo">>}, + {read,<<"foo">>}], + Default = [{user,<<"guest">>}, + {vhost,<<"/">>}, + {configure,<<".*">>}, + {write,<<".*">>}, + {read,<<".*">>}], + Permission = http_get("/permissions/myvhost/myuser"), + assert_list([Permission, Default], http_get("/permissions")), + assert_list([Permission], http_get("/users/myuser/permissions")), + http_delete("/permissions/myvhost/myuser", ?NO_CONTENT), + http_get("/permissions/myvhost/myuser", ?NOT_FOUND), + + http_delete("/users/myuser", ?NO_CONTENT), + http_delete("/vhosts/myvhost", ?NO_CONTENT), + ok. + +connections_test() -> + {ok, Conn} = amqp_connection:start(#amqp_params_network{}), + LocalPort = local_port(Conn), + Path = binary_to_list( + rabbit_mgmt_format:print( + "/connections/127.0.0.1%3A~w%20->%20127.0.0.1%3A5672", + [LocalPort])), + http_get(Path, ?OK), + http_delete(Path, ?NO_CONTENT), + %% TODO rabbit_reader:shutdown/2 returns before the connection is + %% closed. It may not be worth fixing. + timer:sleep(200), + http_get(Path, ?NOT_FOUND). + +test_auth(Code, Headers) -> + {ok, {{_, Code, _}, _, _}} = req(get, "/overview", Headers). + +exchanges_test() -> + %% Can pass booleans or strings + Good = [{type, <<"direct">>}, {durable, <<"true">>}], + http_put("/vhosts/myvhost", none, ?NO_CONTENT), + http_get("/exchanges/myvhost/foo", ?NOT_AUTHORISED), + http_put("/exchanges/myvhost/foo", Good, ?NOT_AUTHORISED), + http_put("/permissions/myvhost/guest", + [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}], + ?NO_CONTENT), + http_get("/exchanges/myvhost/foo", ?NOT_FOUND), + http_put("/exchanges/myvhost/foo", Good, ?NO_CONTENT), + http_put("/exchanges/myvhost/foo", Good, ?NO_CONTENT), + http_get("/exchanges/%2f/foo", ?NOT_FOUND), + assert_item([{name,<<"foo">>}, + {vhost,<<"myvhost">>}, + {type,<<"direct">>}, + {durable,true}, + {auto_delete,false}, + {internal,false}, + {arguments,[]}], + http_get("/exchanges/myvhost/foo")), + + http_put("/exchanges/badvhost/bar", Good, ?NOT_FOUND), + http_put("/exchanges/myvhost/bar", [{type, <<"bad_exchange_type">>}], + ?BAD_REQUEST), + http_put("/exchanges/myvhost/bar", [{type, <<"direct">>}, + {durable, <<"troo">>}], + ?BAD_REQUEST), + http_put("/exchanges/myvhost/foo", [{type, <<"direct">>}], + ?BAD_REQUEST), + + http_delete("/exchanges/myvhost/foo", ?NO_CONTENT), + http_delete("/exchanges/myvhost/foo", ?NOT_FOUND), + + http_delete("/vhosts/myvhost", ?NO_CONTENT), + http_get("/exchanges/badvhost", ?NOT_FOUND), + ok. + +queues_test() -> + Good = [{durable, true}], + http_get("/queues/%2f/foo", ?NOT_FOUND), + http_put("/queues/%2f/foo", Good, ?NO_CONTENT), + http_put("/queues/%2f/foo", Good, ?NO_CONTENT), + http_get("/queues/%2f/foo", ?OK), + + http_put("/queues/badvhost/bar", Good, ?NOT_FOUND), + http_put("/queues/%2f/bar", + [{durable, <<"troo">>}], + ?BAD_REQUEST), + http_put("/queues/%2f/foo", + [{durable, false}], + ?BAD_REQUEST), + + http_put("/queues/%2f/baz", Good, ?NO_CONTENT), + + Queues = http_get("/queues/%2f"), + Queue = http_get("/queues/%2f/foo"), + assert_list([[{name, <<"foo">>}, + {vhost, <<"/">>}, + {durable, true}, + {auto_delete, false}, + {arguments, []}], + [{name, <<"baz">>}, + {vhost, <<"/">>}, + {durable, true}, + {auto_delete, false}, + {arguments, []}]], Queues), + assert_item([{name, <<"foo">>}, + {vhost, <<"/">>}, + {durable, true}, + {auto_delete, false}, + {arguments, []}], Queue), + + http_delete("/queues/%2f/foo", ?NO_CONTENT), + http_delete("/queues/%2f/baz", ?NO_CONTENT), + http_delete("/queues/%2f/foo", ?NOT_FOUND), + http_get("/queues/badvhost", ?NOT_FOUND), + ok. + +bindings_test() -> + XArgs = [{type, <<"direct">>}], + QArgs = [], + http_put("/exchanges/%2f/myexchange", XArgs, ?NO_CONTENT), + http_put("/queues/%2f/myqueue", QArgs, ?NO_CONTENT), + BArgs = [{routing_key, <<"routing">>}, {arguments, []}], + http_post("/bindings/%2f/e/myexchange/q/myqueue", BArgs, ?CREATED), + http_get("/bindings/%2f/e/myexchange/q/myqueue/routing", ?OK), + http_get("/bindings/%2f/e/myexchange/q/myqueue/rooting", ?NOT_FOUND), + Binding = + [{source,<<"myexchange">>}, + {vhost,<<"/">>}, + {destination,<<"myqueue">>}, + {destination_type,<<"queue">>}, + {routing_key,<<"routing">>}, + {arguments,[]}, + {properties_key,<<"routing">>}], + DBinding = + [{source,<<"">>}, + {vhost,<<"/">>}, + {destination,<<"myqueue">>}, + {destination_type,<<"queue">>}, + {routing_key,<<"myqueue">>}, + {arguments,[]}, + {properties_key,<<"myqueue">>}], + Binding = http_get("/bindings/%2f/e/myexchange/q/myqueue/routing"), + assert_list([Binding], + http_get("/bindings/%2f/e/myexchange/q/myqueue")), + assert_list([Binding, DBinding], + http_get("/queues/%2f/myqueue/bindings")), + assert_list([Binding], + http_get("/exchanges/%2f/myexchange/bindings/source")), + http_delete("/bindings/%2f/e/myexchange/q/myqueue/routing", ?NO_CONTENT), + http_delete("/bindings/%2f/e/myexchange/q/myqueue/routing", ?NOT_FOUND), + http_delete("/exchanges/%2f/myexchange", ?NO_CONTENT), + http_delete("/queues/%2f/myqueue", ?NO_CONTENT), + http_get("/bindings/badvhost", ?NOT_FOUND), + http_get("/bindings/badvhost/myqueue/myexchange/routing", ?NOT_FOUND), + http_get("/bindings/%2f/e/myexchange/q/myqueue/routing", ?NOT_FOUND), + ok. + +bindings_post_test() -> + XArgs = [{type, <<"direct">>}], + QArgs = [], + BArgs = [{routing_key, <<"routing">>}, {arguments, [{foo, <<"bar">>}]}], + http_put("/exchanges/%2f/myexchange", XArgs, ?NO_CONTENT), + http_put("/queues/%2f/myqueue", QArgs, ?NO_CONTENT), + http_post("/bindings/%2f/e/myexchange/q/badqueue", BArgs, ?NOT_FOUND), + http_post("/bindings/%2f/e/badexchange/q/myqueue", BArgs, ?NOT_FOUND), + Headers1 = http_post("/bindings/%2f/e/myexchange/q/myqueue", [], ?CREATED), + "../../../../%2F/e/myexchange/q/myqueue/~" = pget("location", Headers1), + Headers2 = http_post("/bindings/%2f/e/myexchange/q/myqueue", BArgs, ?CREATED), + PropertiesKey = "routing~V4mGFgnPNrdtRmluZIxTDA", + PropertiesKeyBin = list_to_binary(PropertiesKey), + "../../../../%2F/e/myexchange/q/myqueue/" ++ PropertiesKey = + pget("location", Headers2), + URI = "/bindings/%2F/e/myexchange/q/myqueue/" ++ PropertiesKey, + [{source,<<"myexchange">>}, + {vhost,<<"/">>}, + {destination,<<"myqueue">>}, + {destination_type,<<"queue">>}, + {routing_key,<<"routing">>}, + {arguments,[{foo,<<"bar">>}]}, + {properties_key,PropertiesKeyBin}] = http_get(URI, ?OK), + http_get(URI ++ "x", ?NOT_FOUND), + http_delete(URI, ?NO_CONTENT), + http_delete("/exchanges/%2f/myexchange", ?NO_CONTENT), + http_delete("/queues/%2f/myqueue", ?NO_CONTENT), + ok. + +bindings_e2e_test() -> + BArgs = [{routing_key, <<"routing">>}, {arguments, []}], + http_post("/bindings/%2f/e/amq.direct/e/badexchange", BArgs, ?NOT_FOUND), + http_post("/bindings/%2f/e/badexchange/e/amq.fanout", BArgs, ?NOT_FOUND), + Headers = http_post("/bindings/%2f/e/amq.direct/e/amq.fanout", BArgs, ?CREATED), + "../../../../%2F/e/amq.direct/e/amq.fanout/routing" = + pget("location", Headers), + [{source,<<"amq.direct">>}, + {vhost,<<"/">>}, + {destination,<<"amq.fanout">>}, + {destination_type,<<"exchange">>}, + {routing_key,<<"routing">>}, + {arguments,[]}, + {properties_key,<<"routing">>}] = + http_get("/bindings/%2f/e/amq.direct/e/amq.fanout/routing", ?OK), + http_delete("/bindings/%2f/e/amq.direct/e/amq.fanout/routing", ?NO_CONTENT), + http_post("/bindings/%2f/e/amq.direct/e/amq.headers", BArgs, ?CREATED), + Binding = + [{source,<<"amq.direct">>}, + {vhost,<<"/">>}, + {destination,<<"amq.headers">>}, + {destination_type,<<"exchange">>}, + {routing_key,<<"routing">>}, + {arguments,[]}, + {properties_key,<<"routing">>}], + Binding = http_get("/bindings/%2f/e/amq.direct/e/amq.headers/routing"), + assert_list([Binding], + http_get("/bindings/%2f/e/amq.direct/e/amq.headers")), + assert_list([Binding], + http_get("/exchanges/%2f/amq.direct/bindings/source")), + assert_list([Binding], + http_get("/exchanges/%2f/amq.headers/bindings/destination")), + http_delete("/bindings/%2f/e/amq.direct/e/amq.headers/routing", ?NO_CONTENT), + http_get("/bindings/%2f/e/amq.direct/e/amq.headers/rooting", ?NOT_FOUND), + ok. + +permissions_administrator_test() -> + http_put("/users/isadmin", [{password, <<"isadmin">>}, + {tags, <<"administrator">>}], ?NO_CONTENT), + http_put("/users/notadmin", [{password, <<"notadmin">>}, + {tags, <<"administrator">>}], ?NO_CONTENT), + http_put("/users/notadmin", [{password, <<"notadmin">>}, + {tags, <<"management">>}], ?NO_CONTENT), + Test = + fun(Path) -> + http_get(Path, "notadmin", "notadmin", ?NOT_AUTHORISED), + http_get(Path, "isadmin", "isadmin", ?OK), + http_get(Path, "guest", "guest", ?OK) + end, + %% All users can get a list of vhosts. It may be filtered. + %%Test("/vhosts"), + Test("/vhosts/%2f"), + Test("/vhosts/%2f/permissions"), + Test("/users"), + Test("/users/guest"), + Test("/users/guest/permissions"), + Test("/permissions"), + Test("/permissions/%2f/guest"), + http_delete("/users/notadmin", ?NO_CONTENT), + http_delete("/users/isadmin", ?NO_CONTENT), + ok. + +permissions_vhost_test() -> + QArgs = [], + PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}], + http_put("/users/myuser", [{password, <<"myuser">>}, + {tags, <<"management">>}], ?NO_CONTENT), + http_put("/vhosts/myvhost1", none, ?NO_CONTENT), + http_put("/vhosts/myvhost2", none, ?NO_CONTENT), + http_put("/permissions/myvhost1/myuser", PermArgs, ?NO_CONTENT), + http_put("/permissions/myvhost1/guest", PermArgs, ?NO_CONTENT), + http_put("/permissions/myvhost2/guest", PermArgs, ?NO_CONTENT), + assert_list([[{name, <<"/">>}], + [{name, <<"myvhost1">>}], + [{name, <<"myvhost2">>}]], http_get("/vhosts", ?OK)), + assert_list([[{name, <<"myvhost1">>}]], + http_get("/vhosts", "myuser", "myuser", ?OK)), + http_put("/queues/myvhost1/myqueue", QArgs, ?NO_CONTENT), + http_put("/queues/myvhost2/myqueue", QArgs, ?NO_CONTENT), + Test1 = + fun(Path) -> + Results = http_get(Path, "myuser", "myuser", ?OK), + [case pget(vhost, Result) of + <<"myvhost2">> -> + throw({got_result_from_vhost2_in, Path, Result}); + _ -> + ok + end || Result <- Results] + end, + Test2 = + fun(Path1, Path2) -> + http_get(Path1 ++ "/myvhost1/" ++ Path2, "myuser", "myuser", + ?OK), + http_get(Path1 ++ "/myvhost2/" ++ Path2, "myuser", "myuser", + ?NOT_AUTHORISED) + end, + Test1("/exchanges"), + Test2("/exchanges", ""), + Test2("/exchanges", "amq.direct"), + Test1("/queues"), + Test2("/queues", ""), + Test2("/queues", "myqueue"), + Test1("/bindings"), + Test2("/bindings", ""), + Test2("/queues", "myqueue/bindings"), + Test2("/exchanges", "amq.default/bindings/source"), + Test2("/exchanges", "amq.default/bindings/destination"), + Test2("/bindings", "e/amq.default/q/myqueue"), + Test2("/bindings", "e/amq.default/q/myqueue/myqueue"), + http_delete("/vhosts/myvhost1", ?NO_CONTENT), + http_delete("/vhosts/myvhost2", ?NO_CONTENT), + http_delete("/users/myuser", ?NO_CONTENT), + ok. + +permissions_amqp_test() -> + %% Just test that it works at all, not that it works in all possible cases. + QArgs = [], + PermArgs = [{configure, <<"foo.*">>}, {write, <<"foo.*">>}, + {read, <<"foo.*">>}], + http_put("/users/myuser", [{password, <<"myuser">>}, + {tags, <<"management">>}], ?NO_CONTENT), + http_put("/permissions/%2f/myuser", PermArgs, ?NO_CONTENT), + http_put("/queues/%2f/bar-queue", QArgs, "myuser", "myuser", + ?NOT_AUTHORISED), + http_put("/queues/%2f/bar-queue", QArgs, "nonexistent", "nonexistent", + ?NOT_AUTHORISED), + http_delete("/users/myuser", ?NO_CONTENT), + ok. + +get_conn(Username, Password) -> + {ok, Conn} = amqp_connection:start(#amqp_params_network{ + username = list_to_binary(Username), + password = list_to_binary(Password)}), + LocalPort = local_port(Conn), + ConnPath = rabbit_misc:format( + "/connections/127.0.0.1%3A~w%20->%20127.0.0.1%3A5672", + [LocalPort]), + ChPath = rabbit_misc:format( + "/channels/127.0.0.1%3A~w%20->%20127.0.0.1%3A5672%20(1)", + [LocalPort]), + ConnChPath = rabbit_misc:format( + "/connections/127.0.0.1%3A~w%20->%20127.0.0.1%3A5672/channels", + [LocalPort]), + {Conn, ConnPath, ChPath, ConnChPath}. + +permissions_connection_channel_test() -> + PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}], + http_put("/users/user", [{password, <<"user">>}, + {tags, <<"management">>}], ?NO_CONTENT), + http_put("/permissions/%2f/user", PermArgs, ?NO_CONTENT), + http_put("/users/monitor", [{password, <<"monitor">>}, + {tags, <<"monitoring">>}], ?NO_CONTENT), + http_put("/permissions/%2f/monitor", PermArgs, ?NO_CONTENT), + {Conn1, UserConn, UserCh, UserConnCh} = get_conn("user", "user"), + {Conn2, MonConn, MonCh, MonConnCh} = get_conn("monitor", "monitor"), + {Conn3, AdmConn, AdmCh, AdmConnCh} = get_conn("guest", "guest"), + {ok, _Ch1} = amqp_connection:open_channel(Conn1), + {ok, _Ch2} = amqp_connection:open_channel(Conn2), + {ok, _Ch3} = amqp_connection:open_channel(Conn3), + + AssertLength = fun (Path, User, Len) -> + ?assertEqual(Len, + length(http_get(Path, User, User, ?OK))) + end, + [begin + AssertLength(P, "user", 1), + AssertLength(P, "monitor", 3), + AssertLength(P, "guest", 3) + end || P <- ["/connections", "/channels"]], + + AssertRead = fun(Path, UserStatus) -> + http_get(Path, "user", "user", UserStatus), + http_get(Path, "monitor", "monitor", ?OK), + http_get(Path, ?OK) + end, + AssertRead(UserConn, ?OK), + AssertRead(MonConn, ?NOT_AUTHORISED), + AssertRead(AdmConn, ?NOT_AUTHORISED), + AssertRead(UserCh, ?OK), + AssertRead(MonCh, ?NOT_AUTHORISED), + AssertRead(AdmCh, ?NOT_AUTHORISED), + AssertRead(UserConnCh, ?OK), + AssertRead(MonConnCh, ?NOT_AUTHORISED), + AssertRead(AdmConnCh, ?NOT_AUTHORISED), + + AssertClose = fun(Path, User, Status) -> + http_delete(Path, User, User, Status) + end, + AssertClose(UserConn, "monitor", ?NOT_AUTHORISED), + AssertClose(MonConn, "user", ?NOT_AUTHORISED), + AssertClose(AdmConn, "guest", ?NO_CONTENT), + AssertClose(MonConn, "guest", ?NO_CONTENT), + AssertClose(UserConn, "user", ?NO_CONTENT), + + http_delete("/users/user", ?NO_CONTENT), + http_delete("/users/monitor", ?NO_CONTENT), + http_get("/connections/foo", ?NOT_FOUND), + http_get("/channels/foo", ?NOT_FOUND), + ok. + +defs(Key, URI, CreateMethod, Args) -> + defs(Key, URI, CreateMethod, Args, + fun(URI2) -> http_delete(URI2, ?NO_CONTENT) end). + +defs_v(Key, URI, CreateMethod, Args) -> + Rep1 = fun (S, S2) -> re:replace(S, "", S2, [{return, list}]) end, + Rep2 = fun (L, V2) -> lists:keymap(fun (vhost) -> V2; + (V) -> V end, 2, L) end, + %% Test against default vhost + defs(Key, Rep1(URI, "%2f"), CreateMethod, Rep2(Args, <<"/">>)), + + %% Test against new vhost + http_put("/vhosts/test", none, ?NO_CONTENT), + PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}], + http_put("/permissions/test/guest", PermArgs, ?NO_CONTENT), + defs(Key, Rep1(URI, "test"), CreateMethod, Rep2(Args, <<"test">>), + fun(URI2) -> http_delete(URI2, ?NO_CONTENT), + http_delete("/vhosts/test", ?NO_CONTENT) end). + +defs(Key, URI, CreateMethod, Args, DeleteFun) -> + %% Create the item + URI2 = case CreateMethod of + put -> http_put(URI, Args, ?NO_CONTENT), + URI; + post -> Headers = http_post(URI, Args, ?CREATED), + rabbit_web_dispatch_util:unrelativise( + URI, pget("location", Headers)) + end, + %% Make sure it ends up in definitions + Definitions = http_get("/definitions", ?OK), + true = lists:any(fun(I) -> test_item(Args, I) end, pget(Key, Definitions)), + + %% Delete it + DeleteFun(URI2), + + %% Post the definitions back, it should get recreated in correct form + http_post("/definitions", Definitions, ?CREATED), + assert_item(Args, http_get(URI2, ?OK)), + + %% And delete it again + DeleteFun(URI2), + + ok. + +definitions_test() -> + rabbit_runtime_parameters_test:register(), + rabbit_runtime_parameters_test:register_policy_validator(), + + defs_v(queues, "/queues//my-queue", put, + [{name, <<"my-queue">>}, + {durable, true}]), + defs_v(exchanges, "/exchanges//my-exchange", put, + [{name, <<"my-exchange">>}, + {type, <<"direct">>}]), + defs_v(bindings, "/bindings//e/amq.direct/e/amq.fanout", post, + [{routing_key, <<"routing">>}, {arguments, []}]), + defs_v(policies, "/policies//my-policy", put, + [{vhost, vhost}, + {name, <<"my-policy">>}, + {pattern, <<".*">>}, + {definition, [{testpos, [1, 2, 3]}]}, + {priority, 1}]), + defs_v(parameters, "/parameters/test//good", put, + [{vhost, vhost}, + {component, <<"test">>}, + {name, <<"good">>}, + {value, <<"ignore">>}]), + defs(users, "/users/myuser", put, + [{name, <<"myuser">>}, + {password_hash, <<"WAbU0ZIcvjTpxM3Q3SbJhEAM2tQ=">>}, + {tags, <<"management">>}]), + defs(vhosts, "/vhosts/myvhost", put, + [{name, <<"myvhost">>}]), + defs(permissions, "/permissions/%2f/guest", put, + [{user, <<"guest">>}, + {vhost, <<"/">>}, + {configure, <<"c">>}, + {write, <<"w">>}, + {read, <<"r">>}]), + + %% We just messed with guest's permissions + http_put("/permissions/%2f/guest", + [{configure, <<".*">>}, + {write, <<".*">>}, + {read, <<".*">>}], ?NO_CONTENT), + + BrokenConfig = + [{users, []}, + {vhosts, []}, + {permissions, []}, + {queues, []}, + {exchanges, [[{name, <<"amq.direct">>}, + {vhost, <<"/">>}, + {type, <<"definitely not direct">>}, + {durable, true}, + {auto_delete, false}, + {arguments, []} + ]]}, + {bindings, []}], + http_post("/definitions", BrokenConfig, ?BAD_REQUEST), + + rabbit_runtime_parameters_test:unregister_policy_validator(), + rabbit_runtime_parameters_test:unregister(), + ok. + +definitions_remove_things_test() -> + {ok, Conn} = amqp_connection:start(#amqp_params_network{}), + {ok, Ch} = amqp_connection:open_channel(Conn), + amqp_channel:call(Ch, #'queue.declare'{ queue = <<"my-exclusive">>, + exclusive = true }), + http_get("/queues/%2f/my-exclusive", ?OK), + Definitions = http_get("/definitions", ?OK), + [] = pget(queues, Definitions), + [] = pget(exchanges, Definitions), + [] = pget(bindings, Definitions), + amqp_channel:close(Ch), + amqp_connection:close(Conn), + ok. + +definitions_server_named_queue_test() -> + {ok, Conn} = amqp_connection:start(#amqp_params_network{}), + {ok, Ch} = amqp_connection:open_channel(Conn), + #'queue.declare_ok'{ queue = QName } = + amqp_channel:call(Ch, #'queue.declare'{}), + amqp_channel:close(Ch), + amqp_connection:close(Conn), + Path = "/queues/%2f/" ++ mochiweb_util:quote_plus(QName), + http_get(Path, ?OK), + Definitions = http_get("/definitions", ?OK), + http_delete(Path, ?NO_CONTENT), + http_get(Path, ?NOT_FOUND), + http_post("/definitions", Definitions, ?CREATED), + http_get(Path, ?OK), + http_delete(Path, ?NO_CONTENT), + ok. + +aliveness_test() -> + [{status, <<"ok">>}] = http_get("/aliveness-test/%2f", ?OK), + http_get("/aliveness-test/foo", ?NOT_FOUND), + http_delete("/queues/%2f/aliveness-test", ?NO_CONTENT), + ok. + +arguments_test() -> + XArgs = [{type, <<"headers">>}, + {arguments, [{'alternate-exchange', <<"amq.direct">>}]}], + QArgs = [{arguments, [{'x-expires', 1800000}]}], + BArgs = [{routing_key, <<"">>}, + {arguments, [{'x-match', <<"all">>}, + {foo, <<"bar">>}]}], + http_put("/exchanges/%2f/myexchange", XArgs, ?NO_CONTENT), + http_put("/queues/%2f/myqueue", QArgs, ?NO_CONTENT), + http_post("/bindings/%2f/e/myexchange/q/myqueue", BArgs, ?CREATED), + Definitions = http_get("/definitions", ?OK), + http_delete("/exchanges/%2f/myexchange", ?NO_CONTENT), + http_delete("/queues/%2f/myqueue", ?NO_CONTENT), + http_post("/definitions", Definitions, ?CREATED), + [{'alternate-exchange', <<"amq.direct">>}] = + pget(arguments, http_get("/exchanges/%2f/myexchange", ?OK)), + [{'x-expires', 1800000}] = + pget(arguments, http_get("/queues/%2f/myqueue", ?OK)), + true = lists:sort([{'x-match', <<"all">>}, {foo, <<"bar">>}]) =:= + lists:sort(pget(arguments, + http_get("/bindings/%2f/e/myexchange/q/myqueue/" ++ + "~nXOkVwqZzUOdS9_HcBWheg", ?OK))), + http_delete("/exchanges/%2f/myexchange", ?NO_CONTENT), + http_delete("/queues/%2f/myqueue", ?NO_CONTENT), + ok. + +arguments_table_test() -> + Args = [{'upstreams', [<<"amqp://localhost/%2f/upstream1">>, + <<"amqp://localhost/%2f/upstream2">>]}], + XArgs = [{type, <<"headers">>}, + {arguments, Args}], + http_put("/exchanges/%2f/myexchange", XArgs, ?NO_CONTENT), + Definitions = http_get("/definitions", ?OK), + http_delete("/exchanges/%2f/myexchange", ?NO_CONTENT), + http_post("/definitions", Definitions, ?CREATED), + Args = pget(arguments, http_get("/exchanges/%2f/myexchange", ?OK)), + http_delete("/exchanges/%2f/myexchange", ?NO_CONTENT), + ok. + +queue_purge_test() -> + QArgs = [], + http_put("/queues/%2f/myqueue", QArgs, ?NO_CONTENT), + {ok, Conn} = amqp_connection:start(#amqp_params_network{}), + {ok, Ch} = amqp_connection:open_channel(Conn), + Publish = fun() -> + amqp_channel:call( + Ch, #'basic.publish'{exchange = <<"">>, + routing_key = <<"myqueue">>}, + #amqp_msg{payload = <<"message">>}) + end, + Publish(), + Publish(), + amqp_channel:call( + Ch, #'queue.declare'{queue = <<"exclusive">>, exclusive = true}), + {#'basic.get_ok'{}, _} = + amqp_channel:call(Ch, #'basic.get'{queue = <<"myqueue">>}), + http_delete("/queues/%2f/myqueue/contents", ?NO_CONTENT), + http_delete("/queues/%2f/badqueue/contents", ?NOT_FOUND), + http_delete("/queues/%2f/exclusive/contents", ?BAD_REQUEST), + http_delete("/queues/%2f/exclusive", ?BAD_REQUEST), + #'basic.get_empty'{} = + amqp_channel:call(Ch, #'basic.get'{queue = <<"myqueue">>}), + amqp_channel:close(Ch), + amqp_connection:close(Conn), + http_delete("/queues/%2f/myqueue", ?NO_CONTENT), + ok. + +queue_actions_test() -> + http_put("/queues/%2f/q", [], ?NO_CONTENT), + http_post("/queues/%2f/q/actions", [{action, sync}], ?NO_CONTENT), + http_post("/queues/%2f/q/actions", [{action, cancel_sync}], ?NO_CONTENT), + http_post("/queues/%2f/q/actions", [{action, change_colour}], ?BAD_REQUEST), + http_delete("/queues/%2f/q", ?NO_CONTENT), + ok. + +exclusive_consumer_test() -> + {ok, Conn} = amqp_connection:start(#amqp_params_network{}), + {ok, Ch} = amqp_connection:open_channel(Conn), + #'queue.declare_ok'{ queue = QName } = + amqp_channel:call(Ch, #'queue.declare'{exclusive = true}), + amqp_channel:subscribe(Ch, #'basic.consume'{queue = QName, + exclusive = true}, self()), + timer:sleep(1000), %% Sadly we need to sleep to let the stats update + http_get("/queues/%2f/"), %% Just check we don't blow up + amqp_channel:close(Ch), + amqp_connection:close(Conn), + ok. + +sorting_test() -> + QArgs = [], + PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}], + http_put("/vhosts/vh1", none, ?NO_CONTENT), + http_put("/permissions/vh1/guest", PermArgs, ?NO_CONTENT), + http_put("/queues/%2f/test0", QArgs, ?NO_CONTENT), + http_put("/queues/vh1/test1", QArgs, ?NO_CONTENT), + http_put("/queues/%2f/test2", QArgs, ?NO_CONTENT), + http_put("/queues/vh1/test3", QArgs, ?NO_CONTENT), + assert_list([[{name, <<"test0">>}], + [{name, <<"test2">>}], + [{name, <<"test1">>}], + [{name, <<"test3">>}]], http_get("/queues", ?OK)), + assert_list([[{name, <<"test0">>}], + [{name, <<"test1">>}], + [{name, <<"test2">>}], + [{name, <<"test3">>}]], http_get("/queues?sort=name", ?OK)), + assert_list([[{name, <<"test0">>}], + [{name, <<"test2">>}], + [{name, <<"test1">>}], + [{name, <<"test3">>}]], http_get("/queues?sort=vhost", ?OK)), + assert_list([[{name, <<"test3">>}], + [{name, <<"test1">>}], + [{name, <<"test2">>}], + [{name, <<"test0">>}]], http_get("/queues?sort_reverse=true", ?OK)), + assert_list([[{name, <<"test3">>}], + [{name, <<"test2">>}], + [{name, <<"test1">>}], + [{name, <<"test0">>}]], http_get("/queues?sort=name&sort_reverse=true", ?OK)), + assert_list([[{name, <<"test3">>}], + [{name, <<"test1">>}], + [{name, <<"test2">>}], + [{name, <<"test0">>}]], http_get("/queues?sort=vhost&sort_reverse=true", ?OK)), + %% Rather poor but at least test it doesn't blow up with dots + http_get("/queues?sort=owner_pid_details.name", ?OK), + http_delete("/queues/%2f/test0", ?NO_CONTENT), + http_delete("/queues/vh1/test1", ?NO_CONTENT), + http_delete("/queues/%2f/test2", ?NO_CONTENT), + http_delete("/queues/vh1/test3", ?NO_CONTENT), + http_delete("/vhosts/vh1", ?NO_CONTENT), + ok. + +columns_test() -> + http_put("/queues/%2f/test", [{arguments, [{<<"foo">>, <<"bar">>}]}], + ?NO_CONTENT), + [[{name, <<"test">>}, {arguments, [{foo, <<"bar">>}]}]] = + http_get("/queues?columns=arguments.foo,name", ?OK), + [{name, <<"test">>}, {arguments, [{foo, <<"bar">>}]}] = + http_get("/queues/%2f/test?columns=arguments.foo,name", ?OK), + http_delete("/queues/%2f/test", ?NO_CONTENT), + ok. + +get_test() -> + %% Real world example... + Headers = [{<<"x-forwarding">>, array, + [{table, + [{<<"uri">>, longstr, + <<"amqp://localhost/%2f/upstream">>}]}]}], + http_put("/queues/%2f/myqueue", [], ?NO_CONTENT), + {ok, Conn} = amqp_connection:start(#amqp_params_network{}), + {ok, Ch} = amqp_connection:open_channel(Conn), + Publish = fun (Payload) -> + amqp_channel:cast( + Ch, #'basic.publish'{exchange = <<>>, + routing_key = <<"myqueue">>}, + #amqp_msg{props = #'P_basic'{headers = Headers}, + payload = Payload}) + end, + Publish(<<"1aaa">>), + Publish(<<"2aaa">>), + Publish(<<"3aaa">>), + amqp_connection:close(Conn), + [Msg] = http_post("/queues/%2f/myqueue/get", [{requeue, false}, + {count, 1}, + {encoding, auto}, + {truncate, 1}], ?OK), + false = pget(redelivered, Msg), + <<>> = pget(exchange, Msg), + <<"myqueue">> = pget(routing_key, Msg), + <<"1">> = pget(payload, Msg), + [{'x-forwarding', + [[{uri,<<"amqp://localhost/%2f/upstream">>}]]}] = + pget(headers, pget(properties, Msg)), + + [M2, M3] = http_post("/queues/%2f/myqueue/get", [{requeue, true}, + {count, 5}, + {encoding, auto}], ?OK), + <<"2aaa">> = pget(payload, M2), + <<"3aaa">> = pget(payload, M3), + 2 = length(http_post("/queues/%2f/myqueue/get", [{requeue, false}, + {count, 5}, + {encoding, auto}], ?OK)), + [] = http_post("/queues/%2f/myqueue/get", [{requeue, false}, + {count, 5}, + {encoding, auto}], ?OK), + http_delete("/queues/%2f/myqueue", ?NO_CONTENT), + ok. + +get_fail_test() -> + http_put("/users/myuser", [{password, <<"password">>}, + {tags, <<"management">>}], ?NO_CONTENT), + http_put("/queues/%2f/myqueue", [], ?NO_CONTENT), + http_post("/queues/%2f/myqueue/get", + [{requeue, false}, + {count, 1}, + {encoding, auto}], "myuser", "password", ?NOT_AUTHORISED), + http_delete("/queues/%2f/myqueue", ?NO_CONTENT), + http_delete("/users/myuser", ?NO_CONTENT), + ok. + +publish_test() -> + Headers = [{'x-forwarding', [[{uri,<<"amqp://localhost/%2f/upstream">>}]]}], + Msg = msg(<<"myqueue">>, Headers, <<"Hello world">>), + http_put("/queues/%2f/myqueue", [], ?NO_CONTENT), + ?assertEqual([{routed, true}], + http_post("/exchanges/%2f/amq.default/publish", Msg, ?OK)), + [Msg2] = http_post("/queues/%2f/myqueue/get", [{requeue, false}, + {count, 1}, + {encoding, auto}], ?OK), + assert_item(Msg, Msg2), + http_post("/exchanges/%2f/amq.default/publish", Msg2, ?OK), + [Msg3] = http_post("/queues/%2f/myqueue/get", [{requeue, false}, + {count, 1}, + {encoding, auto}], ?OK), + assert_item(Msg, Msg3), + http_delete("/queues/%2f/myqueue", ?NO_CONTENT), + ok. + +publish_fail_test() -> + Msg = msg(<<"myqueue">>, [], <<"Hello world">>), + http_put("/queues/%2f/myqueue", [], ?NO_CONTENT), + http_put("/users/myuser", [{password, <<"password">>}, + {tags, <<"management">>}], ?NO_CONTENT), + http_post("/exchanges/%2f/amq.default/publish", Msg, "myuser", "password", + ?NOT_AUTHORISED), + Msg2 = [{exchange, <<"">>}, + {routing_key, <<"myqueue">>}, + {properties, [{user_id, <<"foo">>}]}, + {payload, <<"Hello world">>}, + {payload_encoding, <<"string">>}], + http_post("/exchanges/%2f/amq.default/publish", Msg2, ?BAD_REQUEST), + Msg3 = [{exchange, <<"">>}, + {routing_key, <<"myqueue">>}, + {properties, []}, + {payload, [<<"not a string">>]}, + {payload_encoding, <<"string">>}], + http_post("/exchanges/%2f/amq.default/publish", Msg3, ?BAD_REQUEST), + MsgTemplate = [{exchange, <<"">>}, + {routing_key, <<"myqueue">>}, + {payload, <<"Hello world">>}, + {payload_encoding, <<"string">>}], + [http_post("/exchanges/%2f/amq.default/publish", + [{properties, [BadProp]} | MsgTemplate], ?BAD_REQUEST) + || BadProp <- [{priority, <<"really high">>}, + {timestamp, <<"recently">>}, + {expiration, 1234}]], + http_delete("/users/myuser", ?NO_CONTENT), + ok. + +publish_base64_test() -> + Msg = msg(<<"myqueue">>, [], <<"YWJjZA==">>, <<"base64">>), + BadMsg1 = msg(<<"myqueue">>, [], <<"flibble">>, <<"base64">>), + BadMsg2 = msg(<<"myqueue">>, [], <<"YWJjZA==">>, <<"base99">>), + http_put("/queues/%2f/myqueue", [], ?NO_CONTENT), + http_post("/exchanges/%2f/amq.default/publish", Msg, ?OK), + http_post("/exchanges/%2f/amq.default/publish", BadMsg1, ?BAD_REQUEST), + http_post("/exchanges/%2f/amq.default/publish", BadMsg2, ?BAD_REQUEST), + [Msg2] = http_post("/queues/%2f/myqueue/get", [{requeue, false}, + {count, 1}, + {encoding, auto}], ?OK), + ?assertEqual(<<"abcd">>, pget(payload, Msg2)), + http_delete("/queues/%2f/myqueue", ?NO_CONTENT), + ok. + +publish_unrouted_test() -> + Msg = msg(<<"hmmm">>, [], <<"Hello world">>), + ?assertEqual([{routed, false}], + http_post("/exchanges/%2f/amq.default/publish", Msg, ?OK)). + +parameters_test() -> + rabbit_runtime_parameters_test:register(), + + http_put("/parameters/test/%2f/good", [{value, <<"ignore">>}], ?NO_CONTENT), + http_put("/parameters/test/%2f/maybe", [{value, <<"good">>}], ?NO_CONTENT), + http_put("/parameters/test/%2f/maybe", [{value, <<"bad">>}], ?BAD_REQUEST), + http_put("/parameters/test/%2f/bad", [{value, <<"good">>}], ?BAD_REQUEST), + http_put("/parameters/test/um/good", [{value, <<"ignore">>}], ?NOT_FOUND), + + Good = [{vhost, <<"/">>}, + {component, <<"test">>}, + {name, <<"good">>}, + {value, <<"ignore">>}], + Maybe = [{vhost, <<"/">>}, + {component, <<"test">>}, + {name, <<"maybe">>}, + {value, <<"good">>}], + List = [Good, Maybe], + + assert_list(List, http_get("/parameters")), + assert_list(List, http_get("/parameters/test")), + assert_list(List, http_get("/parameters/test/%2f")), + assert_list([], http_get("/parameters/oops")), + http_get("/parameters/test/oops", ?NOT_FOUND), + + assert_item(Good, http_get("/parameters/test/%2f/good", ?OK)), + assert_item(Maybe, http_get("/parameters/test/%2f/maybe", ?OK)), + + http_delete("/parameters/test/%2f/good", ?NO_CONTENT), + http_delete("/parameters/test/%2f/maybe", ?NO_CONTENT), + http_delete("/parameters/test/%2f/bad", ?NOT_FOUND), + + 0 = length(http_get("/parameters")), + 0 = length(http_get("/parameters/test")), + 0 = length(http_get("/parameters/test/%2f")), + rabbit_runtime_parameters_test:unregister(), + ok. + +policy_test() -> + rabbit_runtime_parameters_test:register_policy_validator(), + PolicyPos = [{vhost, <<"/">>}, + {name, <<"policy_pos">>}, + {pattern, <<".*">>}, + {definition, [{testpos,[1,2,3]}]}, + {priority, 10}], + PolicyEven = [{vhost, <<"/">>}, + {name, <<"policy_even">>}, + {pattern, <<".*">>}, + {definition, [{testeven,[1,2,3,4]}]}, + {priority, 10}], + http_put( + "/policies/%2f/policy_pos", + lists:keydelete(key, 1, PolicyPos), + ?NO_CONTENT), + http_put( + "/policies/%2f/policy_even", + lists:keydelete(key, 1, PolicyEven), + ?NO_CONTENT), + assert_item(PolicyPos, http_get("/policies/%2f/policy_pos", ?OK)), + assert_item(PolicyEven, http_get("/policies/%2f/policy_even", ?OK)), + List = [PolicyPos, PolicyEven], + assert_list(List, http_get("/policies", ?OK)), + assert_list(List, http_get("/policies/%2f", ?OK)), + + http_delete("/policies/%2f/policy_pos", ?NO_CONTENT), + http_delete("/policies/%2f/policy_even", ?NO_CONTENT), + 0 = length(http_get("/policies")), + 0 = length(http_get("/policies/%2f")), + rabbit_runtime_parameters_test:unregister_policy_validator(), + ok. + +policy_permissions_test() -> + rabbit_runtime_parameters_test:register(), + + http_put("/users/admin", [{password, <<"admin">>}, + {tags, <<"administrator">>}], ?NO_CONTENT), + http_put("/users/mon", [{password, <<"monitor">>}, + {tags, <<"monitoring">>}], ?NO_CONTENT), + http_put("/users/policy", [{password, <<"policy">>}, + {tags, <<"policymaker">>}], ?NO_CONTENT), + http_put("/users/mgmt", [{password, <<"mgmt">>}, + {tags, <<"management">>}], ?NO_CONTENT), + Perms = [{configure, <<".*">>}, + {write, <<".*">>}, + {read, <<".*">>}], + http_put("/vhosts/v", none, ?NO_CONTENT), + http_put("/permissions/v/admin", Perms, ?NO_CONTENT), + http_put("/permissions/v/mon", Perms, ?NO_CONTENT), + http_put("/permissions/v/policy", Perms, ?NO_CONTENT), + http_put("/permissions/v/mgmt", Perms, ?NO_CONTENT), + + Policy = [{pattern, <<".*">>}, + {definition, [{<<"ha-mode">>, <<"all">>}]}], + Param = [{value, <<"">>}], + + http_put("/policies/%2f/HA", Policy, ?NO_CONTENT), + http_put("/parameters/test/%2f/good", Param, ?NO_CONTENT), + + Pos = fun (U) -> + http_put("/policies/v/HA", Policy, U, U, ?NO_CONTENT), + http_put( + "/parameters/test/v/good", Param, U, U, ?NO_CONTENT), + 1 = length(http_get("/policies", U, U, ?OK)), + 1 = length(http_get("/parameters/test", U, U, ?OK)), + 1 = length(http_get("/parameters", U, U, ?OK)), + 1 = length(http_get("/policies/v", U, U, ?OK)), + 1 = length(http_get("/parameters/test/v", U, U, ?OK)), + http_get("/policies/v/HA", U, U, ?OK), + http_get("/parameters/test/v/good", U, U, ?OK) + end, + Neg = fun (U) -> + http_put("/policies/v/HA", Policy, U, U, ?NOT_AUTHORISED), + http_put( + "/parameters/test/v/good", Param, U, U, ?NOT_AUTHORISED), + http_put( + "/parameters/test/v/admin", Param, U, U, ?NOT_AUTHORISED), + http_get("/policies", U, U, ?NOT_AUTHORISED), + http_get("/policies/v", U, U, ?NOT_AUTHORISED), + http_get("/parameters", U, U, ?NOT_AUTHORISED), + http_get("/parameters/test", U, U, ?NOT_AUTHORISED), + http_get("/parameters/test/v", U, U, ?NOT_AUTHORISED), + http_get("/policies/v/HA", U, U, ?NOT_AUTHORISED), + http_get("/parameters/test/v/good", U, U, ?NOT_AUTHORISED) + end, + AlwaysNeg = + fun (U) -> + http_put("/policies/%2f/HA", Policy, U, U, ?NOT_AUTHORISED), + http_put( + "/parameters/test/%2f/good", Param, U, U, ?NOT_AUTHORISED), + http_get("/policies/%2f/HA", U, U, ?NOT_AUTHORISED), + http_get("/parameters/test/%2f/good", U, U, ?NOT_AUTHORISED) + end, + + [Neg(U) || U <- ["mon", "mgmt"]], + [Pos(U) || U <- ["admin", "policy"]], + [AlwaysNeg(U) || U <- ["mon", "mgmt", "admin", "policy"]], + + %% This one is deliberately different between admin and policymaker. + http_put("/parameters/test/v/admin", Param, "admin", "admin", ?NO_CONTENT), + http_put("/parameters/test/v/admin", Param, "policy", "policy", + ?BAD_REQUEST), + + http_delete("/vhosts/v", ?NO_CONTENT), + http_delete("/users/admin", ?NO_CONTENT), + http_delete("/users/mon", ?NO_CONTENT), + http_delete("/users/policy", ?NO_CONTENT), + http_delete("/users/mgmt", ?NO_CONTENT), + http_delete("/policies/%2f/HA", ?NO_CONTENT), + + rabbit_runtime_parameters_test:unregister(), + ok. + + +extensions_test() -> + [[{javascript,<<"dispatcher.js">>}]] = http_get("/extensions", ?OK), + ok. + +%%--------------------------------------------------------------------------- + +msg(Key, Headers, Body) -> + msg(Key, Headers, Body, <<"string">>). + +msg(Key, Headers, Body, Enc) -> + [{exchange, <<"">>}, + {routing_key, Key}, + {properties, [{delivery_mode, 2}, + {headers, Headers}]}, + {payload, Body}, + {payload_encoding, Enc}]. + +local_port(Conn) -> + [{sock, Sock}] = amqp_connection:info(Conn, [sock]), + {ok, Port} = inet:port(Sock), + Port. + +%%--------------------------------------------------------------------------- +http_get(Path) -> + http_get(Path, ?OK). + +http_get(Path, CodeExp) -> + http_get(Path, "guest", "guest", CodeExp). + +http_get(Path, User, Pass, CodeExp) -> + {ok, {{_HTTP, CodeAct, _}, Headers, ResBody}} = + req(get, Path, [auth_header(User, Pass)]), + assert_code(CodeExp, CodeAct, "GET", Path, ResBody), + decode(CodeExp, Headers, ResBody). + +http_put(Path, List, CodeExp) -> + http_put_raw(Path, format_for_upload(List), CodeExp). + +http_put(Path, List, User, Pass, CodeExp) -> + http_put_raw(Path, format_for_upload(List), User, Pass, CodeExp). + +http_post(Path, List, CodeExp) -> + http_post_raw(Path, format_for_upload(List), CodeExp). + +http_post(Path, List, User, Pass, CodeExp) -> + http_post_raw(Path, format_for_upload(List), User, Pass, CodeExp). + +format_for_upload(none) -> + <<"">>; +format_for_upload(List) -> + iolist_to_binary(mochijson2:encode({struct, List})). + +http_put_raw(Path, Body, CodeExp) -> + http_upload_raw(put, Path, Body, "guest", "guest", CodeExp). + +http_put_raw(Path, Body, User, Pass, CodeExp) -> + http_upload_raw(put, Path, Body, User, Pass, CodeExp). + +http_post_raw(Path, Body, CodeExp) -> + http_upload_raw(post, Path, Body, "guest", "guest", CodeExp). + +http_post_raw(Path, Body, User, Pass, CodeExp) -> + http_upload_raw(post, Path, Body, User, Pass, CodeExp). + +http_upload_raw(Type, Path, Body, User, Pass, CodeExp) -> + {ok, {{_HTTP, CodeAct, _}, Headers, ResBody}} = + req(Type, Path, [auth_header(User, Pass)], Body), + assert_code(CodeExp, CodeAct, Type, Path, ResBody), + decode(CodeExp, Headers, ResBody). + +http_delete(Path, CodeExp) -> + http_delete(Path, "guest", "guest", CodeExp). + +http_delete(Path, User, Pass, CodeExp) -> + {ok, {{_HTTP, CodeAct, _}, Headers, ResBody}} = + req(delete, Path, [auth_header(User, Pass)]), + assert_code(CodeExp, CodeAct, "DELETE", Path, ResBody), + decode(CodeExp, Headers, ResBody). + +assert_code(CodeExp, CodeAct, Type, Path, Body) -> + case CodeExp of + CodeAct -> ok; + _ -> throw({expected, CodeExp, got, CodeAct, type, Type, + path, Path, body, Body}) + end. + +req(Type, Path, Headers) -> + httpc:request(Type, {?PREFIX ++ Path, Headers}, ?HTTPC_OPTS, []). + +req(Type, Path, Headers, Body) -> + httpc:request(Type, {?PREFIX ++ Path, Headers, "application/json", Body}, + ?HTTPC_OPTS, []). + +decode(?OK, _Headers, ResBody) -> cleanup(mochijson2:decode(ResBody)); +decode(_, Headers, _ResBody) -> Headers. + +cleanup(L) when is_list(L) -> + [cleanup(I) || I <- L]; +cleanup({struct, I}) -> + cleanup(I); +cleanup({K, V}) when is_binary(K) -> + {list_to_atom(binary_to_list(K)), cleanup(V)}; +cleanup(I) -> + I. + +auth_header(Username, Password) -> + {"Authorization", + "Basic " ++ binary_to_list(base64:encode(Username ++ ":" ++ Password))}. + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_unit.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_unit.erl new file mode 100644 index 0000000..a3ecc80 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_unit.erl @@ -0,0 +1,63 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Console. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_test_unit). + +-include_lib("eunit/include/eunit.hrl"). + +tokenise_test() -> + [] = rabbit_mgmt_format:tokenise(""), + ["foo"] = rabbit_mgmt_format:tokenise("foo"), + ["foo", "bar"] = rabbit_mgmt_format:tokenise("foo~bar"), + ["foo", "", "bar"] = rabbit_mgmt_format:tokenise("foo~~bar"), + ok. + +pack_binding_test() -> + assert_binding(<<"~">>, + <<"">>, []), + assert_binding(<<"foo">>, + <<"foo">>, []), + assert_binding(<<"foo%7Ebar%2Fbash">>, + <<"foo~bar/bash">>, []), + assert_binding(<<"foo%7Ebar%7Ebash">>, + <<"foo~bar~bash">>, []), + ok. + +amqp_table_test() -> + assert_table({struct, []}, []), + assert_table({struct, [{<<"x-expires">>, 1000}]}, + [{<<"x-expires">>, long, 1000}]), + assert_table({struct, + [{<<"x-forwarding">>, + [{struct, + [{<<"uri">>, <<"amqp://localhost/%2f/upstream">>}]}]}]}, + [{<<"x-forwarding">>, array, + [{table, [{<<"uri">>, longstr, + <<"amqp://localhost/%2f/upstream">>}]}]}]). + +assert_table(JSON, AMQP) -> + ?assertEqual(JSON, rabbit_mgmt_format:amqp_table(AMQP)), + ?assertEqual(AMQP, rabbit_mgmt_format:to_amqp_table(JSON)). + +%%-------------------------------------------------------------------- + +assert_binding(Packed, Routing, Args) -> + case rabbit_mgmt_format:pack_binding_props(Routing, Args) of + Packed -> + ok; + Act -> + throw({pack, Routing, Args, expected, Packed, got, Act}) + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_util.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_util.erl new file mode 100644 index 0000000..1e53d89 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbit_mgmt_test_util.erl @@ -0,0 +1,45 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Console. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2012 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_test_util). + +-export([assert_list/2, assert_item/2, test_item/2]). + +assert_list(Exp, Act) -> + case length(Exp) == length(Act) of + true -> ok; + false -> throw({expected, Exp, actual, Act}) + end, + [case length(lists:filter(fun(ActI) -> test_item(ExpI, ActI) end, Act)) of + 1 -> ok; + N -> throw({found, N, ExpI, in, Act}) + end || ExpI <- Exp]. + +assert_item(Exp, Act) -> + case test_item0(Exp, Act) of + [] -> ok; + Or -> throw(Or) + end. + +test_item(Exp, Act) -> + case test_item0(Exp, Act) of + [] -> true; + _ -> false + end. + +test_item0(Exp, Act) -> + [{did_not_find, ExpI, in, Act} || ExpI <- Exp, + not lists:member(ExpI, Act)]. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbitmqadmin-test.py b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbitmqadmin-test.py new file mode 100755 index 0000000..8e1d310 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/rabbitmqadmin-test.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python + +import unittest +import os +import os.path +import socket +import subprocess +import sys +import shutil + +# TODO test: SSL, depth, config file, encodings(?), completion(???) + +class TestRabbitMQAdmin(unittest.TestCase): + def test_no_args(self): + self.run_fail([]) + + def test_help(self): + self.run_success(['--help']) + self.run_success(['help', 'subcommands']) + self.run_success(['help', 'config']) + self.run_fail(['help', 'astronomy']) + + def test_host(self): + self.run_success(['show', 'overview']) + self.run_success(['--host', 'localhost', 'show', 'overview']) + self.run_fail(['--host', 'some-host-that-does-not-exist', 'show', 'overview']) + + def test_port(self): + # Test port selection + self.run_success(['--port', '15672', 'show', 'overview']) + # Test port not open + self.run_fail(['--port', '15673', 'show', 'overview']) + # Test port open but not talking HTTP + self.run_fail(['--port', '5672', 'show', 'overview']) + + def test_config(self): + original_home = os.getenv('HOME') + tmpdir = os.getenv("TMPDIR") or os.getenv("TEMP") or "/tmp" + shutil.copyfile(os.path.dirname(__file__) + os.sep + "default-config", + tmpdir + os.sep + ".rabbitmqadmin.conf") + os.environ['HOME'] = tmpdir + + self.run_fail(['--config', '/tmp/no-such-config-file', 'show', 'overview']) + + cf = os.path.dirname(__file__) + os.sep + "test-config" + self.run_success(['--config', cf, '--node', 'host_normal', 'show', 'overview']) + + # test 'default node in the config file' where "default" uses an invalid host + self.run_fail(['--config', cf, 'show', 'overview']) + self.run_success(["show", "overview"]) + self.run_fail(['--node', 'non_default', "show", "overview"]) + os.environ['HOME'] = original_home + + def test_user(self): + self.run_success(['--user', 'guest', '--password', 'guest', 'show', 'overview']) + self.run_fail(['--user', 'no', '--password', 'guest', 'show', 'overview']) + self.run_fail(['--user', 'guest', '--password', 'no', 'show', 'overview']) + + def test_fmt_long(self): + self.assert_output(""" +-------------------------------------------------------------------------------- + + name: / +tracing: False + +-------------------------------------------------------------------------------- + +""", ['--format', 'long', 'list', 'vhosts', 'name', 'tracing']) + + def test_fmt_kvp(self): + self.assert_output("""name="/" tracing="False" +""", ['--format', 'kvp', 'list', 'vhosts', 'name', 'tracing']) + + def test_fmt_tsv(self): + self.assert_output("""name tracing +/ False +""", ['--format', 'tsv', 'list', 'vhosts', 'name', 'tracing']) + + def test_fmt_table(self): + out = """+------+---------+ +| name | tracing | ++------+---------+ +| / | False | ++------+---------+ +""" + self.assert_output(out, ['list', 'vhosts', 'name', 'tracing']) + self.assert_output(out, ['--format', 'table', 'list', 'vhosts', 'name', 'tracing']) + + def test_fmt_bash(self): + self.assert_output("""/ +""", ['--format', 'bash', 'list', 'vhosts', 'name', 'tracing']) + + def test_vhosts(self): + self.assert_list(['/'], l('vhosts')) + self.run_success(['declare', 'vhost', 'name=foo']) + self.assert_list(['/', 'foo'], l('vhosts')) + self.run_success(['delete', 'vhost', 'name=foo']) + self.assert_list(['/'], l('vhosts')) + + def test_users(self): + self.assert_list(['guest'], l('users')) + self.run_fail(['declare', 'user', 'name=foo']) + self.run_success(['declare', 'user', 'name=foo', 'password=pass', 'tags=']) + self.assert_list(['foo', 'guest'], l('users')) + self.run_success(['delete', 'user', 'name=foo']) + self.assert_list(['guest'], l('users')) + + def test_permissions(self): + self.run_success(['declare', 'vhost', 'name=foo']) + self.run_success(['declare', 'user', 'name=bar', 'password=pass', 'tags=']) + self.assert_table([['guest', '/']], ['list', 'permissions', 'user', 'vhost']) + self.run_success(['declare', 'permission', 'user=bar', 'vhost=foo', 'configure=.*', 'write=.*', 'read=.*']) + self.assert_table([['guest', '/'], ['bar', 'foo']], ['list', 'permissions', 'user', 'vhost']) + self.run_success(['delete', 'user', 'name=bar']) + self.run_success(['delete', 'vhost', 'name=foo']) + + def test_alt_vhost(self): + self.run_success(['declare', 'vhost', 'name=foo']) + self.run_success(['declare', 'permission', 'user=guest', 'vhost=foo', 'configure=.*', 'write=.*', 'read=.*']) + self.run_success(['declare', 'queue', 'name=in_/']) + self.run_success(['--vhost', 'foo', 'declare', 'queue', 'name=in_foo']) + self.assert_table([['/', 'in_/'], ['foo', 'in_foo']], ['list', 'queues', 'vhost', 'name']) + self.run_success(['--vhost', 'foo', 'delete', 'queue', 'name=in_foo']) + self.run_success(['delete', 'queue', 'name=in_/']) + self.run_success(['delete', 'vhost', 'name=foo']) + + def test_exchanges(self): + self.run_success(['declare', 'exchange', 'name=foo', 'type=direct']) + self.assert_list(['', 'amq.direct', 'amq.fanout', 'amq.headers', 'amq.match', 'amq.rabbitmq.log', 'amq.rabbitmq.trace', 'amq.topic', 'foo'], l('exchanges')) + self.run_success(['delete', 'exchange', 'name=foo']) + + def test_queues(self): + self.run_success(['declare', 'queue', 'name=foo']) + self.assert_list(['foo'], l('queues')) + self.run_success(['delete', 'queue', 'name=foo']) + + def test_bindings(self): + self.run_success(['declare', 'queue', 'name=foo']) + self.run_success(['declare', 'binding', 'source=amq.direct', 'destination=foo', 'destination_type=queue', 'routing_key=test']) + self.assert_table([['', 'foo', 'queue', 'foo'], ['amq.direct', 'foo', 'queue', 'test']], ['list', 'bindings', 'source', 'destination', 'destination_type', 'routing_key']) + self.run_success(['delete', 'queue', 'name=foo']) + + def test_policies(self): + self.run_success(['declare', 'policy', 'name=ha', 'pattern=.*', 'definition={"ha-mode":"all"}']) + self.assert_table([['ha', '/', '.*', '{"ha-mode": "all"}']], ['list', 'policies', 'name', 'vhost', 'pattern', 'definition']) + self.run_success(['delete', 'policy', 'name=ha']) + + def test_parameters(self): + self.ctl(['eval', 'rabbit_runtime_parameters_test:register().']) + self.run_success(['declare', 'parameter', 'component=test', 'name=good', 'value=123']) + self.assert_table([['test', 'good', '/', '123']], ['list', 'parameters', 'component', 'name', 'vhost', 'value']) + self.run_success(['delete', 'parameter', 'component=test', 'name=good']) + self.ctl(['eval', 'rabbit_runtime_parameters_test:unregister().']) + + def test_publish(self): + self.run_success(['declare', 'queue', 'name=test']) + self.run_success(['publish', 'routing_key=test', 'payload=test_1']) + self.run_success(['publish', 'routing_key=test', 'payload=test_2']) + self.run_success(['publish', 'routing_key=test'], stdin='test_3') + self.assert_table([exp_msg('test', 2, False, 'test_1')], ['get', 'queue=test', 'requeue=false']) + self.assert_table([exp_msg('test', 1, False, 'test_2')], ['get', 'queue=test', 'requeue=true']) + self.assert_table([exp_msg('test', 1, True, 'test_2')], ['get', 'queue=test', 'requeue=false']) + self.assert_table([exp_msg('test', 0, False, 'test_3')], ['get', 'queue=test', 'requeue=false']) + self.run_success(['publish', 'routing_key=test'], stdin='test_4') + filename = '/tmp/rabbitmq-test/get.txt' + self.run_success(['get', 'queue=test', 'requeue=false', 'payload_file=' + filename]) + with open(filename) as f: + self.assertEqual('test_4', f.read()) + os.remove(filename) + self.run_success(['delete', 'queue', 'name=test']) + + def test_ignore_vhost(self): + self.run_success(['--vhost', '/', 'show', 'overview']) + self.run_success(['--vhost', '/', 'list', 'users']) + self.run_success(['--vhost', '/', 'list', 'vhosts']) + self.run_success(['--vhost', '/', 'list', 'nodes']) + self.run_success(['--vhost', '/', 'list', 'permissions']) + self.run_success(['--vhost', '/', 'declare', 'user', 'name=foo', 'password=pass', 'tags=']) + self.run_success(['delete', 'user', 'name=foo']) + + def test_sort(self): + self.run_success(['declare', 'queue', 'name=foo']) + self.run_success(['declare', 'binding', 'source=amq.direct', 'destination=foo', 'destination_type=queue', 'routing_key=bbb']) + self.run_success(['declare', 'binding', 'source=amq.topic', 'destination=foo', 'destination_type=queue', 'routing_key=aaa']) + self.assert_table([['', 'foo'], ['amq.direct', 'bbb'], ['amq.topic', 'aaa']], ['--sort', 'source', 'list', 'bindings', 'source', 'routing_key']) + self.assert_table([['amq.topic', 'aaa'], ['amq.direct', 'bbb'], ['', 'foo']], ['--sort', 'routing_key', 'list', 'bindings', 'source', 'routing_key']) + self.assert_table([['amq.topic', 'aaa'], ['amq.direct', 'bbb'], ['', 'foo']], ['--sort', 'source', '--sort-reverse', 'list', 'bindings', 'source', 'routing_key']) + self.run_success(['delete', 'queue', 'name=foo']) + + # --------------------------------------------------------------------------- + + def run_success(self, args, **kwargs): + (stdout, ret) = self.admin(args, **kwargs) + if ret != 0: + self.fail(stdout) + + def run_fail(self, args): + (stdout, ret) = self.admin(args) + if ret == 0: + self.fail(stdout) + + def assert_output(self, expected, args): + self.assertEqual(expected, self.admin(args)[0]) + + def assert_list(self, expected, args0): + args = ['-f', 'tsv', '-q'] + args.extend(args0) + self.assertEqual(expected, self.admin(args)[0].splitlines()) + + def assert_table(self, expected, args0): + args = ['-f', 'tsv', '-q'] + args.extend(args0) + self.assertEqual(expected, [l.split('\t') for l in self.admin(args)[0].splitlines()]) + + def admin(self, args, stdin=None): + return run('../../../bin/rabbitmqadmin', args, stdin) + + def ctl(self, args0, stdin=None): + args = ['-n', 'rabbit-test'] + args.extend(args0) + (stdout, ret) = run('../../../../rabbitmq-server/scripts/rabbitmqctl', args, stdin) + if ret != 0: + self.fail(stdout) + +def run(cmd, args, stdin): + path = os.path.normpath(os.path.join(os.getcwd(), sys.argv[0], cmd)) + cmdline = [path] + cmdline.extend(args) + proc = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdout, stderr) = proc.communicate(stdin) + returncode = proc.returncode + return (stdout + stderr, returncode) + +def l(thing): + return ['list', thing, 'name'] + +def exp_msg(key, count, redelivered, payload): + # routing_key, exchange, message_count, payload, payload_bytes, payload_encoding, properties, redelivered + return [key, '', str(count), payload, str(len(payload)), 'string', '', str(redelivered)] + +if __name__ == '__main__': + print "\nrabbitmqadmin tests\n===================\n" + suite = unittest.TestLoader().loadTestsFromTestCase(TestRabbitMQAdmin) + unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/test-config b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/test-config new file mode 100644 index 0000000..93322e7 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-management/test/src/test-config @@ -0,0 +1,15 @@ +# rabbitmqadmin.conf.example START + +[host_normal] +hostname = localhost +port = 15672 +username = guest +password = guest +declare_vhost = / # Used as default for declare / delete only +vhost = / # Used as default for declare / delete / list + +[default] +hostname = localhost +port = 99999 +username = guest +password = guest diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/.srcdist_done b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/.srcdist_done new file mode 100644 index 0000000..e69de29 diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/Makefile b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/Makefile new file mode 100644 index 0000000..482105a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/Makefile @@ -0,0 +1 @@ +include ../umbrella.mk diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/README.md b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/README.md new file mode 100644 index 0000000..72ba9ea --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/README.md @@ -0,0 +1,9 @@ +# RabbitMQ MQTT adapter + +The MQTT adapter is included in the RabbitMQ distribution. To enable +it, use rabbitmq-plugins: + + rabbitmq-plugins enable rabbitmq_mqtt + +Full usage instructions can be found at +. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/include/rabbit_mqtt.hrl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/include/rabbit_mqtt.hrl new file mode 100644 index 0000000..8104c79 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/include/rabbit_mqtt.hrl @@ -0,0 +1,43 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-define(CLIENT_ID_MAXLEN, 23). + +%% reader state +-record(state, { socket, + conn_name, + await_recv, + connection_state, + keepalive, + keepalive_sup, + conserve, + parse_state, + proc_state }). + +%% processor state +-record(proc_state, { socket, + subscriptions, + consumer_tags, + unacked_pubs, + awaiting_ack, + awaiting_seqno, + message_id, + client_id, + clean_sess, + will_msg, + channels, + connection, + exchange }). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/include/rabbit_mqtt_frame.hrl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/include/rabbit_mqtt_frame.hrl new file mode 100644 index 0000000..87f24d5 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/include/rabbit_mqtt_frame.hrl @@ -0,0 +1,93 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-define(PROTOCOL_NAMES, [{3, "MQIsdp"}, {4, "MQTT"}]). + +%% frame types + +-define(CONNECT, 1). +-define(CONNACK, 2). +-define(PUBLISH, 3). +-define(PUBACK, 4). +-define(PUBREC, 5). +-define(PUBREL, 6). +-define(PUBCOMP, 7). +-define(SUBSCRIBE, 8). +-define(SUBACK, 9). +-define(UNSUBSCRIBE, 10). +-define(UNSUBACK, 11). +-define(PINGREQ, 12). +-define(PINGRESP, 13). +-define(DISCONNECT, 14). + +%% connect return codes + +-define(CONNACK_ACCEPT, 0). +-define(CONNACK_PROTO_VER, 1). %% unacceptable protocol version +-define(CONNACK_INVALID_ID, 2). %% identifier rejected +-define(CONNACK_SERVER, 3). %% server unavailable +-define(CONNACK_CREDENTIALS, 4). %% bad user name or password +-define(CONNACK_AUTH, 5). %% not authorized + +%% qos levels + +-define(QOS_0, 0). +-define(QOS_1, 1). +-define(QOS_2, 2). + +-record(mqtt_frame, {fixed, + variable, + payload}). + +-record(mqtt_frame_fixed, {type = 0, + dup = 0, + qos = 0, + retain = 0}). + +-record(mqtt_frame_connect, {proto_ver, + will_retain, + will_qos, + will_flag, + clean_sess, + keep_alive, + client_id, + will_topic, + will_msg, + username, + password}). + +-record(mqtt_frame_connack, {return_code}). + +-record(mqtt_frame_publish, {topic_name, + message_id}). + +-record(mqtt_frame_subscribe,{message_id, + topic_table}). + +-record(mqtt_frame_suback, {message_id, + qos_table = []}). + +-record(mqtt_topic, {name, + qos}). + +-record(mqtt_frame_other, {other}). + +-record(mqtt_msg, {retain, + qos, + topic, + dup, + message_id, + payload}). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/lib/junit.jar b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/lib/junit.jar new file mode 100644 index 0000000000000000000000000000000000000000..674d71e89ea154dbe2e3cd032821c22b39e8fd68 GIT binary patch literal 121070 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 F`9DH82$KK+ literal 0 HcmV?d00001 diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/package.mk b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/package.mk new file mode 100644 index 0000000..e564fab --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/package.mk @@ -0,0 +1,5 @@ +RELEASABLE:=true +DEPS:=rabbitmq-erlang-client + +RABBITMQ_TEST_PATH=$(PACKAGE_DIR)/../../rabbitmq-test +WITH_BROKER_TEST_SCRIPTS:=$(PACKAGE_DIR)/test/test.sh diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt.erl new file mode 100644 index 0000000..25e191a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt.erl @@ -0,0 +1,28 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mqtt). + +-behaviour(application). +-export([start/2, stop/1]). + +start(normal, []) -> + {ok, Listeners} = application:get_env(tcp_listeners), + {ok, SslListeners} = application:get_env(ssl_listeners), + rabbit_mqtt_sup:start_link({Listeners, SslListeners}, []). + +stop(_State) -> + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_collector.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_collector.erl new file mode 100644 index 0000000..26009ee --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_collector.erl @@ -0,0 +1,94 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mqtt_collector). + +-behaviour(gen_server). + +-export([start_link/0, register/2, unregister/2]). + +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-record(state, {client_ids}). + +-define(SERVER, ?MODULE). + +%%---------------------------------------------------------------------------- + +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +register(ClientId, Pid) -> + gen_server:call(rabbit_mqtt_collector, {register, ClientId, Pid}, infinity). + +unregister(ClientId, Pid) -> + gen_server:call(rabbit_mqtt_collector, {unregister, ClientId, Pid}, infinity). + +%%---------------------------------------------------------------------------- + +init([]) -> + {ok, #state{client_ids = dict:new()}}. % clientid -> {pid, monitor} + +%%-------------------------------------------------------------------------- + +handle_call({register, ClientId, Pid}, _From, + State = #state{client_ids = Ids}) -> + Ids1 = case dict:find(ClientId, Ids) of + {ok, {OldPid, MRef}} when Pid =/= OldPid -> + catch gen_server2:cast(OldPid, duplicate_id), + erlang:demonitor(MRef), + dict:erase(ClientId, Ids); + error -> + Ids + end, + Ids2 = dict:store(ClientId, {Pid, erlang:monitor(process, Pid)}, Ids1), + {reply, ok, State#state{client_ids = Ids2}}; + +handle_call({unregister, ClientId, Pid}, _From, State = #state{client_ids = Ids}) -> + {Reply, Ids1} = case dict:find(ClientId, Ids) of + {ok, {Pid, MRef}} -> erlang:demonitor(MRef), + {ok, dict:erase(ClientId, Ids)}; + _ -> {ok, Ids} + end, + {reply, Reply, State#state{ client_ids = Ids1 }}; + +handle_call(Msg, _From, State) -> + {stop, {unhandled_call, Msg}, State}. + +handle_cast(Msg, State) -> + {stop, {unhandled_cast, Msg}, State}. + +handle_info({'EXIT', _, {shutdown, closed}}, State) -> + {stop, {shutdown, closed}, State}; + +handle_info({'DOWN', MRef, process, DownPid, _Reason}, + State = #state{client_ids = Ids}) -> + Ids1 = dict:filter(fun (ClientId, {Pid, M}) + when Pid =:= DownPid, MRef =:= M -> + rabbit_log:warning("MQTT disconnect from ~p~n", + [ClientId]), + false; + (_, _) -> + true + end, Ids), + {noreply, State #state{ client_ids = Ids1 }}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_connection_sup.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_connection_sup.erl new file mode 100644 index 0000000..dd722c0 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_connection_sup.erl @@ -0,0 +1,51 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mqtt_connection_sup). + +-behaviour(supervisor2). + +-define(MAX_WAIT, 16#ffffffff). + +-export([start_link/0, start_keepalive_link/0]). + +-export([init/1]). + +%%---------------------------------------------------------------------------- + +start_link() -> + {ok, SupPid} = supervisor2:start_link(?MODULE, []), + {ok, ReaderPid} = supervisor2:start_child( + SupPid, + {rabbit_mqtt_reader, + {rabbit_mqtt_reader, start_link, []}, + intrinsic, ?MAX_WAIT, worker, [rabbit_mqtt_reader]}), + {ok, KeepaliveSup} = supervisor2:start_child( + SupPid, + {rabbit_keepalive_sup, + {rabbit_mqtt_connection_sup, start_keepalive_link, []}, + intrinsic, infinity, supervisor, [rabbit_keepalive_sup]}), + {ok, SupPid, {KeepaliveSup, ReaderPid}}. + +start_keepalive_link() -> + supervisor2:start_link(?MODULE, []). + +%%---------------------------------------------------------------------------- + +init([]) -> + {ok, {{one_for_all, 0, 1}, []}}. + + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_frame.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_frame.erl new file mode 100644 index 0000000..39172c7 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_frame.erl @@ -0,0 +1,232 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mqtt_frame). + +-export([parse/2, initial_state/0]). +-export([serialise/1]). + +-include("rabbit_mqtt_frame.hrl"). + +-define(RESERVED, 0). +-define(MAX_LEN, 16#fffffff). +-define(HIGHBIT, 2#10000000). +-define(LOWBITS, 2#01111111). + +initial_state() -> none. + +parse(<<>>, none) -> + {more, fun(Bin) -> parse(Bin, none) end}; +parse(<>, none) -> + parse_remaining_len(Rest, #mqtt_frame_fixed{ type = MessageType, + dup = bool(Dup), + qos = QoS, + retain = bool(Retain) }); +parse(Bin, Cont) -> Cont(Bin). + +parse_remaining_len(<<>>, Fixed) -> + {more, fun(Bin) -> parse_remaining_len(Bin, Fixed) end}; +parse_remaining_len(Rest, Fixed) -> + parse_remaining_len(Rest, Fixed, 1, 0). + +parse_remaining_len(_Bin, _Fixed, _Multiplier, Length) + when Length > ?MAX_LEN -> + {error, invalid_mqtt_frame_len}; +parse_remaining_len(<<>>, Fixed, Multiplier, Length) -> + {more, fun(Bin) -> parse_remaining_len(Bin, Fixed, Multiplier, Length) end}; +parse_remaining_len(<<1:1, Len:7, Rest/binary>>, Fixed, Multiplier, Value) -> + parse_remaining_len(Rest, Fixed, Multiplier * ?HIGHBIT, Value + Len * Multiplier); +parse_remaining_len(<<0:1, Len:7, Rest/binary>>, Fixed, Multiplier, Value) -> + parse_frame(Rest, Fixed, Value + Len * Multiplier). + +parse_frame(Bin, #mqtt_frame_fixed{ type = Type, + qos = Qos } = Fixed, Length) -> + case {Type, Bin} of + {?CONNECT, <>} -> + {ProtoName, Rest1} = parse_utf(FrameBin), + <> = Rest1, + <> = Rest2, + {ClientId, Rest4} = parse_utf(Rest3), + {WillTopic, Rest5} = parse_utf(Rest4, WillFlag), + {WillMsg, Rest6} = parse_msg(Rest5, WillFlag), + {UserName, Rest7} = parse_utf(Rest6, UsernameFlag), + {PasssWord, <<>>} = parse_utf(Rest7, PasswordFlag), + case protocol_name_approved(ProtoVersion, ProtoName) of + true -> + wrap(Fixed, + #mqtt_frame_connect{ + proto_ver = ProtoVersion, + will_retain = bool(WillRetain), + will_qos = WillQos, + will_flag = bool(WillFlag), + clean_sess = bool(CleanSession), + keep_alive = KeepAlive, + client_id = ClientId, + will_topic = WillTopic, + will_msg = WillMsg, + username = UserName, + password = PasssWord}, Rest); + false -> + {error, protocol_header_corrupt} + end; + {?PUBLISH, <>} -> + {TopicName, Rest1} = parse_utf(FrameBin), + {MessageId, Payload} = case Qos of + 0 -> {undefined, Rest1}; + _ -> <> = Rest1, + {M, R} + end, + wrap(Fixed, #mqtt_frame_publish { topic_name = TopicName, + message_id = MessageId }, + Payload, Rest); + {?PUBACK, <>} -> + <> = FrameBin, + wrap(Fixed, #mqtt_frame_publish { message_id = MessageId }, Rest); + {Subs, <>} + when Subs =:= ?SUBSCRIBE orelse Subs =:= ?UNSUBSCRIBE -> + 1 = Qos, + <> = FrameBin, + Topics = parse_topics(Subs, Rest1, []), + wrap(Fixed, #mqtt_frame_subscribe { message_id = MessageId, + topic_table = Topics }, Rest); + {Minimal, Rest} + when Minimal =:= ?DISCONNECT orelse Minimal =:= ?PINGREQ -> + Length = 0, + wrap(Fixed, Rest); + {_, TooShortBin} -> + {more, fun(BinMore) -> + parse_frame(<>, + Fixed, Length) + end} + end. + +parse_topics(_, <<>>, Topics) -> + Topics; +parse_topics(?SUBSCRIBE = Sub, Bin, Topics) -> + {Name, <<_:6, QoS:2, Rest/binary>>} = parse_utf(Bin), + parse_topics(Sub, Rest, [#mqtt_topic { name = Name, qos = QoS } | Topics]); +parse_topics(?UNSUBSCRIBE = Sub, Bin, Topics) -> + {Name, <>} = parse_utf(Bin), + parse_topics(Sub, Rest, [#mqtt_topic { name = Name } | Topics]). + +wrap(Fixed, Variable, Payload, Rest) -> + {ok, #mqtt_frame { variable = Variable, fixed = Fixed, payload = Payload }, Rest}. +wrap(Fixed, Variable, Rest) -> + {ok, #mqtt_frame { variable = Variable, fixed = Fixed }, Rest}. +wrap(Fixed, Rest) -> + {ok, #mqtt_frame { fixed = Fixed }, Rest}. + +parse_utf(Bin, 0) -> + {undefined, Bin}; +parse_utf(Bin, _) -> + parse_utf(Bin). + +parse_utf(<>) -> + {binary_to_list(Str), Rest}. + +parse_msg(Bin, 0) -> + {undefined, Bin}; +parse_msg(<>, _) -> + {Msg, Rest}. + +bool(0) -> false; +bool(1) -> true. + +%% serialisation + +serialise(#mqtt_frame{ fixed = Fixed, + variable = Variable, + payload = Payload }) -> + serialise_variable(Fixed, Variable, serialise_payload(Payload)). + +serialise_payload(undefined) -> <<>>; +serialise_payload(B) when is_binary(B) -> B. + +serialise_variable(#mqtt_frame_fixed { type = ?CONNACK } = Fixed, + #mqtt_frame_connack { return_code = ReturnCode }, + <<>> = PayloadBin) -> + VariableBin = <>, + serialise_fixed(Fixed, VariableBin, PayloadBin); + +serialise_variable(#mqtt_frame_fixed { type = SubAck } = Fixed, + #mqtt_frame_suback { message_id = MessageId, + qos_table = Qos }, + <<>> = _PayloadBin) + when SubAck =:= ?SUBACK orelse SubAck =:= ?UNSUBACK -> + VariableBin = <>, + QosBin = << <> || Q <- Qos >>, + serialise_fixed(Fixed, VariableBin, QosBin); + +serialise_variable(#mqtt_frame_fixed { type = ?PUBLISH, + qos = Qos } = Fixed, + #mqtt_frame_publish { topic_name = TopicName, + message_id = MessageId }, + PayloadBin) -> + TopicBin = serialise_utf(TopicName), + MessageIdBin = case Qos of + 0 -> <<>>; + 1 -> <> + end, + serialise_fixed(Fixed, <>, PayloadBin); + +serialise_variable(#mqtt_frame_fixed { type = ?PUBACK } = Fixed, + #mqtt_frame_publish { message_id = MessageId }, + PayloadBin) -> + MessageIdBin = <>, + serialise_fixed(Fixed, MessageIdBin, PayloadBin); + +serialise_variable(#mqtt_frame_fixed {} = Fixed, + undefined, + <<>> = _PayloadBin) -> + serialise_fixed(Fixed, <<>>, <<>>). + +serialise_fixed(#mqtt_frame_fixed{ type = Type, + dup = Dup, + qos = Qos, + retain = Retain }, VariableBin, PayloadBin) + when is_integer(Type) andalso ?CONNECT =< Type andalso Type =< ?DISCONNECT -> + Len = size(VariableBin) + size(PayloadBin), + true = (Len =< ?MAX_LEN), + LenBin = serialise_len(Len), + <>. + +serialise_utf(String) -> + StringBin = unicode:characters_to_binary(String), + Len = size(StringBin), + true = (Len =< 16#ffff), + <>. + +serialise_len(N) when N =< ?LOWBITS -> + <<0:1, N:7>>; +serialise_len(N) -> + <<1:1, (N rem ?HIGHBIT):7, (serialise_len(N div ?HIGHBIT))/binary>>. + +opt(undefined) -> ?RESERVED; +opt(false) -> 0; +opt(true) -> 1; +opt(X) when is_integer(X) -> X. + +protocol_name_approved(Ver, Name) -> + lists:member({Ver, Name}, ?PROTOCOL_NAMES). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_processor.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_processor.erl new file mode 100644 index 0000000..eb17673 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_processor.erl @@ -0,0 +1,511 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mqtt_processor). + +-export([info/2, initial_state/1, + process_frame/2, amqp_pub/2, amqp_callback/2, send_will/1, + close_connection/1]). + +-include_lib("amqp_client/include/amqp_client.hrl"). +-include("rabbit_mqtt_frame.hrl"). +-include("rabbit_mqtt.hrl"). + +-define(FRAME_TYPE(Frame, Type), + Frame = #mqtt_frame{ fixed = #mqtt_frame_fixed{ type = Type }}). + +initial_state(Socket) -> + #proc_state{ unacked_pubs = gb_trees:empty(), + awaiting_ack = gb_trees:empty(), + message_id = 1, + subscriptions = dict:new(), + consumer_tags = {undefined, undefined}, + channels = {undefined, undefined}, + exchange = rabbit_mqtt_util:env(exchange), + socket = Socket }. + +info(client_id, #proc_state{ client_id = ClientId }) -> ClientId. + +process_frame(#mqtt_frame{ fixed = #mqtt_frame_fixed{ type = Type }}, + PState = #proc_state{ connection = undefined } ) + when Type =/= ?CONNECT -> + {error, connect_expected, PState}; +process_frame(Frame = #mqtt_frame{ fixed = #mqtt_frame_fixed{ type = Type }}, + PState ) -> + %%rabbit_log:info("MQTT received frame ~p ~n", [Frame]), + try process_request(Type, Frame, PState) of + Result -> Result + catch _:Error -> + close_connection(PState), + {error, Error} + end. + +process_request(?CONNECT, + #mqtt_frame{ variable = #mqtt_frame_connect{ + username = Username, + password = Password, + proto_ver = ProtoVersion, + clean_sess = CleanSess, + client_id = ClientId0, + keep_alive = Keepalive} = Var}, PState) -> + ClientId = case ClientId0 of + [] -> rabbit_mqtt_util:gen_client_id(); + [_|_] -> ClientId0 + end, + {ReturnCode, PState1} = + case {lists:member(ProtoVersion, proplists:get_keys(?PROTOCOL_NAMES)), + ClientId0 =:= [] andalso CleanSess =:= false} of + {false, _} -> + {?CONNACK_PROTO_VER, PState}; + {_, true} -> + {?CONNACK_INVALID_ID, PState}; + _ -> + case creds(Username, Password) of + nocreds -> + rabbit_log:error("MQTT login failed - no credentials~n"), + {?CONNACK_CREDENTIALS, PState}; + {UserBin, PassBin} -> + case process_login(UserBin, PassBin, ProtoVersion, PState) of + {?CONNACK_ACCEPT, Conn} -> + link(Conn), + {ok, Ch} = amqp_connection:open_channel(Conn), + ok = rabbit_mqtt_collector:register( + ClientId, self()), + Prefetch = rabbit_mqtt_util:env(prefetch), + #'basic.qos_ok'{} = amqp_channel:call( + Ch, #'basic.qos'{prefetch_count = Prefetch}), + rabbit_mqtt_reader:start_keepalive(self(), Keepalive), + {?CONNACK_ACCEPT, + maybe_clean_sess( + PState #proc_state{ will_msg = make_will_msg(Var), + clean_sess = CleanSess, + channels = {Ch, undefined}, + connection = Conn, + client_id = ClientId })}; + ConnAck -> + {ConnAck, PState} + end + end + end, + send_client(#mqtt_frame{ fixed = #mqtt_frame_fixed{ type = ?CONNACK}, + variable = #mqtt_frame_connack{ + return_code = ReturnCode }}, PState1), + {ok, PState1}; + +process_request(?PUBACK, + #mqtt_frame{ + variable = #mqtt_frame_publish{ message_id = MessageId }}, + #proc_state{ channels = {Channel, _}, + awaiting_ack = Awaiting } = PState) -> + Tag = gb_trees:get(MessageId, Awaiting), + amqp_channel:cast( + Channel, #'basic.ack'{ delivery_tag = Tag }), + {ok, PState #proc_state{ awaiting_ack = gb_trees:delete( MessageId, Awaiting)}}; + +process_request(?PUBLISH, + #mqtt_frame{ + fixed = #mqtt_frame_fixed{ qos = ?QOS_2 }}, PState) -> + {error, qos2_not_supported, PState}; +process_request(?PUBLISH, + #mqtt_frame{ + fixed = #mqtt_frame_fixed{ qos = Qos, + retain = Retain, + dup = Dup }, + variable = #mqtt_frame_publish{ topic_name = Topic, + message_id = MessageId }, + payload = Payload }, PState) -> + {ok, amqp_pub(#mqtt_msg{ retain = Retain, + qos = Qos, + topic = Topic, + dup = Dup, + message_id = MessageId, + payload = Payload }, PState)}; + +process_request(?SUBSCRIBE, + #mqtt_frame{ + variable = #mqtt_frame_subscribe{ message_id = MessageId, + topic_table = Topics }, + payload = undefined }, + #proc_state{ channels = {Channel, _}, + exchange = Exchange} = PState0) -> + {QosResponse, PState1} = + lists:foldl(fun (#mqtt_topic{ name = TopicName, + qos = Qos }, {QosList, PState}) -> + SupportedQos = supported_subs_qos(Qos), + {Queue, #proc_state{ subscriptions = Subs } = PState1} = + ensure_queue(SupportedQos, PState), + Binding = #'queue.bind'{ + queue = Queue, + exchange = Exchange, + routing_key = rabbit_mqtt_util:mqtt2amqp( + TopicName)}, + #'queue.bind_ok'{} = amqp_channel:call(Channel, Binding), + {[SupportedQos | QosList], + PState1 #proc_state{ subscriptions = + dict:append(TopicName, SupportedQos, Subs) }} + end, {[], PState0}, Topics), + send_client(#mqtt_frame{ fixed = #mqtt_frame_fixed{ type = ?SUBACK }, + variable = #mqtt_frame_suback{ + message_id = MessageId, + qos_table = QosResponse }}, PState1), + + {ok, PState1}; + +process_request(?UNSUBSCRIBE, + #mqtt_frame{ + variable = #mqtt_frame_subscribe{ message_id = MessageId, + topic_table = Topics }, + payload = undefined }, #proc_state{ channels = {Channel, _}, + exchange = Exchange, + client_id = ClientId, + subscriptions = Subs0} = PState) -> + Queues = rabbit_mqtt_util:subcription_queue_name(ClientId), + Subs1 = + lists:foldl( + fun (#mqtt_topic{ name = TopicName }, Subs) -> + QosSubs = case dict:find(TopicName, Subs) of + {ok, Val} when is_list(Val) -> lists:usort(Val); + error -> [] + end, + lists:foreach( + fun (QosSub) -> + Queue = element(QosSub + 1, Queues), + Binding = #'queue.unbind'{ + queue = Queue, + exchange = Exchange, + routing_key = + rabbit_mqtt_util:mqtt2amqp(TopicName)}, + #'queue.unbind_ok'{} = amqp_channel:call(Channel, Binding) + end, QosSubs), + dict:erase(TopicName, Subs) + end, Subs0, Topics), + send_client(#mqtt_frame{ fixed = #mqtt_frame_fixed { type = ?UNSUBACK }, + variable = #mqtt_frame_suback{ message_id = MessageId }}, + PState), + {ok, PState #proc_state{ subscriptions = Subs1 }}; + +process_request(?PINGREQ, #mqtt_frame{}, PState) -> + send_client(#mqtt_frame{ fixed = #mqtt_frame_fixed{ type = ?PINGRESP }}, + PState), + {ok, PState}; + +process_request(?DISCONNECT, #mqtt_frame{}, PState) -> + {stop, PState}. + +%%---------------------------------------------------------------------------- + +amqp_callback({#'basic.deliver'{ consumer_tag = ConsumerTag, + delivery_tag = DeliveryTag, + routing_key = RoutingKey }, + #amqp_msg{ props = #'P_basic'{ headers = Headers }, + payload = Payload }} = Delivery, + #proc_state{ channels = {Channel, _}, + awaiting_ack = Awaiting, + message_id = MsgId } = PState) -> + case {delivery_dup(Delivery), delivery_qos(ConsumerTag, Headers, PState)} of + {true, {?QOS_0, ?QOS_1}} -> + amqp_channel:cast( + Channel, #'basic.ack'{ delivery_tag = DeliveryTag }), + {ok, PState}; + {true, {?QOS_0, ?QOS_0}} -> + {ok, PState}; + {Dup, {DeliveryQos, _SubQos} = Qos} -> + send_client( + #mqtt_frame{ fixed = #mqtt_frame_fixed{ + type = ?PUBLISH, + qos = DeliveryQos, + dup = Dup }, + variable = #mqtt_frame_publish{ + message_id = + case DeliveryQos of + ?QOS_0 -> undefined; + ?QOS_1 -> MsgId + end, + topic_name = + rabbit_mqtt_util:amqp2mqtt( + RoutingKey) }, + payload = Payload}, PState), + case Qos of + {?QOS_0, ?QOS_0} -> + {ok, PState}; + {?QOS_1, ?QOS_1} -> + {ok, + next_msg_id( + PState #proc_state{ + awaiting_ack = + gb_trees:insert(MsgId, DeliveryTag, Awaiting)})}; + {?QOS_0, ?QOS_1} -> + amqp_channel:cast( + Channel, #'basic.ack'{ delivery_tag = DeliveryTag }), + {ok, PState} + end + end; + +amqp_callback(#'basic.ack'{ multiple = true, delivery_tag = Tag } = Ack, + PState = #proc_state{ unacked_pubs = UnackedPubs }) -> + case gb_trees:size(UnackedPubs) > 0 andalso + gb_trees:take_smallest(UnackedPubs) of + {TagSmall, MsgId, UnackedPubs1} when TagSmall =< Tag -> + send_client( + #mqtt_frame{ fixed = #mqtt_frame_fixed{ type = ?PUBACK }, + variable = #mqtt_frame_publish{ message_id = MsgId }}, + PState), + amqp_callback(Ack, PState #proc_state{ unacked_pubs = UnackedPubs1 }); + _ -> + {ok, PState} + end; + +amqp_callback(#'basic.ack'{ multiple = false, delivery_tag = Tag }, + PState = #proc_state{ unacked_pubs = UnackedPubs }) -> + send_client( + #mqtt_frame{ fixed = #mqtt_frame_fixed{ type = ?PUBACK }, + variable = #mqtt_frame_publish{ + message_id = gb_trees:get( + Tag, UnackedPubs) }}, PState), + {ok, PState #proc_state{ unacked_pubs = gb_trees:delete(Tag, UnackedPubs) }}. + +delivery_dup({#'basic.deliver'{ redelivered = Redelivered }, + #amqp_msg{ props = #'P_basic'{ headers = Headers }}}) -> + case rabbit_mqtt_util:table_lookup(Headers, <<"x-mqtt-dup">>) of + undefined -> Redelivered; + {bool, Dup} -> Redelivered orelse Dup + end. + +next_msg_id(PState = #proc_state{ message_id = 16#ffff }) -> + PState #proc_state{ message_id = 1 }; +next_msg_id(PState = #proc_state{ message_id = MsgId }) -> + PState #proc_state{ message_id = MsgId + 1 }. + +%% decide at which qos level to deliver based on subscription +%% and the message publish qos level. non-MQTT publishes are +%% assumed to be qos 1, regardless of delivery_mode. +delivery_qos(Tag, _Headers, #proc_state{ consumer_tags = {Tag, _} }) -> + {?QOS_0, ?QOS_0}; +delivery_qos(Tag, Headers, #proc_state{ consumer_tags = {_, Tag} }) -> + case rabbit_mqtt_util:table_lookup(Headers, <<"x-mqtt-publish-qos">>) of + {byte, Qos} -> {lists:min([Qos, ?QOS_1]), ?QOS_1}; + undefined -> {?QOS_1, ?QOS_1} + end. + +maybe_clean_sess(PState = #proc_state { clean_sess = false }) -> + {_Queue, PState1} = ensure_queue(?QOS_1, PState), + PState1; +maybe_clean_sess(PState = #proc_state { clean_sess = true, + connection = Conn, + client_id = ClientId }) -> + {_, Queue} = rabbit_mqtt_util:subcription_queue_name(ClientId), + {ok, Channel} = amqp_connection:open_channel(Conn), + try amqp_channel:call(Channel, #'queue.delete'{ queue = Queue }) of + #'queue.delete_ok'{} -> ok = amqp_channel:close(Channel) + catch + exit:_Error -> ok + end, + PState. + +%%---------------------------------------------------------------------------- + +make_will_msg(#mqtt_frame_connect{ will_flag = false }) -> + undefined; +make_will_msg(#mqtt_frame_connect{ will_retain = Retain, + will_qos = Qos, + will_topic = Topic, + will_msg = Msg }) -> + #mqtt_msg{ retain = Retain, + qos = Qos, + topic = Topic, + dup = false, + payload = Msg }. + +process_login(UserBin, PassBin, ProtoVersion, + #proc_state{ channels = {undefined, undefined}, + socket = Sock }) -> + {VHost, UsernameBin} = get_vhost_username(UserBin), + case amqp_connection:start(#amqp_params_direct{ + username = UsernameBin, + password = PassBin, + virtual_host = VHost, + adapter_info = adapter_info(Sock, ProtoVersion)}) of + {ok, Connection} -> + case rabbit_access_control:check_user_loopback(UsernameBin, Sock) of + ok -> {?CONNACK_ACCEPT, Connection}; + not_allowed -> amqp_connection:close(Connection), + rabbit_log:warning( + "MQTT login failed for ~p access_refused " + "(access must be from localhost)~n", + [binary_to_list(UsernameBin)]), + ?CONNACK_AUTH + end; + {error, {auth_failure, Explanation}} -> + rabbit_log:error("MQTT login failed for ~p auth_failure: ~s~n", + [binary_to_list(UserBin), Explanation]), + ?CONNACK_CREDENTIALS; + {error, access_refused} -> + rabbit_log:warning("MQTT login failed for ~p access_refused " + "(vhost access not allowed)~n", + [binary_to_list(UserBin)]), + ?CONNACK_AUTH + end. + +get_vhost_username(UserBin) -> + %% split at the last colon, disallowing colons in username + case re:split(UserBin, ":(?!.*?:)") of + [Vhost, UserName] -> {Vhost, UserName}; + [UserBin] -> {rabbit_mqtt_util:env(vhost), UserBin} + end. + +creds(User, Pass) -> + DefaultUser = rabbit_mqtt_util:env(default_user), + DefaultPass = rabbit_mqtt_util:env(default_pass), + Anon = rabbit_mqtt_util:env(allow_anonymous), + U = case {User =/= undefined, is_binary(DefaultUser), Anon =:= true} of + {true, _, _ } -> list_to_binary(User); + {false, true, true} -> DefaultUser; + _ -> nocreds + end, + case U of + nocreds -> + nocreds; + _ -> + case {Pass =/= undefined, is_binary(DefaultPass), Anon =:= true} of + {true, _, _ } -> {U, list_to_binary(Pass)}; + {false, true, true} -> {U, DefaultPass}; + _ -> {U, none} + end + end. + +supported_subs_qos(?QOS_0) -> ?QOS_0; +supported_subs_qos(?QOS_1) -> ?QOS_1; +supported_subs_qos(?QOS_2) -> ?QOS_1. + +delivery_mode(?QOS_0) -> 1; +delivery_mode(?QOS_1) -> 2. + +%% different qos subscriptions are received in different queues +%% with appropriate durability and timeout arguments +%% this will lead to duplicate messages for overlapping subscriptions +%% with different qos values - todo: prevent duplicates +ensure_queue(Qos, #proc_state{ channels = {Channel, _}, + client_id = ClientId, + clean_sess = CleanSess, + consumer_tags = {TagQ0, TagQ1} = Tags} = PState) -> + {QueueQ0, QueueQ1} = rabbit_mqtt_util:subcription_queue_name(ClientId), + Qos1Args = case {rabbit_mqtt_util:env(subscription_ttl), CleanSess} of + {undefined, _} -> + []; + {Ms, false} when is_integer(Ms) -> + [{<<"x-expires">>, long, Ms}]; + _ -> + [] + end, + QueueSetup = + case {TagQ0, TagQ1, Qos} of + {undefined, _, ?QOS_0} -> + {QueueQ0, + #'queue.declare'{ queue = QueueQ0, + durable = false, + auto_delete = true }, + #'basic.consume'{ queue = QueueQ0, + no_ack = true }}; + {_, undefined, ?QOS_1} -> + {QueueQ1, + #'queue.declare'{ queue = QueueQ1, + durable = true, + auto_delete = CleanSess, + arguments = Qos1Args }, + #'basic.consume'{ queue = QueueQ1, + no_ack = false }}; + {_, _, ?QOS_0} -> + {exists, QueueQ0}; + {_, _, ?QOS_1} -> + {exists, QueueQ1} + end, + case QueueSetup of + {Queue, Declare, Consume} -> + #'queue.declare_ok'{} = amqp_channel:call(Channel, Declare), + #'basic.consume_ok'{ consumer_tag = Tag } = + amqp_channel:call(Channel, Consume), + {Queue, PState #proc_state{ consumer_tags = setelement(Qos+1, Tags, Tag) }}; + {exists, Q} -> + {Q, PState} + end. + +send_will(PState = #proc_state{ will_msg = WillMsg }) -> + amqp_pub(WillMsg, PState). + +amqp_pub(undefined, PState) -> + PState; + +%% set up a qos1 publishing channel if necessary +%% this channel will only be used for publishing, not consuming +amqp_pub(Msg = #mqtt_msg{ qos = ?QOS_1 }, + PState = #proc_state{ channels = {ChQos0, undefined}, + awaiting_seqno = undefined, + connection = Conn }) -> + {ok, Channel} = amqp_connection:open_channel(Conn), + #'confirm.select_ok'{} = amqp_channel:call(Channel, #'confirm.select'{}), + amqp_channel:register_confirm_handler(Channel, self()), + amqp_pub(Msg, PState #proc_state{ channels = {ChQos0, Channel}, + awaiting_seqno = 1 }); + +amqp_pub(#mqtt_msg{ qos = Qos, + topic = Topic, + dup = Dup, + message_id = MessageId, + payload = Payload }, + PState = #proc_state{ channels = {ChQos0, ChQos1}, + exchange = Exchange, + unacked_pubs = UnackedPubs, + awaiting_seqno = SeqNo }) -> + Method = #'basic.publish'{ exchange = Exchange, + routing_key = + rabbit_mqtt_util:mqtt2amqp(Topic)}, + Headers = [{<<"x-mqtt-publish-qos">>, byte, Qos}, + {<<"x-mqtt-dup">>, bool, Dup}], + Msg = #amqp_msg{ props = #'P_basic'{ headers = Headers, + delivery_mode = delivery_mode(Qos)}, + payload = Payload }, + {UnackedPubs1, Ch, SeqNo1} = + case Qos =:= ?QOS_1 andalso MessageId =/= undefined of + true -> {gb_trees:enter(SeqNo, MessageId, UnackedPubs), ChQos1, + SeqNo + 1}; + false -> {UnackedPubs, ChQos0, SeqNo} + end, + amqp_channel:cast_flow(Ch, Method, Msg), + PState #proc_state{ unacked_pubs = UnackedPubs1, + awaiting_seqno = SeqNo1 }. + +adapter_info(Sock, ProtoVer) -> + amqp_connection:socket_adapter_info( + Sock, {'MQTT', integer_to_list(ProtoVer)}). + +send_client(Frame, #proc_state{ socket = Sock }) -> + %rabbit_log:info("MQTT sending frame ~p ~n", [Frame]), + rabbit_net:port_command(Sock, rabbit_mqtt_frame:serialise(Frame)). + +close_connection(PState = #proc_state{ connection = undefined }) -> + PState; +close_connection(PState = #proc_state{ connection = Connection, + client_id = ClientId }) -> + % todo: maybe clean session + case ClientId of + undefined -> ok; + _ -> ok = rabbit_mqtt_collector:unregister(ClientId, self()) + end, + %% ignore noproc or other exceptions to avoid debris + catch amqp_connection:close(Connection), + PState #proc_state{ channels = {undefined, undefined}, + connection = undefined }. + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_reader.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_reader.erl new file mode 100644 index 0000000..d2d6b04 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_reader.erl @@ -0,0 +1,284 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mqtt_reader). +-behaviour(gen_server2). + +-export([start_link/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + code_change/3, terminate/2]). + +-export([conserve_resources/3, start_keepalive/2]). + +-include_lib("amqp_client/include/amqp_client.hrl"). +-include("rabbit_mqtt.hrl"). + +%%---------------------------------------------------------------------------- + +start_link() -> + gen_server2:start_link(?MODULE, [], []). + +conserve_resources(Pid, _, Conserve) -> + Pid ! {conserve_resources, Conserve}, + ok. + +%%---------------------------------------------------------------------------- + +init([]) -> + {ok, undefined, hibernate, {backoff, 1000, 1000, 10000}}. + +handle_call(Msg, From, State) -> + {stop, {mqtt_unexpected_call, Msg, From}, State}. + +handle_cast({go, Sock0, SockTransform, KeepaliveSup}, undefined) -> + process_flag(trap_exit, true), + case rabbit_net:connection_string(Sock0, inbound) of + {ok, ConnStr} -> + log(info, "accepting MQTT connection ~p (~s)~n", [self(), ConnStr]), + case SockTransform(Sock0) of + {ok, Sock} -> + rabbit_alarm:register( + self(), {?MODULE, conserve_resources, []}), + ProcessorState = rabbit_mqtt_processor:initial_state(Sock), + {noreply, + control_throttle( + #state{socket = Sock, + conn_name = ConnStr, + await_recv = false, + connection_state = running, + keepalive = {none, none}, + keepalive_sup = KeepaliveSup, + conserve = false, + parse_state = rabbit_mqtt_frame:initial_state(), + proc_state = ProcessorState }), + hibernate}; + {error, Reason} -> + rabbit_net:fast_close(Sock0), + {stop, {network_error, Reason, ConnStr}, undefined} + end; + {network_error, Reason} -> + rabbit_net:fast_close(Sock0), + {stop, {shutdown, Reason}, undefined}; + {error, enotconn} -> + rabbit_net:fast_close(Sock0), + {stop, shutdown, undefined}; + {error, Reason} -> + rabbit_net:fast_close(Sock0), + {stop, {network_error, Reason}, undefined} + end; + +handle_cast(duplicate_id, + State = #state{ proc_state = PState, + conn_name = ConnName }) -> + log(warning, "MQTT disconnecting duplicate client id ~p (~p)~n", + [rabbit_mqtt_processor:info(client_id, PState), ConnName]), + {stop, {shutdown, duplicate_id}, State}; + +handle_cast(Msg, State) -> + {stop, {mqtt_unexpected_cast, Msg}, State}. + +handle_info({#'basic.deliver'{}, #amqp_msg{}} = Delivery, + State = #state{ proc_state = ProcState }) -> + callback_reply(State, rabbit_mqtt_processor:amqp_callback(Delivery, ProcState)); + +handle_info(#'basic.ack'{} = Ack, State = #state{ proc_state = ProcState }) -> + callback_reply(State, rabbit_mqtt_processor:amqp_callback(Ack, ProcState)); + +handle_info(#'basic.consume_ok'{}, State) -> + {noreply, State, hibernate}; + +handle_info(#'basic.cancel'{}, State) -> + {stop, {shutdown, subscription_cancelled}, State}; + +handle_info({'EXIT', _Conn, Reason}, State) -> + {stop, {connection_died, Reason}, State}; + +handle_info({inet_reply, _Ref, ok}, State) -> + {noreply, State, hibernate}; + +handle_info({inet_async, Sock, _Ref, {ok, Data}}, + State = #state{ socket = Sock }) -> + process_received_bytes( + Data, control_throttle(State #state{ await_recv = false })); + +handle_info({inet_async, _Sock, _Ref, {error, Reason}}, State = #state {}) -> + network_error(Reason, State); + +handle_info({inet_reply, _Sock, {error, Reason}}, State = #state {}) -> + network_error(Reason, State); + +handle_info({conserve_resources, Conserve}, State) -> + {noreply, control_throttle(State #state{ conserve = Conserve }), hibernate}; + +handle_info({bump_credit, Msg}, State) -> + credit_flow:handle_bump_msg(Msg), + {noreply, control_throttle(State), hibernate}; + +handle_info({start_keepalives, Keepalive}, + State = #state { keepalive_sup = KeepaliveSup, socket = Sock }) -> + %% Only the client has the responsibility for sending keepalives + SendFun = fun() -> ok end, + Parent = self(), + ReceiveFun = fun() -> Parent ! keepalive_timeout end, + Heartbeater = rabbit_heartbeat:start( + KeepaliveSup, Sock, 0, SendFun, Keepalive, ReceiveFun), + {noreply, State #state { keepalive = Heartbeater }}; + +handle_info(keepalive_timeout, State = #state { conn_name = ConnStr }) -> + log(error, "closing MQTT connection ~p (keepalive timeout)~n", [ConnStr]), + {stop, {shutdown, keepalive_timeout}, State}; + +handle_info(Msg, State) -> + {stop, {mqtt_unexpected_msg, Msg}, State}. + +terminate({network_error, {ssl_upgrade_error, closed}, ConnStr}, _State) -> + log(error, "MQTT detected TLS upgrade error on ~s: connection closed~n", + [ConnStr]); + +terminate({network_error, + {ssl_upgrade_error, + {tls_alert, "handshake failure"}}, ConnStr}, _State) -> + log(error, "MQTT detected TLS upgrade error on ~s: handshake failure~n", + [ConnStr]); + +terminate({network_error, + {ssl_upgrade_error, + {tls_alert, "unknown ca"}}, ConnStr}, _State) -> + log(error, "MQTT detected TLS certificate verification error on ~s: alert 'unknown CA'~n", + [ConnStr]); + +terminate({network_error, + {ssl_upgrade_error, + {tls_alert, Alert}}, ConnStr}, _State) -> + log(error, "MQTT detected TLS upgrade error on ~s: alert ~s~n", + [ConnStr, Alert]); + +terminate({network_error, {ssl_upgrade_error, Reason}, ConnStr}, _State) -> + log(error, "MQTT detected TLS upgrade error on ~s: ~p~n", + [ConnStr, Reason]); + +terminate({network_error, Reason, ConnStr}, _State) -> + log(error, "MQTT detected network error on ~s: ~p~n", + [ConnStr, Reason]); + +terminate({network_error, Reason}, _State) -> + log(error, "MQTT detected network error: ~p~n", [Reason]); + +terminate(normal, State = #state{proc_state = ProcState, + conn_name = ConnName}) -> + rabbit_mqtt_processor:close_connection(ProcState), + log(info, "closing MQTT connection ~p (~s)~n", [self(), ConnName]), + ok; + +terminate(_Reason, State = #state{proc_state = ProcState}) -> + rabbit_mqtt_processor:close_connection(ProcState), + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%---------------------------------------------------------------------------- + +process_received_bytes(<<>>, State) -> + {noreply, State, hibernate}; +process_received_bytes(Bytes, + State = #state{ parse_state = ParseState, + proc_state = ProcState, + conn_name = ConnStr }) -> + case rabbit_mqtt_frame:parse(Bytes, ParseState) of + {more, ParseState1} -> + {noreply, + control_throttle( State #state{ parse_state = ParseState1 }), + hibernate}; + {ok, Frame, Rest} -> + case rabbit_mqtt_processor:process_frame(Frame, ProcState) of + {ok, ProcState1} -> + PS = rabbit_mqtt_frame:initial_state(), + process_received_bytes( + Rest, + State #state{ parse_state = PS, + proc_state = ProcState1 }); + {error, Reason, ProcState1} -> + log(info, "MQTT protocol error ~p for connection ~p~n", + [ConnStr, Reason]), + {stop, {shutdown, Reason}, pstate(State, ProcState1)}; + {error, Error} -> + log(error, "MQTT detected framing error '~p' for connection ~p~n", + [Error, ConnStr]), + {stop, {shutdown, Error}, State}; + {stop, ProcState1} -> + {stop, normal, pstate(State, ProcState1)} + end; + {error, Error} -> + log(error, "MQTT detected framing error '~p' for connection ~p~n", + [ConnStr, Error]), + {stop, {shutdown, Error}, State} + end. + +callback_reply(State, {ok, ProcState}) -> + {noreply, pstate(State, ProcState), hibernate}; +callback_reply(State, {err, Reason, ProcState}) -> + {stop, Reason, pstate(State, ProcState)}. + +start_keepalive(_, 0 ) -> ok; +start_keepalive(Pid, Keepalive) -> Pid ! {start_keepalives, Keepalive}. + +pstate(State = #state {}, PState = #proc_state{}) -> + State #state{ proc_state = PState }. + +%%---------------------------------------------------------------------------- + +log(Level, Fmt) -> rabbit_log:log(connection, Level, Fmt, []). +log(Level, Fmt, Args) -> rabbit_log:log(connection, Level, Fmt, Args). + +send_will_and_terminate(PState, State) -> + rabbit_mqtt_processor:send_will(PState), + % todo: flush channel after publish + {stop, {shutdown, conn_closed}, State}. + +network_error(closed, + State = #state{ conn_name = ConnStr, + proc_state = PState }) -> + log(info, "MQTT detected network error for ~p: peer closed TCP connection~n", + [ConnStr]), + send_will_and_terminate(PState, State); + +network_error(Reason, + State = #state{ conn_name = ConnStr, + proc_state = PState }) -> + log(info, "MQTT detected network error for ~p: ~p~n", [ConnStr, Reason]), + send_will_and_terminate(PState, State). + +run_socket(State = #state{ connection_state = blocked }) -> + State; +run_socket(State = #state{ await_recv = true }) -> + State; +run_socket(State = #state{ socket = Sock }) -> + rabbit_net:async_recv(Sock, 0, infinity), + State#state{ await_recv = true }. + +control_throttle(State = #state{ connection_state = Flow, + conserve = Conserve }) -> + case {Flow, Conserve orelse credit_flow:blocked()} of + {running, true} -> ok = rabbit_heartbeat:pause_monitor( + State#state.keepalive), + State #state{ connection_state = blocked }; + {blocked, false} -> ok = rabbit_heartbeat:resume_monitor( + State#state.keepalive), + run_socket(State #state{ + connection_state = running }); + {_, _} -> run_socket(State) + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_sup.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_sup.erl new file mode 100644 index 0000000..a2b3a23 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_sup.erl @@ -0,0 +1,81 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mqtt_sup). +-behaviour(supervisor2). + +-define(MAX_WAIT, 16#ffffffff). + +-export([start_link/2, init/1]). + +-export([start_client/1, start_ssl_client/2]). + +start_link(Listeners, []) -> + supervisor2:start_link({local, ?MODULE}, ?MODULE, [Listeners]). + +init([{Listeners, SslListeners}]) -> + {ok, SocketOpts} = application:get_env(rabbitmq_mqtt, tcp_listen_options), + SslOpts = case SslListeners of + [] -> none; + _ -> rabbit_networking:ensure_ssl() + end, + {ok, {{one_for_all, 10, 10}, + [{collector, + {rabbit_mqtt_collector, start_link, []}, + transient, ?MAX_WAIT, worker, [rabbit_mqtt_collector]}, + {rabbit_mqtt_client_sup, + {rabbit_client_sup, start_link, [{local, rabbit_mqtt_client_sup}, + {rabbit_mqtt_connection_sup, start_link, []}]}, + transient, infinity, supervisor, [rabbit_client_sup]} | + listener_specs(fun tcp_listener_spec/1, + [SocketOpts], Listeners) ++ + listener_specs(fun ssl_listener_spec/1, + [SocketOpts, SslOpts], SslListeners)]}}. + +listener_specs(Fun, Args, Listeners) -> + [Fun([Address | Args]) || + Listener <- Listeners, + Address <- rabbit_networking:tcp_listener_addresses(Listener)]. + +tcp_listener_spec([Address, SocketOpts]) -> + rabbit_networking:tcp_listener_spec( + rabbit_mqtt_listener_sup, Address, SocketOpts, + mqtt, "MQTT TCP Listener", + {?MODULE, start_client, []}). + +ssl_listener_spec([Address, SocketOpts, SslOpts]) -> + rabbit_networking:tcp_listener_spec( + rabbit_mqtt_listener_sup, Address, SocketOpts, + 'mqtt/ssl', "MQTT SSL Listener", + {?MODULE, start_ssl_client, [SslOpts]}). + +start_client(Sock, SockTransform) -> + {ok, _, {KeepaliveSup, Reader}} = + supervisor2:start_child(rabbit_mqtt_client_sup, []), + ok = rabbit_net:controlling_process(Sock, Reader), + ok = gen_server2:cast(Reader, {go, Sock, SockTransform, KeepaliveSup}), + + %% see comment in rabbit_networking:start_client/2 + gen_event:which_handlers(error_logger), + Reader. + +start_client(Sock) -> + start_client(Sock, fun (S) -> {ok, S} end). + +start_ssl_client(SslOpts, Sock) -> + Transform = rabbit_networking:ssl_transform_fun(SslOpts), + start_client(Sock, Transform). + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_util.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_util.erl new file mode 100644 index 0000000..c675ebd --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbit_mqtt_util.erl @@ -0,0 +1,53 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mqtt_util). + +-include("rabbit_mqtt.hrl"). + +-compile(export_all). + +subcription_queue_name(ClientId) -> + Base = "mqtt-subscription-" ++ ClientId ++ "qos", + {list_to_binary(Base ++ "0"), list_to_binary(Base ++ "1")}. + +%% amqp mqtt descr +%% * + match one topic level +%% # # match multiple topic levels +%% . / topic level separator +mqtt2amqp(Topic) -> + erlang:iolist_to_binary( + re:replace(re:replace(Topic, "/", ".", [global]), + "[\+]", "*", [global])). + +amqp2mqtt(Topic) -> + erlang:iolist_to_binary( + re:replace(re:replace(Topic, "[\*]", "+", [global]), + "[\.]", "/", [global])). + +gen_client_id() -> + lists:nthtail(1, rabbit_guid:string(rabbit_guid:gen_secure(), [])). + +env(Key) -> + case application:get_env(rabbitmq_mqtt, Key) of + {ok, Val} -> Val; + undefined -> undefined + end. + +table_lookup(undefined, _Key) -> + undefined; +table_lookup(Table, Key) -> + rabbit_misc:table_lookup(Table, Key). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbitmq_mqtt.app.src b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbitmq_mqtt.app.src new file mode 100644 index 0000000..b687c0a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/src/rabbitmq_mqtt.app.src @@ -0,0 +1,21 @@ +{application, rabbitmq_mqtt, + [{description, "RabbitMQ MQTT Adapter"}, + {vsn, "%%VSN%%"}, + {modules, []}, + {registered, []}, + {mod, {rabbit_mqtt, []}}, + {env, [{default_user, <<"guest">>}, + {default_pass, <<"guest">>}, + {allow_anonymous, true}, + {vhost, <<"/">>}, + {exchange, <<"amq.topic">>}, + {subscription_ttl, 1800000}, % 30 min + {prefetch, 10}, + {ssl_listeners, []}, + {tcp_listeners, [1883]}, + {tcp_listen_options, [binary, + {packet, raw}, + {reuseaddr, true}, + {backlog, 128}, + {nodelay, true}]}]}, + {applications, [kernel, stdlib, rabbit, amqp_client]}]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/test/Makefile b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/test/Makefile new file mode 100644 index 0000000..6538214 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-mqtt/test/Makefile @@ -0,0 +1,46 @@ +UPSTREAM_GIT=https://git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.java.git +REVISION=00b5b2f99ae8410b7d96d106e080a092c5f92546 + +JC=javac + +TEST_SRC=src +CHECKOUT_DIR=test_client +PAHO_JAR_NAME=org.eclipse.paho.client.mqttv3.jar +PAHO_JAR=$(CHECKOUT_DIR)/org.eclipse.paho.client.mqttv3/$(PAHO_JAR_NAME) +JUNIT_JAR=../lib/junit.jar +JAVA_AMQP_DIR=../../rabbitmq-java-client/ +JAVA_AMQP_CLASSES=$(JAVA_AMQP_DIR)build/classes/ + +TEST_SRCS:=$(shell find $(TEST_SRC) -name '*.java') +ALL_CLASSES:=$(foreach f,$(shell find src -name '*.class'),'$(f)') +TEST_CLASSES:=$(TEST_SRCS:.java=.class) +CP:=$(PAHO_JAR):$(JUNIT_JAR):$(TEST_SRC):$(JAVA_AMQP_CLASSES) + +define class_from_path +$(subst .class,,$(subst src.,,$(subst /,.,$(1)))) +endef + +.PHONY: test +test: $(TEST_CLASSES) build_java_amqp + $(foreach test,$(TEST_CLASSES),CLASSPATH=$(CP) java junit.textui.TestRunner -text $(call class_from_path,$(test))) + +clean: + rm -rf $(PAHO_JAR) $(ALL_CLASSES) + +distclean: clean + rm -rf $(CHECKOUT_DIR) + +$(CHECKOUT_DIR): + git clone $(UPSTREAM_GIT) $@ + (cd $@ && git checkout $(REVISION)) || rm -rf $@ + +$(PAHO_JAR): $(CHECKOUT_DIR) + ant -buildfile $ receivedMessages; + + private final byte[] payload = "payload".getBytes(); + private final String topic = "test-topic"; + private int testDelay = 2000; + private long lastReceipt; + private boolean expectConnectionFailure; + + private ConnectionFactory connectionFactory; + private Connection conn; + private Channel ch; + + // override 10s limit + private class MyConnOpts extends MqttConnectOptions { + private int keepAliveInterval = 60; + @Override + public void setKeepAliveInterval(int keepAliveInterval) { + this.keepAliveInterval = keepAliveInterval; + } + @Override + public int getKeepAliveInterval() { + return keepAliveInterval; + } + } + + @Override + public void setUp() throws MqttException { + clientId = getClass().getSimpleName() + ((int) (10000*Math.random())); + clientId2 = clientId + "-2"; + client = new MqttClient(brokerUrl, clientId, null); + client2 = new MqttClient(brokerUrl, clientId2, null); + conOpt = new MyConnOpts(); + setConOpts(conOpt); + receivedMessages = new ArrayList(); + expectConnectionFailure = false; + } + + @Override + public void tearDown() throws MqttException { + // clean any sticky sessions + setConOpts(conOpt); + client = new MqttClient(brokerUrl, clientId, null); + try { + client.connect(conOpt); + client.disconnect(); + } catch (Exception _) {} + + client2 = new MqttClient(brokerUrl, clientId2, null); + try { + client2.connect(conOpt); + client2.disconnect(); + } catch (Exception _) {} + } + + private void setUpAmqp() throws IOException { + connectionFactory = new ConnectionFactory(); + connectionFactory.setHost(host); + conn = connectionFactory.newConnection(); + ch = conn.createChannel(); + } + + private void tearDownAmqp() throws IOException { + conn.close(); + } + + private void setConOpts(MqttConnectOptions conOpts) { + // provide authentication if the broker needs it + // conOpts.setUserName("guest"); + // conOpts.setPassword("guest".toCharArray()); + conOpts.setCleanSession(true); + conOpts.setKeepAliveInterval(60); + } + + public void testConnectFirst() throws MqttException, IOException, InterruptedException { + NetworkModule networkModule = new TCPNetworkModule(SocketFactory.getDefault(), host, port, ""); + networkModule.start(); + MqttInputStream mqttIn = new MqttInputStream (networkModule.getInputStream()); + MqttOutputStream mqttOut = new MqttOutputStream(networkModule.getOutputStream()); + try { + mqttOut.write(new MqttPingReq()); + mqttOut.flush(); + mqttIn.readMqttWireMessage(); + fail("Error expected if CONNECT is not first packet"); + } catch (IOException _) {} + } + + public void testInvalidUser() throws MqttException { + conOpt.setUserName("invalid-user"); + try { + client.connect(conOpt); + fail("Authentication failure expected"); + } catch (MqttException ex) { + Assert.assertEquals(MqttException.REASON_CODE_FAILED_AUTHENTICATION, ex.getReasonCode()); + } + } + + public void testInvalidPassword() throws MqttException { + conOpt.setUserName("invalid-user"); + conOpt.setPassword("invalid-password".toCharArray()); + try { + client.connect(conOpt); + fail("Authentication failure expected"); + } catch (MqttException ex) { + Assert.assertEquals(MqttException.REASON_CODE_FAILED_AUTHENTICATION, ex.getReasonCode()); + } + } + + + public void testSubscribeQos0() throws MqttException, InterruptedException { + client.connect(conOpt); + client.setCallback(this); + client.subscribe(topic, 0); + + publish(client, topic, 0, payload); + Thread.sleep(testDelay); + Assert.assertEquals(1, receivedMessages.size()); + Assert.assertEquals(true, Arrays.equals(receivedMessages.get(0).getPayload(), payload)); + Assert.assertEquals(0, receivedMessages.get(0).getQos()); + client.disconnect(); + } + + public void testSubscribeUnsubscribe() throws MqttException, InterruptedException { + client.connect(conOpt); + client.setCallback(this); + client.subscribe(topic, 0); + + publish(client, topic, 1, payload); + Thread.sleep(testDelay); + Assert.assertEquals(1, receivedMessages.size()); + Assert.assertEquals(true, Arrays.equals(receivedMessages.get(0).getPayload(), payload)); + Assert.assertEquals(0, receivedMessages.get(0).getQos()); + + client.unsubscribe(topic); + publish(client, topic, 0, payload); + Thread.sleep(testDelay); + Assert.assertEquals(1, receivedMessages.size()); + client.disconnect(); + } + + public void testSubscribeQos1() throws MqttException, InterruptedException { + client.connect(conOpt); + client.setCallback(this); + client.subscribe(topic, 1); + + publish(client, topic, 0, payload); + publish(client, topic, 1, payload); + Thread.sleep(testDelay); + + Assert.assertEquals(2, receivedMessages.size()); + MqttMessage msg1 = receivedMessages.get(0); + MqttMessage msg2 = receivedMessages.get(1); + + Assert.assertEquals(true, Arrays.equals(msg1.getPayload(), payload)); + Assert.assertEquals(0, msg1.getQos()); + + Assert.assertEquals(true, Arrays.equals(msg2.getPayload(), payload)); + Assert.assertEquals(1, msg2.getQos()); + + client.disconnect(); + } + + public void testTopics() throws MqttException, InterruptedException { + client.connect(conOpt); + client.setCallback(this); + client.subscribe("/+/mid/#"); + String cases[] = {"/pre/mid2", "/mid", "/a/mid/b/c/d", "/frob/mid"}; + List expected = Arrays.asList("/a/mid/b/c/d", "/frob/mid"); + for(String example : cases){ + publish(client, example, 0, example.getBytes()); + } + Thread.sleep(testDelay); + Assert.assertEquals(expected.size(), receivedMessages.size()); + for (MqttMessage m : receivedMessages){ + expected.contains(new String(m.getPayload())); + } + client.disconnect(); + } + + public void testNonCleanSession() throws MqttException, InterruptedException { + conOpt.setCleanSession(false); + client.connect(conOpt); + client.subscribe(topic, 1); + client.disconnect(); + + client2.connect(conOpt); + publish(client2, topic, 1, payload); + client2.disconnect(); + + client.setCallback(this); + client.connect(conOpt); + + Thread.sleep(testDelay); + Assert.assertEquals(1, receivedMessages.size()); + Assert.assertEquals(true, Arrays.equals(receivedMessages.get(0).getPayload(), payload)); + client.disconnect(); + } + + public void testCleanSession() throws MqttException, InterruptedException { + conOpt.setCleanSession(false); + client.connect(conOpt); + client.subscribe(topic, 1); + client.disconnect(); + + client2.connect(conOpt); + publish(client2, topic, 1, payload); + client2.disconnect(); + + conOpt.setCleanSession(true); + client.connect(conOpt); + client.setCallback(this); + client.subscribe(topic, 1); + + Thread.sleep(testDelay); + Assert.assertEquals(0, receivedMessages.size()); + client.unsubscribe(topic); + client.disconnect(); + } + + public void testMultipleClientIds() throws MqttException, InterruptedException { + client.connect(conOpt); + client2 = new MqttClient(brokerUrl, clientId, null); + client2.connect(conOpt); + Thread.sleep(testDelay); + Assert.assertFalse(client.isConnected()); + client2.disconnect(); + } + + public void testPing() throws MqttException, InterruptedException { + conOpt.setKeepAliveInterval(1); + client.connect(conOpt); + Thread.sleep(3000); + Assert.assertEquals(true, client.isConnected()); + client.disconnect(); + } + + public void testWill() throws MqttException, InterruptedException, IOException { + client2.connect(conOpt); + client2.subscribe(topic); + client2.setCallback(this); + + final SocketFactory factory = SocketFactory.getDefault(); + final ArrayList sockets = new ArrayList(); + SocketFactory testFactory = new SocketFactory() { + public Socket createSocket(String s, int i) throws IOException { + Socket sock = factory.createSocket(s, i); + sockets.add(sock); + return sock; + } + public Socket createSocket(String s, int i, InetAddress a, int i1) throws IOException { + return null; + } + public Socket createSocket(InetAddress a, int i) throws IOException { + return null; + } + public Socket createSocket(InetAddress a, int i, InetAddress a1, int i1) throws IOException { + return null; + } + @Override + public Socket createSocket() throws IOException { + Socket sock = new Socket(); + sockets.add(sock); + return sock; + } + }; + conOpt.setSocketFactory(testFactory); + MqttTopic willTopic = client.getTopic(topic); + conOpt.setWill(willTopic, payload, 0, false); + conOpt.setCleanSession(false); + client.connect(conOpt); + + Assert.assertEquals(1, sockets.size()); + expectConnectionFailure = true; + sockets.get(0).close(); + Thread.sleep(testDelay); + + Assert.assertEquals(1, receivedMessages.size()); + Assert.assertEquals(true, Arrays.equals(receivedMessages.get(0).getPayload(), payload)); + client2.disconnect(); + } + + public void testSubscribeMultiple() throws MqttException { + client.connect(conOpt); + publish(client, "/topic/1", 1, "msq1-qos1".getBytes()); + + client2.connect(conOpt); + client2.setCallback(this); + client2.subscribe("/topic/#"); + client2.subscribe("/topic/#"); + + publish(client, "/topic/2", 0, "msq2-qos0".getBytes()); + publish(client, "/topic/3", 1, "msq3-qos1".getBytes()); + publish(client, topic, 0, "msq4-qos0".getBytes()); + publish(client, topic, 1, "msq4-qos1".getBytes()); + + Assert.assertEquals(2, receivedMessages.size()); + client.disconnect(); + client2.disconnect(); + } + + public void testPublishMultiple() throws MqttException, InterruptedException { + int pubCount = 50; + for (int subQos=0; subQos < 2; subQos++){ + for (int pubQos=0; pubQos < 2; pubQos++){ + client.connect(conOpt); + client.subscribe(topic, subQos); + client.setCallback(this); + long start = System.currentTimeMillis(); + for (int i=0; i>}]}]} + ]}, + {destinations, [{broker, "amqp://"}]}, + {queue, <<"static">>}, + {publish_fields, [ {exchange, <<"">>}, + {routing_key, <<"static2">>} + ]} + ]} + ]} + ]} +]. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/package.mk b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/package.mk new file mode 100644 index 0000000..6c3bac2 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/package.mk @@ -0,0 +1,9 @@ +RELEASABLE:=true +DEPS:=rabbitmq-management rabbitmq-shovel +WITH_BROKER_TEST_COMMANDS:=rabbit_shovel_mgmt_test_all:all_tests() +WITH_BROKER_TEST_CONFIG:=$(PACKAGE_DIR)/etc/rabbit-test + +CONSTRUCT_APP_PREREQS:=$(shell find $(PACKAGE_DIR)/priv -type f) +define construct_app_commands + cp -r $(PACKAGE_DIR)/priv $(APP_DIR) +endef diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/priv/www/js/shovel.js b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/priv/www/js/shovel.js new file mode 100644 index 0000000..42cad36 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/priv/www/js/shovel.js @@ -0,0 +1,88 @@ +dispatcher_add(function(sammy) { + sammy.get('#/shovels', function() { + render({'shovels': {path: '/shovels', + options: {vhost:true}}}, + 'shovels', '#/shovels'); + }); + sammy.get('#/dynamic-shovels', function() { + render({'shovels': {path: '/parameters/shovel', + options:{vhost:true}}, + 'vhosts': '/vhosts'}, + 'dynamic-shovels', '#/dynamic-shovels'); + }); + sammy.get('#/dynamic-shovels/:vhost/:id', function() { + render({'shovel': '/parameters/shovel/' + esc(this.params['vhost']) + '/' + esc(this.params['id'])}, + 'dynamic-shovel', '#/dynamic-shovels'); + }); + sammy.put('#/shovel-parameters', function() { + var num_keys = ['prefetch-count', 'reconnect-delay']; + var bool_keys = ['add-forward-headers']; + var arrayable_keys = ['src-uri', 'dest-uri']; + put_parameter(this, [], num_keys, bool_keys, arrayable_keys); + return false; + }); + sammy.del('#/shovel-parameters', function() { + if (sync_delete(this, '/parameters/:component/:vhost/:name')) + go_to('#/dynamic-shovels'); + return false; + }); +}); + + +NAVIGATION['Admin'][0]['Shovel Status'] = ['#/shovels', "monitoring"]; +NAVIGATION['Admin'][0]['Shovel Management'] = ['#/dynamic-shovels', "policymaker"]; + +HELP['shovel-uri'] = + 'Both source and destination can be either a local or remote broker. See the "URI examples" pane for examples of how to construct URIs. If connecting to a cluster, you can enter several URIs here separated by spaces.'; + +HELP['shovel-queue-exchange'] = + 'You can set both source and destination as either a queue or an exchange. If you choose "queue", it will be declared beforehand; if you choose "exchange" it will not, but an appropriate binding and queue will be created when the source is an exchange.'; + +HELP['shovel-prefetch'] = + 'Maximum number of unacknowledged messages that may be in flight over a shovel at one time. Defaults to 1000 if not set.'; + +HELP['shovel-reconnect'] = + 'Time in seconds to wait after a shovel goes down before attempting reconnection. Defaults to 1 if not set.'; + +HELP['shovel-forward-headers'] = + 'Whether to add headers to the shovelled messages indicating where they have been shovelled from and to. Defaults to false if not set.'; + +HELP['shovel-ack-mode'] = + '
\ +
on-confirm
\ +
Messages are acknowledged at the source after they have been confirmed at the destination. Handles network errors and broker failures without losing messages. The slowest option, and the default.
\ +
on-publish
\ +
Messages are acknowledged at the source after they have been published at the destination. Handles network errors without losing messages, but may lose messages in the event of broker failures.
\ +
no-ack
\ +
Message acknowledgements are not used. The fastest option, but may lose messages in the event of network or broker failures.
\ +
'; + +HELP['shovel-delete-after'] = + '
\ +
Never
\ +
The shovel never deletes itself; it will persist until it is explicitly removed.
\ +
After initial length transferred
\ +
The shovel will check the length of the queue when it starts up. It will transfer that many messages, and then delete itself.
\ +
'; + +function link_shovel(vhost, name) { + return _link_to(fmt_escape_html(name), '#/dynamic-shovels/' + esc(vhost) + '/' + esc(name)); +} + +function fmt_shovel_endpoint(prefix, shovel) { + var txt = ''; + if (shovel[prefix + '-queue']) { + txt += fmt_string(shovel[prefix + '-queue']) + 'queue'; + } else { + if (shovel[prefix + '-exchange']) { + txt += fmt_string(shovel[prefix + '-exchange']); + } else { + txt += 'as published'; + } + if (shovel[prefix + '-exchange-key']) { + txt += ' : ' + fmt_string(shovel[prefix + '-exchange-key']); + } + txt += 'exchange'; + } + return txt; +} diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/priv/www/js/tmpl/dynamic-shovel.ejs b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/priv/www/js/tmpl/dynamic-shovel.ejs new file mode 100644 index 0000000..61b4aa0 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/priv/www/js/tmpl/dynamic-shovel.ejs @@ -0,0 +1,61 @@ +

Dynamic Shovel: <%= fmt_string(shovel.name) %>

+ +
+

Overview

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Virtual host<%= fmt_string(shovel.vhost) %>
Source<%= fmt_string(shovel.value['src-uri']) %>
<%= fmt_shovel_endpoint('src', shovel.value) %>
Destination<%= fmt_string(shovel.value['dest-uri']) %>
<%= fmt_shovel_endpoint('dest', shovel.value) %>
Prefetch count<%= fmt_string(shovel.value['prefetch-count']) %>
Reconnect delay<%= fmt_time(shovel.value['reconnect-delay'], 's') %>
Add headers<%= fmt_boolean(shovel.value['add-forward-headers']) %>
Ack mode<%= fmt_string(shovel.value['ack-mode']) %>
Auto-delete<%= fmt_string(shovel.value['delete-after']) %>
+
+
+ +
+

Delete this shovel

+
+
+ + + + +
+
+
diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/priv/www/js/tmpl/dynamic-shovels.ejs b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/priv/www/js/tmpl/dynamic-shovels.ejs new file mode 100644 index 0000000..cb82d8b --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/priv/www/js/tmpl/dynamic-shovels.ejs @@ -0,0 +1,237 @@ +

Dynamic Shovels

+
+

Shovels

+
+<% if (shovels.length > 0) { %> + + + +<% if (vhosts_interesting) { %> + +<% } %> + + + + + + + + + + + +<% + for (var i = 0; i < shovels.length; i++) { + var shovel = shovels[i]; +%> + > +<% if (vhosts_interesting) { %> + +<% } %> + + + + + + + + + + + +<% } %> + +
Virtual HostNameSourceDestinationPrefetch CountReconnect DelayAdd headersAck modeAuto-delete
<%= fmt_string(shovel.vhost) %><%= link_shovel(shovel.vhost, shovel.name) %><%= fmt_shortened_uri(shovel.value['src-uri']) %><%= fmt_shovel_endpoint('src', shovel.value) %><%= fmt_shortened_uri(shovel.value['dest-uri']) %><%= fmt_shovel_endpoint('dest', shovel.value) %><%= shovel.value['prefetch-count'] %><%= fmt_time(shovel.value['reconnect-delay'], 's') %><%= fmt_boolean(shovel.value['add-forward-headers']) %><%= fmt_string(shovel.value['ack-mode']) %><%= fmt_string(shovel.value['delete-after']) %>
+<% } else { %> +

... no shovels ...

+<% } %> +
+
+ +
+

Add a new shovel

+
+
+ + +<% if (vhosts_interesting) { %> + + + + +<% } else { %> + +<% } %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
*
Source: + + + + + + + + + +
+ URI + + + + +
* +
+ +
+ +
+
Destination: + + + + + + + + + +
+ URI + + + + +
* +
+ +
+ +
+
+ +
+ + s
+ + + +
+ + + +
+ + + +
+ +
+
+
+
+

URI examples

+
+
    +
  • + amqp://
    + connect to local server as default user +
  • +
  • + amqp://user@/my-vhost
    + connect to local server with alternate user and virtual host + (passwords are not required for local connections) +
  • +
  • + amqp://server-name
    + connect to server-name, without SSL and default credentials +
  • +
  • + amqp://user:password@server-name/my-vhost
    + connect to server-name, with credentials and overridden + virtual host +
  • +
  • + amqps://user:password@server-name?cacertfile=/path/to/cacert.pem&certfile=/path/to/cert.pem&keyfile=/path/to/key.pem&verify=verify_peer
    + connect to server-name, with credentials and SSL +
  • +
  • + amqps://server-name?cacertfile=/path/to/cacert.pem&certfile=/path/to/cert.pem&keyfile=/path/to/key.pem&verify=verify_peer&fail_if_no_peer_cert=true&auth_mechanism=external
    + connect to server-name, with SSL and EXTERNAL authentication +
  • +
+
+
diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/priv/www/js/tmpl/shovels.ejs b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/priv/www/js/tmpl/shovels.ejs new file mode 100644 index 0000000..728bf44 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/priv/www/js/tmpl/shovels.ejs @@ -0,0 +1,70 @@ +

Shovel Status

+<% + var extra_width = 0; + if (vhosts_interesting) extra_width++; + if (nodes_interesting) extra_width++; +%> +
+<% if (shovels.length > 0) { %> + + + + +<% if (nodes_interesting) { %> + +<% } %> +<% if (vhosts_interesting) { %> + +<% } %> + + + + + + + +<% + for (var i = 0; i < shovels.length; i++) { + var shovel = shovels[i]; +%> + > + +<% if (nodes_interesting) { %> + +<% } %> +<% if (vhosts_interesting) { %> + +<% } %> +<% if (shovel.state == 'terminated') { %> + + + + + + +<% } else { %> + + <% if (shovel.definition == undefined) { %> + + + <% } else { %> + + + + + <% } %> + + +<% } %> + <% } %> + +
NameNodeVirtual HostStateSourceDestinationLast changed
+ <%= fmt_string(shovel.name) %> + <%= fmt_string(shovel.type) %> + <%= fmt_node(shovel.node) %><%= fmt_string(shovel.vhost, '') %><%= fmt_state('red', shovel.state) %><%= shovel.timestamp %>
+
<%= fmt_string(shovel.reason) %>
+
<%= fmt_state('green', shovel.state) %><%= fmt_string(shovel.src_uri) %><%= fmt_string(shovel.dest_uri) %><%= fmt_string(shovel.src_uri) %><%= fmt_shovel_endpoint('src', shovel.definition) %><%= fmt_string(shovel.dest_uri) %><%= fmt_shovel_endpoint('dest', shovel.definition) %><%= shovel.timestamp %>
+<% } else { %> +

... no shovels ...

+<% } %> +
diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/src/rabbit_shovel_mgmt.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/src/rabbit_shovel_mgmt.erl new file mode 100644 index 0000000..86812ef --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/src/rabbit_shovel_mgmt.erl @@ -0,0 +1,124 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_shovel_mgmt). + +-behaviour(rabbit_mgmt_extension). + +-export([dispatcher/0, web_ui/0]). +-export([init/1, to_json/2, resource_exists/2, content_types_provided/2, + is_authorized/2]). + +-import(rabbit_misc, [pget/2]). + +-include_lib("rabbitmq_management/include/rabbit_mgmt.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). + +dispatcher() -> [{["shovels"], ?MODULE, []}, + {["shovels", vhost], ?MODULE, []}]. +web_ui() -> [{javascript, <<"shovel.js">>}]. + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +resource_exists(ReqData, Context) -> + {case rabbit_mgmt_util:vhost(ReqData) of + not_found -> false; + _ -> true + end, ReqData, Context}. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply_list( + filter_vhost_req(status(ReqData, Context), ReqData), ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_monitor(ReqData, Context). + +%%-------------------------------------------------------------------- + +filter_vhost_req(List, ReqData) -> + case rabbit_mgmt_util:vhost(ReqData) of + none -> List; + VHost -> [I || I <- List, + pget(vhost, I) =:= VHost] + end. + +%% Allow users to see things in the vhosts they are authorised. But +%% static shovels do not have a vhost, so only allow admins (not +%% monitors) to see them. +filter_vhost_user(List, _ReqData, #context{user = User = #user{tags = Tags}}) -> + VHosts = rabbit_mgmt_util:list_login_vhosts(User), + [I || I <- List, case pget(vhost, I) of + undefined -> lists:member(administrator, Tags); + VHost -> lists:member(VHost, VHosts) + end]. + +status(ReqData, Context) -> + filter_vhost_user( + lists:append([status(Node) || Node <- [node() | nodes()]]), + ReqData, Context). + +status(Node) -> + case rpc:call(Node, rabbit_shovel_status, status, [], infinity) of + {badrpc, {'EXIT', _}} -> + []; + Status -> + [format(Node, I) || I <- Status] + end. + +format(Node, {Name, Type, Info, TS}) -> + [{node, Node}, {timestamp, format_ts(TS)}] ++ + format_name(Type, Name) ++ + format_info(Info, Type, Name). + +format_name(static, Name) -> [{name, Name}, + {type, static}]; +format_name(dynamic, {VHost, Name}) -> [{name, Name}, + {vhost, VHost}, + {type, dynamic}]. + +format_info(starting, _Type, _Name) -> + [{state, starting}]; + +format_info({running, Props}, Type, Name) -> + [{state, running}] ++ lookup_src_dest(Type, Name) ++ Props; + +format_info({terminated, Reason}, _Type, _Name) -> + [{state, terminated}, + {reason, print("~p", [Reason])}]. + +format_ts({{Y, M, D}, {H, Min, S}}) -> + print("~w-~2.2.0w-~2.2.0w ~w:~2.2.0w:~2.2.0w", [Y, M, D, H, Min, S]). + +print(Fmt, Val) -> + list_to_binary(io_lib:format(Fmt, Val)). + +lookup_src_dest(static, _Name) -> + %% This is too messy to do, the config may be on another node and anyway + %% does not necessarily tell us the source and destination very clearly. + []; + +lookup_src_dest(dynamic, {VHost, Name}) -> + Def = pget(value, + rabbit_runtime_parameters:lookup(VHost, <<"shovel">>, Name)), + Ks = [<<"src-queue">>, <<"src-exchange">>, <<"src-exchange-key">>, + <<"dest-queue">>, <<"dest-exchange">>, <<"dest-exchange-key">>], + [{definition, [{K, V} || {K, V} <- Def, lists:member(K, Ks)]}]. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/src/rabbitmq_shovel_management.app.src b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/src/rabbitmq_shovel_management.app.src new file mode 100644 index 0000000..61e4ca0 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/src/rabbitmq_shovel_management.app.src @@ -0,0 +1,6 @@ +{application, rabbitmq_shovel_management, + [{description, "Shovel Status"}, + {vsn, "%%VSN%%"}, + {modules, []}, + {registered, []}, + {applications, [kernel, stdlib, rabbit, rabbitmq_management]}]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/test/src/rabbit_shovel_mgmt_test_all.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/test/src/rabbit_shovel_mgmt_test_all.erl new file mode 100644 index 0000000..3fb8bdd --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/test/src/rabbit_shovel_mgmt_test_all.erl @@ -0,0 +1,28 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Console. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_shovel_mgmt_test_all). + +-export([all_tests/0]). + +all_tests() -> + ok = eunit:test(tests(rabbit_shovel_mgmt_test_http, 60), [verbose]). + +tests(Module, Timeout) -> + {foreach, fun() -> ok end, + [{timeout, Timeout, fun Module:F/0} || + {F, _Arity} <- proplists:get_value(exports, Module:module_info()), + string:right(atom_to_list(F), 5) =:= "_test"]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/test/src/rabbit_shovel_mgmt_test_http.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/test/src/rabbit_shovel_mgmt_test_http.erl new file mode 100644 index 0000000..d0a5c1b --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel-management/test/src/rabbit_shovel_mgmt_test_http.erl @@ -0,0 +1,181 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Console. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_shovel_mgmt_test_http). + +-include_lib("rabbitmq_management/include/rabbit_mgmt_test.hrl"). + +-import(rabbit_misc, [pget/2]). + +shovels_test() -> + http_put("/users/admin", [{password, <<"admin">>}, + {tags, <<"administrator">>}], ?NO_CONTENT), + http_put("/users/mon", [{password, <<"mon">>}, + {tags, <<"monitoring">>}], ?NO_CONTENT), + http_put("/vhosts/v", none, ?NO_CONTENT), + Perms = [{configure, <<".*">>}, + {write, <<".*">>}, + {read, <<".*">>}], + http_put("/permissions/v/guest", Perms, ?NO_CONTENT), + http_put("/permissions/v/admin", Perms, ?NO_CONTENT), + http_put("/permissions/v/mon", Perms, ?NO_CONTENT), + + [http_put("/parameters/shovel/" ++ V ++ "/my-dynamic", + [{value, [{'src-uri', <<"amqp://">>}, + {'dest-uri', <<"amqp://">>}, + {'src-queue', <<"test">>}, + {'dest-queue', <<"test2">>}]}], ?NO_CONTENT) + || V <- ["%2f", "v"]], + Static = [{name, <<"my-static">>}, + {type, <<"static">>}], + Dynamic1 = [{name, <<"my-dynamic">>}, + {vhost, <<"/">>}, + {type, <<"dynamic">>}], + Dynamic2 = [{name, <<"my-dynamic">>}, + {vhost, <<"v">>}, + {type, <<"dynamic">>}], + Assert = fun (Req, User, Res) -> + assert_list(Res, http_get(Req, User, User, ?OK)) + end, + Assert("/shovels", "guest", [Static, Dynamic1, Dynamic2]), + Assert("/shovels/%2f", "guest", [Dynamic1]), + Assert("/shovels/v", "guest", [Dynamic2]), + Assert("/shovels", "admin", [Static, Dynamic2]), + Assert("/shovels/%2f", "admin", []), + Assert("/shovels/v", "admin", [Dynamic2]), + Assert("/shovels", "mon", [Dynamic2]), + Assert("/shovels/%2f", "mon", []), + Assert("/shovels/v", "mon", [Dynamic2]), + + http_delete("/vhosts/v", ?NO_CONTENT), + http_delete("/users/admin", ?NO_CONTENT), + http_delete("/users/mon", ?NO_CONTENT), + ok. + +%%--------------------------------------------------------------------------- +%% TODO this is all copypasta from the mgmt tests + +http_get(Path) -> + http_get(Path, ?OK). + +http_get(Path, CodeExp) -> + http_get(Path, "guest", "guest", CodeExp). + +http_get(Path, User, Pass, CodeExp) -> + {ok, {{_HTTP, CodeAct, _}, Headers, ResBody}} = + req(get, Path, [auth_header(User, Pass)]), + assert_code(CodeExp, CodeAct, "GET", Path, ResBody), + decode(CodeExp, Headers, ResBody). + +http_put(Path, List, CodeExp) -> + http_put_raw(Path, format_for_upload(List), CodeExp). + +http_put(Path, List, User, Pass, CodeExp) -> + http_put_raw(Path, format_for_upload(List), User, Pass, CodeExp). + +http_post(Path, List, CodeExp) -> + http_post_raw(Path, format_for_upload(List), CodeExp). + +http_post(Path, List, User, Pass, CodeExp) -> + http_post_raw(Path, format_for_upload(List), User, Pass, CodeExp). + +format_for_upload(none) -> + <<"">>; +format_for_upload(List) -> + iolist_to_binary(mochijson2:encode({struct, List})). + +http_put_raw(Path, Body, CodeExp) -> + http_upload_raw(put, Path, Body, "guest", "guest", CodeExp). + +http_put_raw(Path, Body, User, Pass, CodeExp) -> + http_upload_raw(put, Path, Body, User, Pass, CodeExp). + +http_post_raw(Path, Body, CodeExp) -> + http_upload_raw(post, Path, Body, "guest", "guest", CodeExp). + +http_post_raw(Path, Body, User, Pass, CodeExp) -> + http_upload_raw(post, Path, Body, User, Pass, CodeExp). + +http_upload_raw(Type, Path, Body, User, Pass, CodeExp) -> + {ok, {{_HTTP, CodeAct, _}, Headers, ResBody}} = + req(Type, Path, [auth_header(User, Pass)], Body), + assert_code(CodeExp, CodeAct, Type, Path, ResBody), + decode(CodeExp, Headers, ResBody). + +http_delete(Path, CodeExp) -> + http_delete(Path, "guest", "guest", CodeExp). + +http_delete(Path, User, Pass, CodeExp) -> + {ok, {{_HTTP, CodeAct, _}, Headers, ResBody}} = + req(delete, Path, [auth_header(User, Pass)]), + assert_code(CodeExp, CodeAct, "DELETE", Path, ResBody), + decode(CodeExp, Headers, ResBody). + +assert_code(CodeExp, CodeAct, Type, Path, Body) -> + case CodeExp of + CodeAct -> ok; + _ -> throw({expected, CodeExp, got, CodeAct, type, Type, + path, Path, body, Body}) + end. + +req(Type, Path, Headers) -> + httpc:request(Type, {?PREFIX ++ Path, Headers}, ?HTTPC_OPTS, []). + +req(Type, Path, Headers, Body) -> + httpc:request(Type, {?PREFIX ++ Path, Headers, "application/json", Body}, + ?HTTPC_OPTS, []). + +decode(?OK, _Headers, ResBody) -> cleanup(mochijson2:decode(ResBody)); +decode(_, Headers, _ResBody) -> Headers. + +cleanup(L) when is_list(L) -> + [cleanup(I) || I <- L]; +cleanup({struct, I}) -> + cleanup(I); +cleanup({K, V}) when is_binary(K) -> + {list_to_atom(binary_to_list(K)), cleanup(V)}; +cleanup(I) -> + I. + +auth_header(Username, Password) -> + {"Authorization", + "Basic " ++ binary_to_list(base64:encode(Username ++ ":" ++ Password))}. + +assert_list(Exp, Act) -> + case length(Exp) == length(Act) of + true -> ok; + false -> throw({expected, Exp, actual, Act}) + end, + [case length(lists:filter(fun(ActI) -> test_item(ExpI, ActI) end, Act)) of + 1 -> ok; + N -> throw({found, N, ExpI, in, Act}) + end || ExpI <- Exp]. + +assert_item(Exp, Act) -> + case test_item0(Exp, Act) of + [] -> ok; + Or -> throw(Or) + end. + +test_item(Exp, Act) -> + case test_item0(Exp, Act) of + [] -> true; + _ -> false + end. + +test_item0(Exp, Act) -> + [{did_not_find, ExpI, in, Act} || ExpI <- Exp, + not lists:member(ExpI, Act)]. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/.srcdist_done b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/.srcdist_done new file mode 100644 index 0000000..e69de29 diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/Makefile b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/Makefile new file mode 100644 index 0000000..482105a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/Makefile @@ -0,0 +1 @@ +include ../umbrella.mk diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/README b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/README new file mode 100644 index 0000000..1d7d1b0 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/README @@ -0,0 +1,4 @@ +Generic build instructions are at: + http://www.rabbitmq.com/plugin-development.html + +See the http://www.rabbitmq.com/shovel.html page for full instructions. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/generate_deps b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/generate_deps new file mode 100644 index 0000000..29587b5 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/generate_deps @@ -0,0 +1,54 @@ +#!/usr/bin/env escript +%% -*- erlang -*- +-mode(compile). + +main([IncludeDir, ErlDir, EbinDir, TargetFile]) -> + ErlDirContents = filelib:wildcard("*.erl", ErlDir), + ErlFiles = [filename:join(ErlDir, FileName) || FileName <- ErlDirContents], + Modules = sets:from_list( + [list_to_atom(filename:basename(FileName, ".erl")) || + FileName <- ErlDirContents]), + Headers = sets:from_list( + [filename:join(IncludeDir, FileName) || + FileName <- filelib:wildcard("*.hrl", IncludeDir)]), + Deps = lists:foldl( + fun (Path, Deps1) -> + dict:store(Path, detect_deps(IncludeDir, EbinDir, + Modules, Headers, Path), + Deps1) + end, dict:new(), ErlFiles), + {ok, Hdl} = file:open(TargetFile, [write, delayed_write]), + dict:fold( + fun (_Path, [], ok) -> + ok; + (Path, Dep, ok) -> + Module = filename:basename(Path, ".erl"), + ok = file:write(Hdl, [EbinDir, "/", Module, ".beam: ", + Path]), + ok = sets:fold(fun (E, ok) -> file:write(Hdl, [" ", E]) end, + ok, Dep), + file:write(Hdl, ["\n"]) + end, ok, Deps), + ok = file:write(Hdl, [TargetFile, ": ", escript:script_name(), "\n"]), + ok = file:sync(Hdl), + ok = file:close(Hdl). + +detect_deps(IncludeDir, EbinDir, Modules, Headers, Path) -> + {ok, Forms} = epp:parse_file(Path, [IncludeDir], [{use_specs, true}]), + lists:foldl( + fun ({attribute, _LineNumber, Attribute, Behaviour}, Deps) + when Attribute =:= behaviour orelse Attribute =:= behavior -> + case sets:is_element(Behaviour, Modules) of + true -> sets:add_element( + [EbinDir, "/", atom_to_list(Behaviour), ".beam"], + Deps); + false -> Deps + end; + ({attribute, _LineNumber, file, {FileName, _LineNumber1}}, Deps) -> + case sets:is_element(FileName, Headers) of + true -> sets:add_element(FileName, Deps); + false -> Deps + end; + (_Form, Deps) -> + Deps + end, sets:new(), Forms). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/include/rabbit_shovel.hrl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/include/rabbit_shovel.hrl new file mode 100644 index 0000000..b0f0aae --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/include/rabbit_shovel.hrl @@ -0,0 +1,32 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-record(endpoint, + {uris, + resource_declaration + }). + +-record(shovel, + {sources, + destinations, + prefetch_count, + ack_mode, + publish_fields, + publish_properties, + queue, + reconnect_delay, + delete_after = never + }). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/package.mk b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/package.mk new file mode 100644 index 0000000..6cf8254 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/package.mk @@ -0,0 +1,3 @@ +RELEASABLE:=true +DEPS:=rabbitmq-erlang-client +WITH_BROKER_TEST_COMMANDS:=rabbit_shovel_test_all:all_tests() diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel.erl new file mode 100644 index 0000000..e416787 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel.erl @@ -0,0 +1,27 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_shovel). + +-export([start/0, stop/0, start/2, stop/1]). + +start() -> rabbit_shovel_sup:start_link(), ok. + +stop() -> ok. + +start(normal, []) -> rabbit_shovel_sup:start_link(). + +stop(_State) -> ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_config.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_config.erl new file mode 100644 index 0000000..8d6bc56 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_config.erl @@ -0,0 +1,242 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_shovel_config). + +-export([parse/2, + ensure_defaults/2]). + +-include_lib("amqp_client/include/amqp_client.hrl"). +-include("rabbit_shovel.hrl"). + +-define(IGNORE_FIELDS, [delete_after]). + +parse(ShovelName, Config) -> + {ok, Defaults} = application:get_env(defaults), + try + {ok, run_state_monad( + [fun enrich_shovel_config/1, + fun parse_shovel_config_proplist/1, + fun parse_shovel_config_dict/1], + {Config, Defaults})} + catch throw:{error, Reason} -> + {error, {invalid_shovel_configuration, ShovelName, Reason}} + end. + +%% ensures that any defaults that have been applied to a parsed +%% shovel, are written back to the original proplist +ensure_defaults(ShovelConfig, ParsedShovel) -> + lists:keystore(reconnect_delay, 1, + ShovelConfig, + {reconnect_delay, + ParsedShovel#shovel.reconnect_delay}). + +enrich_shovel_config({Config, Defaults}) -> + Config1 = proplists:unfold(Config), + case [E || E <- Config1, not (is_tuple(E) andalso tuple_size(E) == 2)] of + [] -> case duplicate_keys(Config1) of + [] -> return(lists:ukeysort(1, Config1 ++ Defaults)); + Dups -> fail({duplicate_parameters, Dups}) + end; + Invalid -> fail({invalid_parameters, Invalid}) + end. + +parse_shovel_config_proplist(Config) -> + Dict = dict:from_list(Config), + Fields = record_info(fields, shovel) -- ?IGNORE_FIELDS, + Keys = dict:fetch_keys(Dict), + case {Keys -- Fields, Fields -- Keys} of + {[], []} -> {_Pos, Dict1} = + lists:foldl( + fun (FieldName, {Pos, Acc}) -> + {Pos + 1, + dict:update(FieldName, + fun (V) -> {V, Pos} end, + Acc)} + end, {2, Dict}, Fields), + return(Dict1); + {[], Missing} -> fail({missing_parameters, Missing}); + {Unknown, _} -> fail({unrecognised_parameters, Unknown}) + end. + +parse_shovel_config_dict(Dict) -> + run_state_monad( + [fun (Shovel) -> {ok, Value} = dict:find(Key, Dict), + try {ParsedValue, Pos} = Fun(Value), + return(setelement(Pos, Shovel, ParsedValue)) + catch throw:{error, Reason} -> + fail({invalid_parameter_value, Key, Reason}) + end + end || {Fun, Key} <- + [{fun parse_endpoint/1, sources}, + {fun parse_endpoint/1, destinations}, + {fun parse_non_negative_integer/1, prefetch_count}, + {fun parse_ack_mode/1, ack_mode}, + {fun parse_binary/1, queue}, + make_parse_publish(publish_fields), + make_parse_publish(publish_properties), + {fun parse_non_negative_number/1, reconnect_delay}]], + #shovel{}). + +%% --=: Plain state monad implementation start :=-- +run_state_monad(FunList, State) -> + lists:foldl(fun (Fun, StateN) -> Fun(StateN) end, State, FunList). + +return(V) -> V. + +fail(Reason) -> throw({error, Reason}). +%% --=: end :=-- + +parse_endpoint({Endpoint, Pos}) when is_list(Endpoint) -> + Brokers = case proplists:get_value(brokers, Endpoint) of + undefined -> + case proplists:get_value(broker, Endpoint) of + undefined -> fail({missing_endpoint_parameter, + broker_or_brokers}); + B -> [B] + end; + Bs when is_list(Bs) -> + Bs; + B -> + fail({expected_list, brokers, B}) + end, + {[], Brokers1} = run_state_monad( + lists:duplicate(length(Brokers), + fun check_uri/1), + {Brokers, []}), + + ResourceDecls = + case proplists:get_value(declarations, Endpoint, []) of + Decls when is_list(Decls) -> + Decls; + Decls -> + fail({expected_list, declarations, Decls}) + end, + {[], ResourceDecls1} = + run_state_monad( + lists:duplicate(length(ResourceDecls), fun parse_declaration/1), + {ResourceDecls, []}), + + DeclareFun = + fun (_Conn, Ch) -> + [amqp_channel:call(Ch, M) || M <- lists:reverse(ResourceDecls1)] + end, + return({#endpoint{uris = Brokers1, + resource_declaration = DeclareFun}, + Pos}); +parse_endpoint({Endpoint, _Pos}) -> + fail({require_list, Endpoint}). + +check_uri({[Uri | Uris], Acc}) -> + case amqp_uri:parse(Uri) of + {ok, _Params} -> + return({Uris, [Uri | Acc]}); + {error, _} = Err -> + throw(Err) + end. + +parse_declaration({[{Method, Props} | Rest], Acc}) when is_list(Props) -> + FieldNames = try rabbit_framing_amqp_0_9_1:method_fieldnames(Method) + catch exit:Reason -> fail(Reason) + end, + case proplists:get_keys(Props) -- FieldNames of + [] -> ok; + UnknownFields -> fail({unknown_fields, Method, UnknownFields}) + end, + {Res, _Idx} = lists:foldl( + fun (K, {R, Idx}) -> + NewR = case proplists:get_value(K, Props) of + undefined -> R; + V -> setelement(Idx, R, V) + end, + {NewR, Idx + 1} + end, {rabbit_framing_amqp_0_9_1:method_record(Method), 2}, + FieldNames), + return({Rest, [Res | Acc]}); +parse_declaration({[{Method, Props} | _Rest], _Acc}) -> + fail({expected_method_field_list, Method, Props}); +parse_declaration({[Method | Rest], Acc}) -> + parse_declaration({[{Method, []} | Rest], Acc}). + +parse_non_negative_integer({N, Pos}) when is_integer(N) andalso N >= 0 -> + return({N, Pos}); +parse_non_negative_integer({N, _Pos}) -> + fail({require_non_negative_integer, N}). + +parse_non_negative_number({N, Pos}) when is_number(N) andalso N >= 0 -> + return({N, Pos}); +parse_non_negative_number({N, _Pos}) -> + fail({require_non_negative_number, N}). + +parse_binary({Binary, Pos}) when is_binary(Binary) -> + return({Binary, Pos}); +parse_binary({NotABinary, _Pos}) -> + fail({require_binary, NotABinary}). + +parse_ack_mode({Val, Pos}) when Val =:= no_ack orelse + Val =:= on_publish orelse + Val =:= on_confirm -> + return({Val, Pos}); +parse_ack_mode({WrongVal, _Pos}) -> + fail({ack_mode_value_requires_one_of, {no_ack, on_publish, on_confirm}, + WrongVal}). + +make_parse_publish(publish_fields) -> + {make_parse_publish1(record_info(fields, 'basic.publish')), publish_fields}; +make_parse_publish(publish_properties) -> + {make_parse_publish1(record_info(fields, 'P_basic')), publish_properties}. + +make_parse_publish1(ValidFields) -> + fun ({Fields, Pos}) when is_list(Fields) -> + make_publish_fun(Fields, Pos, ValidFields); + ({Fields, _Pos}) -> + fail({require_list, Fields}) + end. + +make_publish_fun(Fields, Pos, ValidFields) -> + SuppliedFields = proplists:get_keys(Fields), + case SuppliedFields -- ValidFields of + [] -> + FieldIndices = make_field_indices(ValidFields, Fields), + Fun = fun (_SrcUri, _DestUri, Publish) -> + lists:foldl(fun ({Pos1, Value}, Pub) -> + setelement(Pos1, Pub, Value) + end, Publish, FieldIndices) + end, + return({Fun, Pos}); + Unexpected -> + fail({unexpected_fields, Unexpected, ValidFields}) + end. + +make_field_indices(Valid, Fields) -> + make_field_indices(Fields, field_map(Valid, 2), []). + +make_field_indices([], _Idxs , Acc) -> + lists:reverse(Acc); +make_field_indices([{Key, Value} | Rest], Idxs, Acc) -> + make_field_indices(Rest, Idxs, [{dict:fetch(Key, Idxs), Value} | Acc]). + +field_map(Fields, Idx0) -> + {Dict, _IdxMax} = + lists:foldl(fun (Field, {Dict1, Idx1}) -> + {dict:store(Field, Idx1, Dict1), Idx1 + 1} + end, {dict:new(), Idx0}, Fields), + Dict. + +duplicate_keys(PropList) -> + proplists:get_keys( + lists:foldl(fun (K, L) -> lists:keydelete(K, 1, L) end, PropList, + proplists:get_keys(PropList))). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_dyn_worker_sup.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_dyn_worker_sup.erl new file mode 100644 index 0000000..d5d5268 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_dyn_worker_sup.erl @@ -0,0 +1,40 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_shovel_dyn_worker_sup). +-behaviour(supervisor2). + +-export([start_link/2, init/1]). + +-import(rabbit_misc, [pget/3]). + +-include_lib("rabbit_common/include/rabbit.hrl"). +-define(SUPERVISOR, ?MODULE). + +start_link(Name, Config) -> + supervisor2:start_link(?MODULE, [Name, Config]). + +%%---------------------------------------------------------------------------- + +init([Name, Config]) -> + {ok, {{one_for_one, 1, ?MAX_WAIT}, + [{Name, + {rabbit_shovel_worker, start_link, [dynamic, Name, Config]}, + case pget(<<"reconnect-delay">>, Config, 1) of + N when is_integer(N) andalso N > 0 -> {transient, N}; + _ -> temporary + end, + 16#ffffffff, worker, [rabbit_shovel_worker]}]}}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_dyn_worker_sup_sup.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_dyn_worker_sup_sup.erl new file mode 100644 index 0000000..9be7fd8 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_dyn_worker_sup_sup.erl @@ -0,0 +1,75 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_shovel_dyn_worker_sup_sup). +-behaviour(mirrored_supervisor). + +-export([start_link/0, init/1, adjust/2, stop_child/1]). + +-import(rabbit_misc, [pget/2]). + +-include_lib("rabbit_common/include/rabbit.hrl"). +-define(SUPERVISOR, ?MODULE). + +start_link() -> + {ok, Pid} = mirrored_supervisor:start_link( + {local, ?SUPERVISOR}, ?SUPERVISOR, + fun rabbit_misc:execute_mnesia_transaction/1, ?MODULE, []), + Shovels = rabbit_runtime_parameters:list_component(<<"shovel">>), + [start_child({pget(vhost, Shovel), pget(name, Shovel)}, + pget(value, Shovel)) || Shovel <- Shovels], + {ok, Pid}. + +adjust(Name, Def) -> + case child_exists(Name) of + true -> stop_child(Name); + false -> ok + end, + start_child(Name, Def). + +start_child(Name, Def) -> + case mirrored_supervisor:start_child( + ?SUPERVISOR, + {Name, {rabbit_shovel_dyn_worker_sup, start_link, [Name, Def]}, + transient, ?MAX_WAIT, worker, [rabbit_shovel_dyn_worker_sup]}) of + {ok, _Pid} -> ok; + {error, {already_started, _Pid}} -> ok + end. + +child_exists(Name) -> + lists:any(fun ({N, _, _, _}) -> N =:= Name end, + mirrored_supervisor:which_children(?SUPERVISOR)). + +stop_child(Name) -> + case get(shovel_worker_autodelete) of + true -> ok; %% [1] + _ -> ok = mirrored_supervisor:terminate_child(?SUPERVISOR, Name), + ok = mirrored_supervisor:delete_child(?SUPERVISOR, Name), + rabbit_shovel_status:remove(Name) + end. + +%% [1] An autodeleting worker removes its own parameter, and thus ends +%% up here via the parameter callback. It is a transient worker that +%% is just about to terminate normally - so we don't need to tell the +%% supervisor to stop us - and as usual if we call into our own +%% supervisor we risk deadlock. +%% +%% See rabbit_shovel_worker:maybe_autodelete/1 + +%%---------------------------------------------------------------------------- + +init([]) -> + {ok, {{one_for_one, 3, 10}, []}}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_parameters.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_parameters.erl new file mode 100644 index 0000000..dfcb349 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_parameters.erl @@ -0,0 +1,245 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_shovel_parameters). +-behaviour(rabbit_runtime_parameter). + +-include_lib("amqp_client/include/amqp_client.hrl"). +-include("rabbit_shovel.hrl"). + +-export([validate/5, notify/4, notify_clear/3]). +-export([register/0, parse/2]). + +-import(rabbit_misc, [pget/2, pget/3]). + +-define(ROUTING_HEADER, <<"x-shovelled">>). + +-rabbit_boot_step({?MODULE, + [{description, "shovel parameters"}, + {mfa, {rabbit_shovel_parameters, register, []}}, + {requires, rabbit_registry}, + {enables, recovery}]}). + +register() -> + rabbit_registry:register(runtime_parameter, <<"shovel">>, ?MODULE). + +validate(_VHost, <<"shovel">>, Name, Def, User) -> + [case pget2(<<"src-exchange">>, <<"src-queue">>, Def) of + zero -> {error, "Must specify 'src-exchange' or 'src-queue'", []}; + one -> ok; + both -> {error, "Cannot specify 'src-exchange' and 'src-queue'", []} + end, + case pget2(<<"dest-exchange">>, <<"dest-queue">>, Def) of + zero -> ok; + one -> ok; + both -> {error, "Cannot specify 'dest-exchange' and 'dest-queue'", []} + end, + case {pget(<<"delete-after">>, Def), pget(<<"ack-mode">>, Def)} of + {N, <<"no-ack">>} when is_integer(N) -> + {error, "Cannot specify 'no-ack' and numerical 'delete-after'", []}; + _ -> + ok + end | rabbit_parameter_validation:proplist(Name, validation(User), Def)]; + +validate(_VHost, _Component, Name, _Term, _User) -> + {error, "name not recognised: ~p", [Name]}. + +pget2(K1, K2, Defs) -> case {pget(K1, Defs), pget(K2, Defs)} of + {undefined, undefined} -> zero; + {undefined, _} -> one; + {_, undefined} -> one; + {_, _} -> both + end. + +notify(VHost, <<"shovel">>, Name, Definition) -> + rabbit_shovel_dyn_worker_sup_sup:adjust({VHost, Name}, Definition). + +notify_clear(VHost, <<"shovel">>, Name) -> + rabbit_shovel_dyn_worker_sup_sup:stop_child({VHost, Name}). + +%%---------------------------------------------------------------------------- + +validation(User) -> + [{<<"src-uri">>, validate_uri_fun(User), mandatory}, + {<<"dest-uri">>, validate_uri_fun(User), mandatory}, + {<<"src-exchange">>, fun rabbit_parameter_validation:binary/2,optional}, + {<<"src-exchange-key">>,fun rabbit_parameter_validation:binary/2,optional}, + {<<"src-queue">>, fun rabbit_parameter_validation:binary/2,optional}, + {<<"dest-exchange">>, fun rabbit_parameter_validation:binary/2,optional}, + {<<"dest-exchange-key">>,fun rabbit_parameter_validation:binary/2,optional}, + {<<"dest-queue">>, fun rabbit_parameter_validation:binary/2,optional}, + {<<"prefetch-count">>, fun rabbit_parameter_validation:number/2,optional}, + {<<"reconnect-delay">>, fun rabbit_parameter_validation:number/2,optional}, + {<<"add-forward-headers">>, fun rabbit_parameter_validation:boolean/2,optional}, + {<<"ack-mode">>, rabbit_parameter_validation:enum( + ['no-ack', 'on-publish', 'on-confirm']), optional}, + {<<"delete-after">>, fun validate_delete_after/2, optional} + ]. + +validate_uri_fun(User) -> + fun (Name, Term) -> validate_uri(Name, Term, User) end. + +validate_uri(Name, Term, User) when is_binary(Term) -> + case rabbit_parameter_validation:binary(Name, Term) of + ok -> case amqp_uri:parse(binary_to_list(Term)) of + {ok, P} -> validate_params_user(P, User); + {error, E} -> {error, "\"~s\" not a valid URI: ~p", [Term, E]} + end; + E -> E + end; +validate_uri(Name, Term, User) -> + case rabbit_parameter_validation:list(Name, Term) of + ok -> case [V || URI <- Term, + V <- [validate_uri(Name, URI, User)], + element(1, V) =:= error] of + [] -> ok; + [E | _] -> E + end; + E -> E + end. + +validate_params_user(#amqp_params_direct{}, none) -> + ok; +validate_params_user(#amqp_params_direct{virtual_host = VHost}, + User = #user{username = Username, + auth_backend = M}) -> + case rabbit_vhost:exists(VHost) andalso M:check_vhost_access(User, VHost) of + true -> ok; + false -> {error, "user \"~s\" may not connect to vhost \"~s\"", + [Username, VHost]} + end; +validate_params_user(#amqp_params_network{}, _User) -> + ok. + +validate_delete_after(_Name, <<"never">>) -> ok; +validate_delete_after(_Name, <<"queue-length">>) -> ok; +validate_delete_after(_Name, N) when is_integer(N) -> ok; +validate_delete_after(Name, Term) -> + {error, "~s should be number, \"never\" or \"queue-length\", actually was " + "~p", [Name, Term]}. + +%%---------------------------------------------------------------------------- + +parse({VHost, Name}, Def) -> + SrcURIs = get_uris(<<"src-uri">>, Def), + DestURIs = get_uris(<<"dest-uri">>, Def), + SrcX = pget(<<"src-exchange">>, Def, none), + SrcXKey = pget(<<"src-exchange-key">>, Def, <<>>), %% [1] + SrcQ = pget(<<"src-queue">>, Def, none), + DestX = pget(<<"dest-exchange">>, Def, none), + DestXKey = pget(<<"dest-exchange-key">>, Def, none), + DestQ = pget(<<"dest-queue">>, Def, none), + %% [1] src-exchange-key is never ignored if src-exchange is set + {SrcFun, Queue, Table1} = + case SrcQ of + none -> {fun (_Conn, Ch) -> + Ms = [#'queue.declare'{exclusive = true}, + #'queue.bind'{routing_key = SrcXKey, + exchange = SrcX}], + [amqp_channel:call(Ch, M) || M <- Ms] + end, <<>>, [{<<"src-exchange">>, SrcX}, + {<<"src-exchange-key">>, SrcXKey}]}; + _ -> {fun (Conn, _Ch) -> + ensure_queue(Conn, SrcQ) + end, SrcQ, [{<<"src-queue">>, SrcQ}]} + end, + DestFun = fun (Conn, _Ch) -> + case DestQ of + none -> ok; + _ -> ensure_queue(Conn, DestQ) + end + end, + {X, Key} = case DestQ of + none -> {DestX, DestXKey}; + _ -> {<<>>, DestQ} + end, + Table2 = [{K, V} || {K, V} <- [{<<"dest-exchange">>, DestX}, + {<<"dest-exchange-key">>, DestXKey}, + {<<"dest-queue">>, DestQ}], + V =/= none], + PubFun = fun (_SrcURI, _DestURI, P0) -> + P1 = case X of + none -> P0; + _ -> P0#'basic.publish'{exchange = X} + end, + case Key of + none -> P1; + _ -> P1#'basic.publish'{routing_key = Key} + end + end, + AddHeaders = pget(<<"add-forward-headers">>, Def, false), + Table0 = [{<<"shovelled-by">>, rabbit_nodes:cluster_name()}, + {<<"shovel-name">>, Name}, + {<<"shovel-vhost">>, VHost}], + PubPropsFun = fun (SrcURI, DestURI, P = #'P_basic'{headers = H}) -> + case AddHeaders of + true -> H1 = update_headers( + Table0, Table1 ++ Table2, + SrcURI, DestURI, H), + P#'P_basic'{headers = H1}; + false -> P + end + end, + {ok, #shovel{ + sources = #endpoint{uris = SrcURIs, + resource_declaration = SrcFun}, + destinations = #endpoint{uris = DestURIs, + resource_declaration = DestFun}, + prefetch_count = pget(<<"prefetch-count">>, Def, 1000), + ack_mode = translate_ack_mode( + pget(<<"ack-mode">>, Def, <<"on-confirm">>)), + publish_fields = PubFun, + publish_properties = PubPropsFun, + queue = Queue, + reconnect_delay = pget(<<"reconnect-delay">>, Def, 1), + delete_after = opt_b2a(pget(<<"delete-after">>, Def, <<"never">>)) + }}. + +get_uris(Key, Def) -> + URIs = case pget(Key, Def) of + B when is_binary(B) -> [B]; + L when is_list(L) -> L + end, + [binary_to_list(URI) || URI <- URIs]. + +translate_ack_mode(<<"on-confirm">>) -> on_confirm; +translate_ack_mode(<<"on-publish">>) -> on_publish; +translate_ack_mode(<<"no-ack">>) -> no_ack. + +ensure_queue(Conn, Queue) -> + {ok, Ch} = amqp_connection:open_channel(Conn), + try + amqp_channel:call(Ch, #'queue.declare'{queue = Queue, + passive = true}) + catch exit:{{shutdown, {server_initiated_close, ?NOT_FOUND, _Text}}, _} -> + {ok, Ch2} = amqp_connection:open_channel(Conn), + amqp_channel:call(Ch2, #'queue.declare'{queue = Queue, + durable = true}), + catch amqp_channel:close(Ch2) + + after + catch amqp_channel:close(Ch) + end. + +update_headers(Table0, Table1, SrcURI, DestURI, Headers) -> + Table = Table0 ++ [{<<"src-uri">>, SrcURI}, + {<<"dest-uri">>, DestURI}] ++ Table1, + rabbit_basic:prepend_table_header( + ?ROUTING_HEADER, [{K, longstr, V} || {K, V} <- Table], + Headers). + +opt_b2a(B) when is_binary(B) -> list_to_atom(binary_to_list(B)); +opt_b2a(N) -> N. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_status.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_status.erl new file mode 100644 index 0000000..f30dbc4 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_status.erl @@ -0,0 +1,84 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_shovel_status). +-behaviour(gen_server). + +-export([start_link/0]). + +-export([report/3, remove/1, status/0]). + +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-define(SERVER, ?MODULE). +-define(ETS_NAME, ?MODULE). + +-record(state, {}). +-record(entry, {name, type, info, timestamp}). + +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +report(Name, Type, Info) -> + gen_server:cast(?SERVER, {report, Name, Type, Info, calendar:local_time()}). + +remove(Name) -> + gen_server:cast(?SERVER, {remove, Name}). + +status() -> + gen_server:call(?SERVER, status, infinity). + +init([]) -> + ?ETS_NAME = ets:new(?ETS_NAME, + [named_table, {keypos, #entry.name}, private]), + {ok, #state{}}. + +handle_call(status, _From, State) -> + Entries = ets:tab2list(?ETS_NAME), + {reply, [{Entry#entry.name, Entry#entry.type, Entry#entry.info, + Entry#entry.timestamp} + || Entry <- Entries], State}. + +handle_cast({report, Name, Type, Info, Timestamp}, State) -> + true = ets:insert(?ETS_NAME, #entry{name = Name, type = Type, info = Info, + timestamp = Timestamp}), + rabbit_event:notify(shovel_worker_status, + split_name(Name) ++ split_status(Info)), + {noreply, State}; + +handle_cast({remove, Name}, State) -> + true = ets:delete(?ETS_NAME, Name), + rabbit_event:notify(shovel_worker_removed, split_name(Name)), + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +split_status({running, MoreInfo}) -> [{status, running} | MoreInfo]; +split_status({terminated, Reason}) -> [{status, terminated}, + {reason, Reason}]; +split_status(Status) when is_atom(Status) -> [{status, Status}]. + +split_name({VHost, Name}) -> [{name, Name}, + {vhost, VHost}]; +split_name(Name) when is_atom(Name) -> [{name, Name}]. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_sup.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_sup.erl new file mode 100644 index 0000000..488cef2 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_sup.erl @@ -0,0 +1,87 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_shovel_sup). +-behaviour(supervisor2). + +-export([start_link/0, init/1]). + +-import(rabbit_shovel_config, [ensure_defaults/2]). + +-include("rabbit_shovel.hrl"). + +start_link() -> + case parse_configuration(application:get_env(shovels)) of + {ok, Configurations} -> + supervisor2:start_link({local, ?MODULE}, ?MODULE, [Configurations]); + {error, Reason} -> + {error, Reason} + end. + +init([Configurations]) -> + Len = dict:size(Configurations), + ChildSpecs = [{rabbit_shovel_status, + {rabbit_shovel_status, start_link, []}, + transient, 16#ffffffff, worker, + [rabbit_shovel_status]}, + {rabbit_shovel_dyn_worker_sup_sup, + {rabbit_shovel_dyn_worker_sup_sup, start_link, []}, + transient, 16#ffffffff, supervisor, + [rabbit_shovel_dyn_worker_sup_sup]} | + make_child_specs(Configurations)], + {ok, {{one_for_one, 2*Len, 2}, ChildSpecs}}. + +make_child_specs(Configurations) -> + dict:fold( + fun (ShovelName, ShovelConfig, Acc) -> + [{ShovelName, + {rabbit_shovel_worker_sup, start_link, + [ShovelName, ShovelConfig]}, + permanent, + 16#ffffffff, + supervisor, + [rabbit_shovel_worker_sup]} | Acc] + end, [], Configurations). + +parse_configuration(undefined) -> + {ok, dict:new()}; +parse_configuration({ok, Env}) -> + {ok, Defaults} = application:get_env(defaults), + parse_configuration(Defaults, Env, dict:new()). + +parse_configuration(_Defaults, [], Acc) -> + {ok, Acc}; +parse_configuration(Defaults, [{ShovelName, ShovelConfig} | Env], Acc) + when is_atom(ShovelName) andalso is_list(ShovelConfig) -> + case dict:is_key(ShovelName, Acc) of + true -> {error, {duplicate_shovel_definition, ShovelName}}; + false -> case validate_shovel_config(ShovelName, ShovelConfig) of + {ok, Shovel} -> + %% make sure the config we accumulate has any + %% relevant default values (discovered during + %% validation), applied back to it + UpdatedConfig = ensure_defaults(ShovelConfig, Shovel), + Acc2 = dict:store(ShovelName, UpdatedConfig, Acc), + parse_configuration(Defaults, Env, Acc2); + Error -> + Error + end + end; +parse_configuration(_Defaults, _, _Acc) -> + {error, require_list_of_shovel_configurations}. + +validate_shovel_config(ShovelName, ShovelConfig) -> + rabbit_shovel_config:parse(ShovelName, ShovelConfig). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_worker.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_worker.erl new file mode 100644 index 0000000..38940d3 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_worker.erl @@ -0,0 +1,262 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_shovel_worker). +-behaviour(gen_server2). + +-export([start_link/3]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3]). + +-include_lib("amqp_client/include/amqp_client.hrl"). +-include("rabbit_shovel.hrl"). + +-define(MAX_CONNECTION_CLOSE_TIMEOUT, 10000). + +-record(state, {inbound_conn, inbound_ch, outbound_conn, outbound_ch, + name, type, config, inbound_uri, outbound_uri, unacked, + remaining, %% [1] + remaining_unacked}). %% [2] + +%% [1] Counts down until we shut down in all modes +%% [2] Counts down until we stop publishing in on-confirm mode + +start_link(Type, Name, Config) -> + ok = rabbit_shovel_status:report(Name, Type, starting), + gen_server2:start_link(?MODULE, [Type, Name, Config], []). + +%%--------------------------- +%% Gen Server Implementation +%%--------------------------- + +init([Type, Name, Config]) -> + gen_server2:cast(self(), init), + {ok, Shovel} = parse(Type, Name, Config), + {ok, #state{name = Name, type = Type, config = Shovel}}. + +parse(static, Name, Config) -> rabbit_shovel_config:parse(Name, Config); +parse(dynamic, Name, Config) -> rabbit_shovel_parameters:parse(Name, Config). + +handle_call(_Msg, _From, State) -> + {noreply, State}. + +handle_cast(init, State = #state{config = Config}) -> + random:seed(now()), + #shovel{sources = Sources, destinations = Destinations} = Config, + {InboundConn, InboundChan, InboundURI} = + make_conn_and_chan(Sources#endpoint.uris), + {OutboundConn, OutboundChan, OutboundURI} = + make_conn_and_chan(Destinations#endpoint.uris), + + %% Don't trap exits until we have established connections so that + %% if we try to shut down while waiting for a connection to be + %% established then we don't block + process_flag(trap_exit, true), + + (Sources#endpoint.resource_declaration)(InboundConn, InboundChan), + (Destinations#endpoint.resource_declaration)(OutboundConn, OutboundChan), + + NoAck = Config#shovel.ack_mode =:= no_ack, + case NoAck of + false -> Prefetch = Config#shovel.prefetch_count, + #'basic.qos_ok'{} = + amqp_channel:call( + InboundChan, #'basic.qos'{prefetch_count = Prefetch}); + true -> ok + end, + + case Config#shovel.ack_mode of + on_confirm -> + #'confirm.select_ok'{} = + amqp_channel:call(OutboundChan, #'confirm.select'{}), + ok = amqp_channel:register_confirm_handler(OutboundChan, self()); + _ -> + ok + end, + + Remaining = remaining(InboundChan, Config), + + #'basic.consume_ok'{} = + amqp_channel:subscribe( + InboundChan, #'basic.consume'{queue = Config#shovel.queue, + no_ack = NoAck}, + self()), + + State1 = + State#state{inbound_conn = InboundConn, inbound_ch = InboundChan, + outbound_conn = OutboundConn, outbound_ch = OutboundChan, + inbound_uri = InboundURI, + outbound_uri = OutboundURI, + remaining = Remaining, + remaining_unacked = Remaining, + unacked = gb_trees:empty()}, + ok = report_running(State1), + {noreply, State1}. + +handle_info(#'basic.consume_ok'{}, State) -> + {noreply, State}; + +handle_info({#'basic.deliver'{delivery_tag = Tag, + exchange = Exchange, routing_key = RoutingKey}, + Msg = #amqp_msg{props = Props = #'P_basic'{}}}, + State = #state{inbound_uri = InboundURI, + outbound_uri = OutboundURI, + config = #shovel{publish_properties = PropsFun, + publish_fields = FieldsFun}}) -> + Method = #'basic.publish'{exchange = Exchange, routing_key = RoutingKey}, + Method1 = FieldsFun(InboundURI, OutboundURI, Method), + Msg1 = Msg#amqp_msg{props = PropsFun(InboundURI, OutboundURI, Props)}, + {noreply, publish(Tag, Method1, Msg1, State)}; + +handle_info(#'basic.ack'{delivery_tag = Seq, multiple = Multiple}, + State = #state{config = #shovel{ack_mode = on_confirm}}) -> + {noreply, confirm_to_inbound( + fun (DTag, Multi) -> + #'basic.ack'{delivery_tag = DTag, multiple = Multi} + end, Seq, Multiple, State)}; + +handle_info(#'basic.nack'{delivery_tag = Seq, multiple = Multiple}, + State = #state{config = #shovel{ack_mode = on_confirm}}) -> + {noreply, confirm_to_inbound( + fun (DTag, Multi) -> + #'basic.nack'{delivery_tag = DTag, multiple = Multi} + end, Seq, Multiple, State)}; + +handle_info(#'basic.cancel'{}, State = #state{name = Name}) -> + rabbit_log:warning("Shovel ~p received 'basic.cancel' from the broker~n", + [Name]), + {stop, {shutdown, restart}, State}; + +handle_info({'EXIT', InboundConn, Reason}, + State = #state{inbound_conn = InboundConn}) -> + {stop, {inbound_conn_died, Reason}, State}; + +handle_info({'EXIT', OutboundConn, Reason}, + State = #state{outbound_conn = OutboundConn}) -> + {stop, {outbound_conn_died, Reason}, State}. + +terminate(Reason, #state{inbound_conn = undefined, inbound_ch = undefined, + outbound_conn = undefined, outbound_ch = undefined, + name = Name, type = Type}) -> + rabbit_shovel_status:report(Name, Type, {terminated, Reason}), + ok; +terminate(Reason, State) -> + maybe_autodelete(Reason, State), + catch amqp_connection:close(State#state.inbound_conn, + ?MAX_CONNECTION_CLOSE_TIMEOUT), + catch amqp_connection:close(State#state.outbound_conn, + ?MAX_CONNECTION_CLOSE_TIMEOUT), + rabbit_shovel_status:report(State#state.name, State#state.type, + {terminated, Reason}), + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%--------------------------- +%% Helpers +%%--------------------------- + +confirm_to_inbound(MsgCtr, Seq, Multiple, State = + #state{inbound_ch = InboundChan, unacked = Unacked}) -> + ok = amqp_channel:cast( + InboundChan, MsgCtr(gb_trees:get(Seq, Unacked), Multiple)), + {Unacked1, Removed} = remove_delivery_tags(Seq, Multiple, Unacked, 0), + decr_remaining(Removed, State#state{unacked = Unacked1}). + +remove_delivery_tags(Seq, false, Unacked, 0) -> + {gb_trees:delete(Seq, Unacked), 1}; +remove_delivery_tags(Seq, true, Unacked, Count) -> + case gb_trees:is_empty(Unacked) of + true -> {Unacked, Count}; + false -> {Smallest, _Val, Unacked1} = gb_trees:take_smallest(Unacked), + case Smallest > Seq of + true -> {Unacked, Count}; + false -> remove_delivery_tags(Seq, true, Unacked1, Count+1) + end + end. + +report_running(State) -> + rabbit_shovel_status:report( + State#state.name, State#state.type, + {running, [{src_uri, State#state.inbound_uri}, + {dest_uri, State#state.outbound_uri}]}). + +publish(_Tag, _Method, _Msg, State = #state{remaining_unacked = 0}) -> + %% We are in on-confirm mode, and are autodelete. We have + %% published all the messages we need to; we just wait for acks to + %% come back. So drop subsequent messages on the floor to be + %% requeued later. + State; + +publish(Tag, Method, Msg, + State = #state{inbound_ch = InboundChan, outbound_ch = OutboundChan, + config = Config, unacked = Unacked}) -> + Seq = case Config#shovel.ack_mode of + on_confirm -> amqp_channel:next_publish_seqno(OutboundChan); + _ -> undefined + end, + ok = amqp_channel:call(OutboundChan, Method, Msg), + decr_remaining_unacked( + case Config#shovel.ack_mode of + no_ack -> decr_remaining(1, State); + on_confirm -> State#state{unacked = gb_trees:insert( + Seq, Tag, Unacked)}; + on_publish -> ok = amqp_channel:cast( + InboundChan, #'basic.ack'{delivery_tag = Tag}), + decr_remaining(1, State) + end). + +make_conn_and_chan(URIs) -> + URI = lists:nth(random:uniform(length(URIs)), URIs), + {ok, AmqpParam} = amqp_uri:parse(URI), + {ok, Conn} = amqp_connection:start(AmqpParam), + link(Conn), + {ok, Chan} = amqp_connection:open_channel(Conn), + {Conn, Chan, list_to_binary(amqp_uri:remove_credentials(URI))}. + +remaining(Ch, #shovel{delete_after = never}) -> + unlimited; +remaining(Ch, #shovel{delete_after = 'queue-length', queue = Queue}) -> + #'queue.declare_ok'{message_count = N} = + amqp_channel:call(Ch, #'queue.declare'{queue = Queue, + passive = true}), + N; +remaining(Ch, #shovel{delete_after = Count}) -> + Count. + +decr_remaining(_N, State = #state{remaining = unlimited}) -> + State; +decr_remaining(N, State = #state{remaining = M}) -> + case M > N of + true -> State#state{remaining = M - N}; + false -> exit({shutdown, autodelete}) + end. + +decr_remaining_unacked(State = #state{remaining_unacked = unlimited}) -> + State; +decr_remaining_unacked(State = #state{remaining_unacked = 0}) -> + State; +decr_remaining_unacked(State = #state{remaining_unacked = N}) -> + State#state{remaining_unacked = N - 1}. + +maybe_autodelete({shutdown, autodelete}, #state{name = {VHost, Name}, + type = dynamic}) -> + %% See rabbit_shovel_dyn_worker_sup_sup:stop_child/1 + put(shovel_worker_autodelete, true), + rabbit_runtime_parameters:clear(VHost, <<"shovel">>, Name); +maybe_autodelete(_Reason, _State) -> + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_worker_sup.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_worker_sup.erl new file mode 100644 index 0000000..3528f9b --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbit_shovel_worker_sup.erl @@ -0,0 +1,40 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_shovel_worker_sup). +-behaviour(mirrored_supervisor). + +-export([start_link/2, init/1]). + +-include("rabbit_shovel.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +start_link(ShovelName, ShovelConfig) -> + mirrored_supervisor:start_link({local, ShovelName}, ShovelName, + fun rabbit_misc:execute_mnesia_transaction/1, + ?MODULE, [ShovelName, ShovelConfig]). + +init([Name, Config]) -> + ChildSpecs = [{Name, + {rabbit_shovel_worker, start_link, [static, Name, Config]}, + case proplists:get_value(reconnect_delay, Config, none) of + N when is_integer(N) andalso N > 0 -> {permanent, N}; + _ -> temporary + end, + 16#ffffffff, + worker, + [rabbit_shovel_worker]}], + {ok, {{one_for_one, 1, ?MAX_WAIT}, ChildSpecs}}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbitmq_shovel.app.src b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbitmq_shovel.app.src new file mode 100644 index 0000000..895aa75 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/src/rabbitmq_shovel.app.src @@ -0,0 +1,13 @@ +{application, rabbitmq_shovel, + [{description, "Data Shovel for RabbitMQ"}, + {vsn, "%%VSN%%"}, + {modules, []}, + {registered, []}, + {env, [{defaults, [{prefetch_count, 1000}, + {ack_mode, on_confirm}, + {publish_fields, []}, + {publish_properties, []}, + {reconnect_delay, 5}] + }]}, + {mod, {rabbit_shovel, []}}, + {applications, [kernel, stdlib, rabbit, amqp_client]}]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/test/src/rabbit_shovel_test.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/test/src/rabbit_shovel_test.erl new file mode 100644 index 0000000..efb62a7 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/test/src/rabbit_shovel_test.erl @@ -0,0 +1,247 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_shovel_test). +-export([test/0]). +-include_lib("amqp_client/include/amqp_client.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-define(EXCHANGE, <<"test_exchange">>). +-define(TO_SHOVEL, <<"to_the_shovel">>). +-define(FROM_SHOVEL, <<"from_the_shovel">>). +-define(UNSHOVELLED, <<"unshovelled">>). +-define(SHOVELLED, <<"shovelled">>). +-define(TIMEOUT, 1000). + +main_test() -> + %% it may already be running. Stop if possible + application:stop(rabbitmq_shovel), + + %% shovel can be started with zero shovels configured + ok = application:start(rabbitmq_shovel), + ok = application:stop(rabbitmq_shovel), + + %% various ways of breaking the config + require_list_of_shovel_configurations = + test_broken_shovel_configs(invalid_config), + + require_list_of_shovel_configurations = + test_broken_shovel_configs([{test_shovel, invalid_shovel_config}]), + + Config = [{sources, [{broker, "amqp://"}]}, + {destinations, [{broker, "amqp://"}]}, + {queue, <<"">>}], + + {duplicate_shovel_definition, test_shovel} = + test_broken_shovel_configs( + [{test_shovel, Config}, {test_shovel, Config}]), + + {invalid_parameters, [{invalid, invalid, invalid}]} = + test_broken_shovel_config([{invalid, invalid, invalid} | Config]), + + {duplicate_parameters, [queue]} = + test_broken_shovel_config([{queue, <<"">>} | Config]), + + {missing_parameters, Missing} = + test_broken_shovel_config([]), + [destinations, queue, sources] = lists:sort(Missing), + + {unrecognised_parameters, [invalid]} = + test_broken_shovel_config([{invalid, invalid} | Config]), + + {require_list, invalid} = + test_broken_shovel_sources(invalid), + + {missing_endpoint_parameter, broker_or_brokers} = + test_broken_shovel_sources([]), + + {expected_list, brokers, invalid} = + test_broken_shovel_sources([{brokers, invalid}]), + + {expected_string_uri, 42} = + test_broken_shovel_sources([{brokers, [42]}]), + + {{unexpected_uri_scheme, "invalid"}, "invalid://"} = + test_broken_shovel_sources([{broker, "invalid://"}]), + + {{unable_to_parse_uri, no_scheme}, "invalid"} = + test_broken_shovel_sources([{broker, "invalid"}]), + + {expected_list,declarations, invalid} = + test_broken_shovel_sources([{broker, "amqp://"}, + {declarations, invalid}]), + {unknown_method_name, 42} = + test_broken_shovel_sources([{broker, "amqp://"}, + {declarations, [42]}]), + + {expected_method_field_list, 'queue.declare', 42} = + test_broken_shovel_sources([{broker, "amqp://"}, + {declarations, [{'queue.declare', 42}]}]), + + {unknown_fields, 'queue.declare', [invalid]} = + test_broken_shovel_sources( + [{broker, "amqp://"}, + {declarations, [{'queue.declare', [invalid]}]}]), + + {{invalid_amqp_params_parameter, heartbeat, "text", + [{"heartbeat", "text"}], {not_an_integer, "text"}}, _} = + test_broken_shovel_sources( + [{broker, "amqp://localhost/?heartbeat=text"}]), + + {{invalid_amqp_params_parameter, username, "text", + [{"username", "text"}], + {parameter_unconfigurable_in_query, username, "text"}}, _} = + test_broken_shovel_sources([{broker, "amqp://?username=text"}]), + + {invalid_parameter_value, prefetch_count, + {require_non_negative_integer, invalid}} = + test_broken_shovel_config([{prefetch_count, invalid} | Config]), + + {invalid_parameter_value, ack_mode, + {ack_mode_value_requires_one_of, + {no_ack, on_publish, on_confirm}, invalid}} = + test_broken_shovel_config([{ack_mode, invalid} | Config]), + + {invalid_parameter_value, queue, + {require_binary, invalid}} = + test_broken_shovel_config([{sources, [{broker, "amqp://"}]}, + {destinations, [{broker, "amqp://"}]}, + {queue, invalid}]), + + {invalid_parameter_value, publish_properties, + {require_list, invalid}} = + test_broken_shovel_config([{publish_properties, invalid} | Config]), + + {invalid_parameter_value, publish_properties, + {unexpected_fields, [invalid], _}} = + test_broken_shovel_config([{publish_properties, [invalid]} | Config]), + + {{invalid_ssl_parameter, fail_if_no_peer_cert, "42", _, + {require_boolean, '42'}}, _} = + test_broken_shovel_sources([{broker, "amqps://username:password@host:5673/vhost?cacertfile=/path/to/cacert.pem&certfile=/path/to/certfile.pem&keyfile=/path/to/keyfile.pem&verify=verify_peer&fail_if_no_peer_cert=42"}]), + + %% a working config + application:set_env( + rabbitmq_shovel, + shovels, + [{test_shovel, + [{sources, + [{broker, "amqp:///%2f?heartbeat=5"}, + {declarations, + [{'queue.declare', [exclusive, auto_delete]}, + {'exchange.declare', [{exchange, ?EXCHANGE}, auto_delete]}, + {'queue.bind', [{queue, <<>>}, {exchange, ?EXCHANGE}, + {routing_key, ?TO_SHOVEL}]} + ]}]}, + {destinations, + [{broker, "amqp:///%2f"}]}, + {queue, <<>>}, + {ack_mode, on_confirm}, + {publish_fields, [{exchange, ?EXCHANGE}, {routing_key, ?FROM_SHOVEL}]}, + {publish_properties, [{delivery_mode, 2}, + {cluster_id, <<"my-cluster">>}, + {content_type, ?SHOVELLED}]} + ]}], + infinity), + + ok = application:start(rabbitmq_shovel), + + await_running_shovel(test_shovel), + + {ok, Conn} = amqp_connection:start(#amqp_params_network{}), + {ok, Chan} = amqp_connection:open_channel(Conn), + + #'queue.declare_ok'{ queue = Q } = + amqp_channel:call(Chan, #'queue.declare' { exclusive = true }), + #'queue.bind_ok'{} = + amqp_channel:call(Chan, #'queue.bind' { queue = Q, exchange = ?EXCHANGE, + routing_key = ?FROM_SHOVEL }), + #'queue.bind_ok'{} = + amqp_channel:call(Chan, #'queue.bind' { queue = Q, exchange = ?EXCHANGE, + routing_key = ?TO_SHOVEL }), + + #'basic.consume_ok'{ consumer_tag = CTag } = + amqp_channel:subscribe(Chan, + #'basic.consume' { queue = Q, exclusive = true }, + self()), + receive + #'basic.consume_ok'{ consumer_tag = CTag } -> ok + after ?TIMEOUT -> throw(timeout_waiting_for_consume_ok) + end, + + ok = amqp_channel:call(Chan, + #'basic.publish' { exchange = ?EXCHANGE, + routing_key = ?TO_SHOVEL }, + #amqp_msg { payload = <<42>>, + props = #'P_basic' { + delivery_mode = 2, + content_type = ?UNSHOVELLED } + }), + + receive + {#'basic.deliver' { consumer_tag = CTag, delivery_tag = AckTag, + routing_key = ?FROM_SHOVEL }, + #amqp_msg { payload = <<42>>, + props = #'P_basic' { delivery_mode = 2, + content_type = ?SHOVELLED } + }} -> + ok = amqp_channel:call(Chan, #'basic.ack'{ delivery_tag = AckTag }) + after ?TIMEOUT -> throw(timeout_waiting_for_deliver1) + end, + + [{test_shovel, static, {running, _Info}, _Time}] = + rabbit_shovel_status:status(), + + receive + {#'basic.deliver' { consumer_tag = CTag, delivery_tag = AckTag1, + routing_key = ?TO_SHOVEL }, + #amqp_msg { payload = <<42>>, + props = #'P_basic' { delivery_mode = 2, + content_type = ?UNSHOVELLED } + }} -> + ok = amqp_channel:call(Chan, #'basic.ack'{ delivery_tag = AckTag1 }) + after ?TIMEOUT -> throw(timeout_waiting_for_deliver2) + end, + + amqp_channel:close(Chan), + amqp_connection:close(Conn), + + ok. + +test_broken_shovel_configs(Configs) -> + application:set_env(rabbitmq_shovel, shovels, Configs), + {error, {Error, _}} = application:start(rabbitmq_shovel), + Error. + +test_broken_shovel_config(Config) -> + {invalid_shovel_configuration, test_shovel, Error} = + test_broken_shovel_configs([{test_shovel, Config}]), + Error. + +test_broken_shovel_sources(Sources) -> + {invalid_parameter_value, sources, Error} = + test_broken_shovel_config([{sources, Sources}, + {destinations, [{broker, "amqp://"}]}, + {queue, <<"">>}]), + Error. + +await_running_shovel(Name) -> + case [Name || {Name, _, {running, _}, _} + <- rabbit_shovel_status:status()] of + [_] -> ok; + _ -> timer:sleep(100), + await_running_shovel(Name) + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/test/src/rabbit_shovel_test_all.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/test/src/rabbit_shovel_test_all.erl new file mode 100644 index 0000000..81b568a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/test/src/rabbit_shovel_test_all.erl @@ -0,0 +1,33 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_shovel_test_all). + +-export([all_tests/0]). + +all_tests() -> + ok = eunit:test(tests(rabbit_shovel_test, 60), [verbose]), + ok = eunit:test(tests(rabbit_shovel_test_dyn, 60), [verbose]). + +tests(Module, Timeout) -> + {foreach, fun() -> ok end, + [{timeout, Timeout, fun Module:F/0} || F <- funs(Module, "_test")] ++ + [{timeout, Timeout, Fun} || Gen <- funs(Module, "_test_"), + Fun <- Module:Gen()]}. + +funs(Module, Suffix) -> + [F || {F, _Arity} <- proplists:get_value(exports, Module:module_info()), + string:right(atom_to_list(F), length(Suffix)) =:= Suffix]. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/test/src/rabbit_shovel_test_dyn.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/test/src/rabbit_shovel_test_dyn.erl new file mode 100644 index 0000000..5a8ead7 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-shovel/test/src/rabbit_shovel_test_dyn.erl @@ -0,0 +1,275 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ Federation. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_shovel_test_dyn). + +-include_lib("eunit/include/eunit.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +-import(rabbit_misc, [pget/2]). + +simple_test() -> + with_ch( + fun (Ch) -> + set_param(<<"test">>, [{<<"src-queue">>, <<"src">>}, + {<<"dest-queue">>, <<"dest">>}]), + publish_expect(Ch, <<>>, <<"src">>, <<"dest">>, <<"hello">>) + end). + +exchange_test() -> + with_ch( + fun (Ch) -> + amqp_channel:call(Ch, #'queue.declare'{queue = <<"queue">>, + durable = true}), + amqp_channel:call( + Ch, #'queue.bind'{queue = <<"queue">>, + exchange = <<"amq.topic">>, + routing_key = <<"test-key">>}), + set_param(<<"test">>, [{<<"src-exchange">>, <<"amq.direct">>}, + {<<"src-exchange-key">>,<<"test-key">>}, + {<<"dest-exchange">>, <<"amq.topic">>}]), + publish_expect(Ch, <<"amq.direct">>, <<"test-key">>, + <<"queue">>, <<"hello">>), + set_param(<<"test">>, [{<<"src-exchange">>, <<"amq.direct">>}, + {<<"src-exchange-key">>, <<"test-key">>}, + {<<"dest-exchange">>, <<"amq.topic">>}, + {<<"dest-exchange-key">>,<<"new-key">>}]), + publish(Ch, <<"amq.direct">>, <<"test-key">>, <<"hello">>), + expect_empty(Ch, <<"queue">>), + amqp_channel:call( + Ch, #'queue.bind'{queue = <<"queue">>, + exchange = <<"amq.topic">>, + routing_key = <<"new-key">>}), + publish_expect(Ch, <<"amq.direct">>, <<"test-key">>, + <<"queue">>, <<"hello">>) + end). + +restart_test() -> + with_ch( + fun (Ch) -> + set_param(<<"test">>, [{<<"src-queue">>, <<"src">>}, + {<<"dest-queue">>, <<"dest">>}]), + %% The catch is because connections link to the shovel, + %% so one connection will die, kill the shovel, kill + %% the other connection, then we can't close it + [catch amqp_connection:close(C) || C <- rabbit_direct:list()], + publish_expect(Ch, <<>>, <<"src">>, <<"dest">>, <<"hello">>) + end). + +change_definition_test() -> + with_ch( + fun (Ch) -> + set_param(<<"test">>, [{<<"src-queue">>, <<"src">>}, + {<<"dest-queue">>, <<"dest">>}]), + publish_expect(Ch, <<>>, <<"src">>, <<"dest">>, <<"hello">>), + set_param(<<"test">>, [{<<"src-queue">>, <<"src">>}, + {<<"dest-queue">>, <<"dest2">>}]), + publish_expect(Ch, <<>>, <<"src">>, <<"dest2">>, <<"hello">>), + expect_empty(Ch, <<"dest">>), + clear_param(<<"test">>), + publish_expect(Ch, <<>>, <<"src">>, <<"src">>, <<"hello">>), + expect_empty(Ch, <<"dest">>), + expect_empty(Ch, <<"dest2">>) + end). + +autodelete_test_() -> + [autodelete_case({<<"on-confirm">>, <<"queue-length">>, 0, 100}), + autodelete_case({<<"on-confirm">>, 50, 50, 50}), + autodelete_case({<<"on-publish">>, <<"queue-length">>, 0, 100}), + autodelete_case({<<"on-publish">>, 50, 50, 50}), + %% no-ack is not compatible with explicit count + autodelete_case({<<"no-ack">>, <<"queue-length">>, 0, 100})]. + +autodelete_case(Args) -> + fun () -> with_ch(autodelete_do(Args)) end. + +autodelete_do({AckMode, After, ExpSrc, ExpDest}) -> + fun (Ch) -> + amqp_channel:call(Ch, #'confirm.select'{}), + amqp_channel:call(Ch, #'queue.declare'{queue = <<"src">>}), + publish_count(Ch, <<>>, <<"src">>, <<"hello">>, 100), + amqp_channel:wait_for_confirms(Ch), + set_param_nowait(<<"test">>, [{<<"src-queue">>, <<"src">>}, + {<<"dest-queue">>, <<"dest">>}, + {<<"ack-mode">>, AckMode}, + {<<"delete-after">>, After}]), + await_autodelete(<<"test">>), + expect_count(Ch, <<"src">>, <<"hello">>, ExpSrc), + expect_count(Ch, <<"dest">>, <<"hello">>, ExpDest) + end. + +validation_test() -> + URIs = [{<<"src-uri">>, <<"amqp://">>}, + {<<"dest-uri">>, <<"amqp://">>}], + + %% Need valid src and dest URIs + invalid_param([]), + invalid_param([{<<"src-queue">>, <<"test">>}, + {<<"src-uri">>, <<"derp">>}, + {<<"dest-uri">>, <<"amqp://">>}]), + invalid_param([{<<"src-queue">>, <<"test">>}, + {<<"src-uri">>, [<<"derp">>]}, + {<<"dest-uri">>, <<"amqp://">>}]), + invalid_param([{<<"src-queue">>, <<"test">>}, + {<<"dest-uri">>, <<"amqp://">>}]), + + %% Also need src exchange or queue + invalid_param(URIs), + valid_param([{<<"src-exchange">>, <<"test">>} | URIs]), + QURIs = [{<<"src-queue">>, <<"test">>} | URIs], + valid_param(QURIs), + + %% But not both + invalid_param([{<<"src-exchange">>, <<"test">>} | QURIs]), + + %% Check these are of right type + invalid_param([{<<"prefetch-count">>, <<"three">>} | QURIs]), + invalid_param([{<<"reconnect-delay">>, <<"three">>} | QURIs]), + invalid_param([{<<"ack-mode">>, <<"whenever">>} | QURIs]), + invalid_param([{<<"delete-after">>, <<"whenever">>} | QURIs]), + + %% Can't use explicit message count and no-ack together + invalid_param([{<<"delete-after">>, 1}, + {<<"ack-mode">>, <<"no-ack">>} | QURIs]), + ok. + +security_validation_test() -> + [begin + rabbit_vhost:add(U), + rabbit_auth_backend_internal:add_user(U, <<>>), + rabbit_auth_backend_internal:set_permissions( + U, U, <<".*">>, <<".*">>, <<".*">>) + end || U <- [<<"a">>, <<"b">>]], + + Qs = [{<<"src-queue">>, <<"test">>}, + {<<"dest-queue">>, <<"test2">>}], + + A = lookup_user(<<"a">>), + valid_param([{<<"src-uri">>, <<"amqp:///a">>}, + {<<"dest-uri">>, <<"amqp:///a">>} | Qs], A), + invalid_param([{<<"src-uri">>, <<"amqp:///a">>}, + {<<"dest-uri">>, <<"amqp:///b">>} | Qs], A), + invalid_param([{<<"src-uri">>, <<"amqp:///b">>}, + {<<"dest-uri">>, <<"amqp:///a">>} | Qs], A), + [begin + rabbit_vhost:delete(U), + rabbit_auth_backend_internal:delete_user(U) + end || U <- [<<"a">>, <<"b">>]], + ok. + +%%---------------------------------------------------------------------------- + +with_ch(Fun) -> + {ok, Conn} = amqp_connection:start(#amqp_params_network{}), + {ok, Ch} = amqp_connection:open_channel(Conn), + Fun(Ch), + amqp_connection:close(Conn), + cleanup(), + ok. + +publish(Ch, X, Key, Payload) when is_binary(Payload) -> + publish(Ch, X, Key, #amqp_msg{payload = Payload}); + +publish(Ch, X, Key, Msg = #amqp_msg{}) -> + amqp_channel:cast(Ch, #'basic.publish'{exchange = X, + routing_key = Key}, Msg). + +publish_expect(Ch, X, Key, Q, Payload) -> + publish(Ch, X, Key, Payload), + expect(Ch, Q, Payload). + +expect(Ch, Q, Payload) -> + amqp_channel:subscribe(Ch, #'basic.consume'{queue = Q, + no_ack = true}, self()), + receive + #'basic.consume_ok'{consumer_tag = CTag} -> ok + end, + receive + {#'basic.deliver'{}, #amqp_msg{payload = Payload}} -> + ok + after 1000 -> + exit({not_received, Payload}) + end, + amqp_channel:call(Ch, #'basic.cancel'{consumer_tag = CTag}). + +expect_empty(Ch, Q) -> + ?assertMatch(#'basic.get_empty'{}, + amqp_channel:call(Ch, #'basic.get'{ queue = Q })). + +publish_count(Ch, X, Key, M, Count) -> + [publish(Ch, X, Key, M) || _ <- lists:seq(1, Count)]. + +expect_count(Ch, Q, M, Count) -> + [expect(Ch, Q, M) || _ <- lists:seq(1, Count)], + expect_empty(Ch, Q). + +set_param(Name, Value) -> + set_param_nowait(Name, Value), + await_shovel(Name). + +set_param_nowait(Name, Value) -> + ok = rabbit_runtime_parameters:set( + <<"/">>, <<"shovel">>, Name, [{<<"src-uri">>, <<"amqp://">>}, + {<<"dest-uri">>, [<<"amqp://">>]} | + Value], none). + +invalid_param(Value, User) -> + {error_string, _} = rabbit_runtime_parameters:set( + <<"/">>, <<"shovel">>, <<"invalid">>, Value, User). + +valid_param(Value, User) -> + ok = rabbit_runtime_parameters:set( + <<"/">>, <<"shovel">>, <<"a">>, Value, User), + ok = rabbit_runtime_parameters:clear(<<"/">>, <<"shovel">>, <<"a">>). + +invalid_param(Value) -> invalid_param(Value, none). +valid_param(Value) -> valid_param(Value, none). + +lookup_user(Name) -> + {ok, User} = rabbit_auth_backend_internal:check_user_login(Name, []), + User. + +clear_param(Name) -> + rabbit_runtime_parameters:clear(<<"/">>, <<"shovel">>, Name). + +cleanup() -> + [rabbit_runtime_parameters:clear(pget(vhost, P), + pget(component, P), + pget(name, P)) || + P <- rabbit_runtime_parameters:list()], + [rabbit_amqqueue:delete(Q, false, false) || Q <- rabbit_amqqueue:list()]. + +await_shovel(Name) -> + await(fun () -> lists:member(Name, shovels_from_status()) end). + +await_autodelete(Name) -> + await(fun () -> not lists:member(Name, shovels_from_parameters()) end), + await(fun () -> not lists:member(Name, shovels_from_status()) end). + +await(Pred) -> + case Pred() of + true -> ok; + false -> timer:sleep(100), + await(Pred) + end. + +shovels_from_status() -> + S = rabbit_shovel_status:status(), + [N || {{<<"/">>, N}, dynamic, {running, _}, _} <- S]. + +shovels_from_parameters() -> + L = rabbit_runtime_parameters:list(<<"/">>, <<"shovel">>), + [pget(name, Shovel) || Shovel <- L]. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/.srcdist_done b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/.srcdist_done new file mode 100644 index 0000000..e69de29 diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/Makefile b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/Makefile new file mode 100644 index 0000000..482105a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/Makefile @@ -0,0 +1 @@ +include ../umbrella.mk diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/NOTES b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/NOTES new file mode 100644 index 0000000..b4a9f02 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/NOTES @@ -0,0 +1,71 @@ +Comments from Sean Treadway, 2 June 2008, on the rabbitmq-discuss list: + + - On naming, extensibility, and headers: + + "STOMP looked like it was MQ agnostic and extensible while keeping + the core headers well defined (ack=client, message_id, etc...), + but my application was not MQ agnostic. Plus I saw some of the + ActiveMQ headers weren't available or necessary in RabbitMQ. + + "Keeping the AMQP naming is the best way to piggy back on the AMQP + documentation. For those that need simple, transient queues, the + existing STOMP documentation would be sufficient." + + ... + + "I only have experience with RabbitMQ, so I'm fine with exposing + AMQP rather than try to come to some agreement over the extension + names of standard STOMP headers." + + - On queue deletion over STOMP: + + "Here, I would stick with the verbs defined in STOMP and extend the + verbs with headers. One possibility is to use UNSUBSCRIBE + messages to change the queue properties before sending the + 'basic.cancel' method. Another possibility is to change queue + properties on a SUBSCRIBE message. Neither seem nice to me. Third + option is to do nothing, and delete the queues outside of the + STOMP protocol" + +Comments from Darien Kindlund, 11 February 2009, on the rabbitmq-discuss list: + + - On testing of connection establishment: + + "[O]nce I switched each perl process over to re-using their + existing STOMP connection, things worked much, much better. As + such, I'm continuing development. In your unit testing, you may + want to include rapid connect/disconnect behavior or otherwise + explicitly warn developers to avoid this scenario." + +Comments from Novak Joe, 11 September 2008, on the rabbitmq-discuss list: + + - On broadcast send: + + "That said, I think it would also be useful to add to the STOMP + wiki page an additional note on broadcast SEND. In particular I + found that in order to send a message to a broadcast exchange it + needs look something like: + + --------------------------------- + SEND + destination:x.mytopic + exchange:amq.topic + + my message + \x00 + -------------------------------- + + "However my initial newb intuition was that it should look more like: + + --------------------------------- + SEND + destination: + exchange:amq.topic + routing_key:x.mytopic + + my message + \x00 + -------------------------------- + + "The ruby examples cleared this up but not before I experienced a + bit of confusion on the subject." diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/README.md b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/README.md new file mode 100644 index 0000000..df7b890 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/README.md @@ -0,0 +1,13 @@ +# RabbitMQ STOMP adapter + +The STOMP adapter is included in the RabbitMQ distribution. To enable +it, use rabbitmq-plugins
: + + rabbitmq-plugins enable rabbitmq_stomp + +Binaries for previous versions of the STOMP adapter can be obtained +from +. + +Full usage instructions can be found at +. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/deps/stomppy/Makefile b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/deps/stomppy/Makefile new file mode 100644 index 0000000..a937fb5 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/deps/stomppy/Makefile @@ -0,0 +1,28 @@ +UPSTREAM_HG=https://stomppy.googlecode.com/hg/ +REVISION=16a4000624a7 + +LIB_DIR=stomppy +CHECKOUT_DIR=stomppy-hg + +TARGETS=$(LIB_DIR) + +all: $(TARGETS) + +clean: + rm -rf $(LIB_DIR) + +distclean: clean + rm -rf $(CHECKOUT_DIR) + +$(LIB_DIR) : $(CHECKOUT_DIR) rabbit.patch + rm -rf $@ + cp -R $< $@ + cd $@ && patch -p1 < ../rabbit.patch + +$(CHECKOUT_DIR): + hg clone $(UPSTREAM_HG) $@ + (cd $@ && hg up $(REVISION)) || rm -rf $@ + +echo-revision: + @echo $(REVISION) + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/deps/stomppy/rabbit.patch b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/deps/stomppy/rabbit.patch new file mode 100644 index 0000000..ceebd16 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/deps/stomppy/rabbit.patch @@ -0,0 +1,107 @@ +diff -r 16a4000624a7 stomp/connect.py +--- a/stomp/connect.py Sun May 02 18:15:34 2010 +0100 ++++ b/stomp/connect.py Fri Aug 26 15:35:33 2011 +0100 +@@ -88,7 +88,10 @@ + ssl_key_file = None, + ssl_cert_file = None, + ssl_ca_certs = None, +- ssl_cert_validator = None): ++ ssl_cert_validator = None, ++ version = None, ++ heartbeat = None, ++ virtual_host = None): + """ + Initialize and start this connection. + +@@ -159,6 +162,16 @@ + + where OK is a boolean, and cert is a certificate structure + as returned by ssl.SSLSocket.getpeercert() ++ ++ \param version ++ (optional) stomp version header to send (comma separated) ++ ++ \param heartbeat ++ (optional) heartbeat header to send (STOMP 1.1) ++ ++ \param virtual_host ++ (optional) virtual_host header to send (STOMP 1.1) ++ + """ + + sorted_host_and_ports = [] +@@ -205,6 +218,15 @@ + self.__connect_headers['login'] = user + self.__connect_headers['passcode'] = passcode + ++ if version is not None: ++ self.__connect_headers['accept-version'] = version ++ ++ if heartbeat is not None: ++ self.__connect_headers['heart-beat'] = heartbeat ++ ++ if virtual_host is not None: ++ self.__connect_headers['host'] = virtual_host ++ + self.__socket = None + self.__socket_semaphore = threading.BoundedSemaphore(1) + self.__current_host_and_port = None +@@ -383,6 +405,10 @@ + """ + self.__send_frame_helper('DISCONNECT', '', utils.merge_headers([self.__connect_headers, headers, keyword_headers]), [ ]) + self.__running = False ++ self.close_socket() ++ self.__current_host_and_port = None ++ ++ def close_socket(self): + if self.__socket is not None: + if self.__ssl: + # +@@ -390,20 +416,23 @@ + # + try: + self.__socket = self.__socket.unwrap() +- except Exception: ++ except Exception as e: + # + # unwrap seems flaky on Win with the backported ssl mod, so catch any exception and log it + # +- _, e, _ = sys.exc_info() +- log.warn(e) ++ log.warning("socket unwrap() threw exception: %s" % e) + elif hasattr(socket, 'SHUT_RDWR'): +- self.__socket.shutdown(socket.SHUT_RDWR) ++ try: ++ self.__socket.shutdown(socket.SHUT_RDWR) ++ except Exception as e: ++ log.warning("socket shutdown() threw exception: %s" % e) + # +- # split this into a separate check, because sometimes the socket is nulled between shutdown and this call ++ # caution, because sometimes the socket is nulled between shutdown and this call + # +- if self.__socket is not None: ++ try: + self.__socket.close() +- self.__current_host_and_port = None ++ except Exception as e: ++ log.warning("socket close() threw exception: %s" % e) + + def __convert_dict(self, payload): + """ +@@ -449,6 +478,9 @@ + raise KeyError("Command %s requires header %r" % (command, required_header_key)) + self.__send_frame(command, headers, payload) + ++ def send_frame(self, command, headers={}, payload=''): ++ self.__send_frame(command, headers, payload) ++ + def __send_frame(self, command, headers={}, payload=''): + """ + Send a STOMP frame. +@@ -680,4 +712,4 @@ + sleep_exp += 1 + + if not self.__socket: +- raise exception.ReconnectFailedException +\ No newline at end of file ++ raise exception.ReconnectFailedException diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_recv.pl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_recv.pl new file mode 100755 index 0000000..7b8b9cc --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_recv.pl @@ -0,0 +1,13 @@ +#!/usr/bin/perl -w +# subscribe to messages from the queue 'foo' +use Net::Stomp; +my $stomp = Net::Stomp->new({hostname=>'localhost', port=>'61613'}); +$stomp->connect({login=>'guest', passcode=>'guest'}); +$stomp->subscribe({'destination'=>'/queue/foo', 'ack'=>'client'}); +while (1) { + my $frame = $stomp->receive_frame; + print $frame->body . "\n"; + $stomp->ack({frame=>$frame}); + last if $frame->body eq 'QUIT'; +} +$stomp->disconnect; diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_rpc_client.pl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_rpc_client.pl new file mode 100755 index 0000000..3a98e16 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_rpc_client.pl @@ -0,0 +1,15 @@ +#!/usr/bin/perl -w + +use Net::Stomp; +my $stomp = Net::Stomp->new({hostname=>'localhost', port=>'61613'}); +$stomp->connect({login=>'guest', passcode=>'guest'}); + +my $private_q_name = "/queue/c-" . time() . "-" . rand(); + +$stomp->subscribe({destination => $private_q_name}); +$stomp->send({destination => '/queue/rabbitmq_stomp_rpc_service', + 'reply-to' => $private_q_name, + body => "request from $private_q_name"}); +print "Reply: " . $stomp->receive_frame->body; + +$stomp->disconnect; diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_rpc_service.pl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_rpc_service.pl new file mode 100755 index 0000000..31e79ae --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_rpc_service.pl @@ -0,0 +1,21 @@ +#!/usr/bin/perl -w + +use Net::Stomp; + +my $stomp = Net::Stomp->new({hostname=>'localhost', port=>'61613'}); +$stomp->connect({login=>'guest', passcode=>'guest'}); + +$stomp->subscribe({'destination'=>'/queue/rabbitmq_stomp_rpc_service', 'ack'=>'client'}); +while (1) { + print "Waiting for request...\n"; + my $frame = $stomp->receive_frame; + print "Received message, reply_to = " . $frame->headers->{"reply-to"} . "\n"; + print $frame->body . "\n"; + + $stomp->send({destination => $frame->headers->{"reply-to"}, bytes_message => 1, + body => "Got body: " . $frame->body}); + $stomp->ack({frame=>$frame}); + last if $frame->body eq 'QUIT'; +} + +$stomp->disconnect; diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_send.pl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_send.pl new file mode 100755 index 0000000..4d26b78 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_send.pl @@ -0,0 +1,9 @@ +#!/usr/bin/perl -w +# send a message to the queue 'foo' +use Net::Stomp; +my $stomp = Net::Stomp->new({hostname=>'localhost', port=>'61613'}); +$stomp->connect({login=>'guest', passcode=>'guest'}); +$stomp->send({destination=>'/exchange/amq.fanout', + bytes_message=>1, + body=>($ARGV[0] or "test\0message")}); +$stomp->disconnect; diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_send_many.pl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_send_many.pl new file mode 100755 index 0000000..f6ff54e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_send_many.pl @@ -0,0 +1,11 @@ +#!/usr/bin/perl -w +# send a message to the queue 'foo' +use Net::Stomp; +my $stomp = Net::Stomp->new({hostname=>'localhost', port=>'61613'}); +$stomp->connect({login=>'guest', passcode=>'guest'}); +for (my $i = 0; $i < 10000; $i++) { + $stomp->send({destination=>'/queue/foo', + bytes_message=>1, + body=>($ARGV[0] or "message $i")}); +} +$stomp->disconnect; diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_slow_recv.pl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_slow_recv.pl new file mode 100755 index 0000000..043568f --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/perl/rabbitmq_stomp_slow_recv.pl @@ -0,0 +1,14 @@ +#!/usr/bin/perl -w +# subscribe to messages from the queue 'foo' +use Net::Stomp; +my $stomp = Net::Stomp->new({hostname=>'localhost', port=>'61613'}); +$stomp->connect({login=>'guest', passcode=>'guest', prefetch=>1}); +$stomp->subscribe({'destination'=>'/queue/foo', 'ack'=>'client'}); +while (1) { + my $frame = $stomp->receive_frame; + print $frame->body . "\n"; + sleep 1; + $stomp->ack({frame=>$frame}); + last if $frame->body eq 'QUIT'; +} +$stomp->disconnect; diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/cb-receiver.rb b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/cb-receiver.rb new file mode 100644 index 0000000..4e6e261 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/cb-receiver.rb @@ -0,0 +1,8 @@ +require 'rubygems' +require 'stomp' + +conn = Stomp::Connection.open('guest', 'guest', 'localhost') +conn.subscribe('/queue/carl') +while mesg = conn.receive + puts mesg.body +end diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/cb-sender.rb b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/cb-sender.rb new file mode 100644 index 0000000..3d75946 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/cb-sender.rb @@ -0,0 +1,6 @@ +require 'rubygems' +require 'stomp' + +client = Stomp::Client.new("guest", "guest", "localhost", 61613) +10000.times { |i| client.publish '/queue/carl', "Test Message number #{i}"} +client.publish '/queue/carl', "All Done!" diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/cb-slow-receiver.rb b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/cb-slow-receiver.rb new file mode 100644 index 0000000..d98e5f8 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/cb-slow-receiver.rb @@ -0,0 +1,13 @@ +require 'rubygems' +require 'stomp' + +# Note: requires support for connect_headers hash in the STOMP gem's connection.rb +conn = Stomp::Connection.open('guest', 'guest', 'localhost', 61613, false, 5, {:prefetch => 1}) +conn.subscribe('/queue/carl', {:ack => 'client'}) +while mesg = conn.receive + puts mesg.body + puts 'Sleeping...' + sleep 0.2 + puts 'Awake again. Acking.' + conn.ack mesg.headers['message-id'] +end diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/persistent-receiver.rb b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/persistent-receiver.rb new file mode 100644 index 0000000..5a83df6 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/persistent-receiver.rb @@ -0,0 +1,11 @@ +require 'rubygems' +require 'stomp' + +conn = Stomp::Connection.open('guest', 'guest', 'localhost') +conn.subscribe('/queue/durable', :'auto-delete' => false, :durable => true) + +puts "Waiting for messages..." + +while mesg = conn.receive + puts mesg.body +end diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/persistent-sender.rb b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/persistent-sender.rb new file mode 100644 index 0000000..1be32d6 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/persistent-sender.rb @@ -0,0 +1,13 @@ +require 'rubygems' +require 'stomp' + +# Use this case to test durable queues +# +# Start the sender - 11 messages will be sent to /queue/durable and the sender exits +# Stop the server - 11 messages will be written to disk +# Start the server +# Start the receiver - 11 messages should be received and the receiver - interrupt the receive loop + +client = Stomp::Client.new("guest", "guest", "localhost", 61613) +10.times { |i| client.publish '/queue/durable', "Test Message number #{i} sent at #{Time.now}", 'delivery-mode' => '2'} +client.publish '/queue/durable', "All Done!" diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/topic-broadcast-receiver.rb b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/topic-broadcast-receiver.rb new file mode 100644 index 0000000..b338e53 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/topic-broadcast-receiver.rb @@ -0,0 +1,11 @@ +require 'rubygems' +require 'stomp' + +topic = ARGV[0] || 'x' +puts "Binding to /topic/#{topic}" + +conn = Stomp::Connection.open('guest', 'guest', 'localhost') +conn.subscribe("/topic/#{topic}") +while mesg = conn.receive + puts mesg.body +end diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/topic-broadcast-with-unsubscribe.rb b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/topic-broadcast-with-unsubscribe.rb new file mode 100644 index 0000000..19f05ee --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/topic-broadcast-with-unsubscribe.rb @@ -0,0 +1,13 @@ +require 'rubygems' +require 'stomp' # this is a gem + +conn = Stomp::Connection.open('guest', 'guest', 'localhost') +puts "Subscribing to /topic/x" +conn.subscribe('/topic/x') +puts 'Receiving...' +mesg = conn.receive +puts mesg.body +puts "Unsubscribing from /topic/x" +conn.unsubscribe('/topic/x') +puts 'Sleeping 5 seconds...' +sleep 5 diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/topic-sender.rb b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/topic-sender.rb new file mode 100644 index 0000000..b0861f9 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/examples/ruby/topic-sender.rb @@ -0,0 +1,7 @@ +require 'rubygems' +require 'stomp' + +client = Stomp::Client.new("guest", "guest", "localhost", 61613) +client.publish '/topic/x.y', 'first message' +client.publish '/topic/x.z', 'second message' +client.publish '/topic/x', 'third message' diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/include/rabbit_stomp.hrl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/include/rabbit_stomp.hrl new file mode 100644 index 0000000..62504a0 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/include/rabbit_stomp.hrl @@ -0,0 +1,22 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-record(stomp_configuration, {default_login, + default_passcode, + implicit_connect, + ssl_cert_login}). + +-define(SUPPORTED_VERSIONS, ["1.0", "1.1", "1.2"]). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/include/rabbit_stomp_frame.hrl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/include/rabbit_stomp_frame.hrl new file mode 100644 index 0000000..77a946c --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/include/rabbit_stomp_frame.hrl @@ -0,0 +1,17 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-record(stomp_frame, {command, headers, body_iolist}). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/include/rabbit_stomp_headers.hrl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/include/rabbit_stomp_headers.hrl new file mode 100644 index 0000000..c7ab43c --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/include/rabbit_stomp_headers.hrl @@ -0,0 +1,51 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2011-2014 GoPivotal, Inc. All rights reserved. +%% + +-define(HEADER_ACCEPT_VERSION, "accept-version"). +-define(HEADER_ACK, "ack"). +-define(HEADER_AMQP_MESSAGE_ID, "amqp-message-id"). +-define(HEADER_APP_ID, "app-id"). +-define(HEADER_CONTENT_ENCODING, "content-encoding"). +-define(HEADER_CONTENT_LENGTH, "content-length"). +-define(HEADER_CONTENT_TYPE, "content-type"). +-define(HEADER_CORRELATION_ID, "correlation-id"). +-define(HEADER_DESTINATION, "destination"). +-define(HEADER_EXPIRATION, "expiration"). +-define(HEADER_HEART_BEAT, "heart-beat"). +-define(HEADER_HOST, "host"). +-define(HEADER_ID, "id"). +-define(HEADER_LOGIN, "login"). +-define(HEADER_MESSAGE_ID, "message-id"). +-define(HEADER_PASSCODE, "passcode"). +-define(HEADER_PERSISTENT, "persistent"). +-define(HEADER_PREFETCH_COUNT, "prefetch-count"). +-define(HEADER_PRIORITY, "priority"). +-define(HEADER_RECEIPT, "receipt"). +-define(HEADER_REPLY_TO, "reply-to"). +-define(HEADER_SERVER, "server"). +-define(HEADER_SESSION, "session"). +-define(HEADER_SUBSCRIPTION, "subscription"). +-define(HEADER_TIMESTAMP, "timestamp"). +-define(HEADER_TRANSACTION, "transaction"). +-define(HEADER_TYPE, "type"). +-define(HEADER_USER_ID, "user-id"). +-define(HEADER_VERSION, "version"). + +-define(MESSAGE_ID_SEPARATOR, "@@"). + +-define(HEADERS_NOT_ON_SEND, [?HEADER_MESSAGE_ID]). + +-define(TEMP_QUEUE_ID_PREFIX, "/temp-queue/"). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/package.mk b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/package.mk new file mode 100644 index 0000000..bddbfde --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/package.mk @@ -0,0 +1,48 @@ +RELEASABLE:=true +DEPS:=rabbitmq-server rabbitmq-erlang-client +STANDALONE_TEST_COMMANDS:=eunit:test([rabbit_stomp_test_util,rabbit_stomp_test_frame],[verbose]) +WITH_BROKER_TEST_SCRIPTS:=$(PACKAGE_DIR)/test/src/test.py $(PACKAGE_DIR)/test/src/test_connect_options.py +WITH_BROKER_TEST_COMMANDS:=rabbit_stomp_test:all_tests() rabbit_stomp_amqqueue_test:all_tests() + +RABBITMQ_TEST_PATH=$(PACKAGE_DIR)/../rabbitmq-test +ABS_PACKAGE_DIR:=$(abspath $(PACKAGE_DIR)) + +CERTS_DIR:=$(ABS_PACKAGE_DIR)/test/certs +CAN_RUN_SSL:=$(shell if [ -d $(RABBITMQ_TEST_PATH) ]; then echo "true"; else echo "false"; fi) + +TEST_CONFIG_PATH=$(TEST_EBIN_DIR)/test.config +WITH_BROKER_TEST_CONFIG:=$(TEST_EBIN_DIR)/test + +.PHONY: $(TEST_CONFIG_PATH) + +ifeq ($(CAN_RUN_SSL),true) + +WITH_BROKER_TEST_SCRIPTS += $(PACKAGE_DIR)/test/src/test_ssl.py + +$(TEST_CONFIG_PATH): $(CERTS_DIR) $(ABS_PACKAGE_DIR)/test/src/ssl.config + sed -e "s|%%CERTS_DIR%%|$(CERTS_DIR)|g" < $(ABS_PACKAGE_DIR)/test/src/ssl.config > $@ + @echo "\nRunning SSL tests\n" + +$(CERTS_DIR): + mkdir -p $(CERTS_DIR) + make -C $(RABBITMQ_TEST_PATH)/certs all PASSWORD=test DIR=$(CERTS_DIR) + +else +$(TEST_CONFIG_PATH): $(ABS_PACKAGE_DIR)/test/src/non_ssl.config + cp $(ABS_PACKAGE_DIR)/test/src/non_ssl.config $@ + @echo "\nNOT running SSL tests - looked in $(RABBITMQ_TEST_PATH) \n" + +endif + +define package_rules + +$(PACKAGE_DIR)+pre-test:: $(TEST_CONFIG_PATH) + make -C $(PACKAGE_DIR)/deps/stomppy + +$(PACKAGE_DIR)+clean:: + rm -rf $(CERTS_DIR) + +$(PACKAGE_DIR)+clean-with-deps:: + make -C $(PACKAGE_DIR)/deps/stomppy distclean + +endef diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp.erl new file mode 100644 index 0000000..bb8f7f9 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp.erl @@ -0,0 +1,87 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_stomp). + +-include("rabbit_stomp.hrl"). + +-behaviour(application). +-export([start/2, stop/1]). + +-define(DEFAULT_CONFIGURATION, + #stomp_configuration{ + default_login = undefined, + default_passcode = undefined, + implicit_connect = false, + ssl_cert_login = false}). + +start(normal, []) -> + Config = parse_configuration(), + Listeners = parse_listener_configuration(), + rabbit_stomp_sup:start_link(Listeners, Config). + +stop(_State) -> + ok. + +parse_listener_configuration() -> + {ok, Listeners} = application:get_env(tcp_listeners), + {ok, SslListeners} = application:get_env(ssl_listeners), + {Listeners, SslListeners}. + +parse_configuration() -> + {ok, UserConfig} = application:get_env(default_user), + Conf0 = parse_default_user(UserConfig, ?DEFAULT_CONFIGURATION), + {ok, SSLLogin} = application:get_env(ssl_cert_login), + {ok, ImplicitConnect} = application:get_env(implicit_connect), + Conf = Conf0#stomp_configuration{ssl_cert_login = SSLLogin, + implicit_connect = ImplicitConnect}, + report_configuration(Conf), + Conf. + +parse_default_user([], Configuration) -> + Configuration; +parse_default_user([{login, Login} | Rest], Configuration) -> + parse_default_user(Rest, Configuration#stomp_configuration{ + default_login = Login}); +parse_default_user([{passcode, Passcode} | Rest], Configuration) -> + parse_default_user(Rest, Configuration#stomp_configuration{ + default_passcode = Passcode}); +parse_default_user([Unknown | Rest], Configuration) -> + rabbit_log:warning("rabbit_stomp: ignoring invalid default_user " + "configuration option: ~p~n", [Unknown]), + parse_default_user(Rest, Configuration). + +report_configuration(#stomp_configuration{ + default_login = Login, + implicit_connect = ImplicitConnect, + ssl_cert_login = SSLCertLogin}) -> + case Login of + undefined -> ok; + _ -> rabbit_log:info("rabbit_stomp: default user '~s' " + "enabled~n", [Login]) + end, + + case ImplicitConnect of + true -> rabbit_log:info("rabbit_stomp: implicit connect enabled~n"); + false -> ok + end, + + case SSLCertLogin of + true -> rabbit_log:info("rabbit_stomp: ssl_cert_login enabled~n"); + false -> ok + end, + + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_client_sup.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_client_sup.erl new file mode 100644 index 0000000..d0f41b7 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_client_sup.erl @@ -0,0 +1,55 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_stomp_client_sup). +-behaviour(supervisor2). + +-define(MAX_WAIT, 16#ffffffff). +-export([start_link/1, init/1]). + +start_link(Configuration) -> + {ok, SupPid} = supervisor2:start_link(?MODULE, []), + {ok, HelperPid} = + supervisor2:start_child(SupPid, + {rabbit_stomp_heartbeat_sup, + {rabbit_connection_helper_sup, start_link, []}, + intrinsic, infinity, supervisor, + [rabbit_connection_helper_sup]}), + %% The processor is intrinsic. When it exits, the supervisor goes too. + {ok, ProcessorPid} = + supervisor2:start_child(SupPid, + {rabbit_stomp_processor, + {rabbit_stomp_processor, start_link, + [Configuration]}, + intrinsic, ?MAX_WAIT, worker, + [rabbit_stomp_processor]}), + %% We want the reader to be transient since when it exits normally + %% the processor may have some work still to do (and the reader + %% tells the processor to exit). However, if the reader terminates + %% abnormally then we want to take everything down. + {ok, ReaderPid} = supervisor2:start_child( + SupPid, + {rabbit_stomp_reader, + {rabbit_stomp_reader, + start_link, [HelperPid, ProcessorPid, Configuration]}, + transient, ?MAX_WAIT, worker, + [rabbit_stomp_reader]}), + + {ok, SupPid, ReaderPid}. + +init([]) -> + {ok, {{one_for_all, 0, 1}, []}}. + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_frame.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_frame.erl new file mode 100644 index 0000000..335a3a8 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_frame.erl @@ -0,0 +1,250 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +%% stomp_frame implements the STOMP framing protocol "version 1.0", as +%% per http://stomp.codehaus.org/Protocol + +-module(rabbit_stomp_frame). + +-include("rabbit_stomp_frame.hrl"). +-include("rabbit_stomp_headers.hrl"). + +-export([parse/2, initial_state/0]). +-export([header/2, header/3, + boolean_header/2, boolean_header/3, + integer_header/2, integer_header/3, + binary_header/2, binary_header/3]). +-export([serialize/1]). + +initial_state() -> none. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% STOMP 1.1 frames basic syntax +%% Rabbit modifications: +%% o CR LF is equivalent to LF in all element terminators (eol). +%% o Escape codes for header names and values include \r for CR +%% and CR is not allowed. +%% o Header names and values are not limited to UTF-8 strings. +%% o Header values may contain unescaped colons +%% +%% frame_seq ::= *(noise frame) +%% noise ::= *(NUL | eol) +%% eol ::= LF | CR LF +%% frame ::= cmd hdrs body NUL +%% body ::= *OCTET +%% cmd ::= 1*NOTEOL eol +%% hdrs ::= *hdr eol +%% hdr ::= hdrname COLON hdrvalue eol +%% hdrname ::= 1*esc_char +%% hdrvalue ::= *esc_char +%% esc_char ::= HDROCT | BACKSLASH ESCCODE +%% +%% Terms in CAPS all represent sets (alternatives) of single octets. +%% They are defined here using a small extension of BNF, minus (-): +%% +%% term1 - term2 denotes any of the possibilities in term1 +%% excluding those in term2. +%% In this grammar minus is only used for sets of single octets. +%% +%% OCTET ::= '00'x..'FF'x % any octet +%% NUL ::= '00'x % the zero octet +%% LF ::= '\n' % '0a'x newline or linefeed +%% CR ::= '\r' % '0d'x carriage return +%% NOTEOL ::= OCTET - (CR | LF) % any octet except CR or LF +%% BACKSLASH ::= '\\' % '5c'x +%% ESCCODE ::= 'c' | 'n' | 'r' | BACKSLASH +%% COLON ::= ':' +%% HDROCT ::= NOTEOL - (COLON | BACKSLASH) +%% % octets allowed in a header +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% explicit frame characters +-define(NUL, 0). +-define(CR, $\r). +-define(LF, $\n). +-define(BSL, $\\). +-define(COLON, $:). + +%% header escape codes +-define(LF_ESC, $n). +-define(BSL_ESC, $\\). +-define(COLON_ESC, $c). +-define(CR_ESC, $r). + +%% parser state +-record(state, {acc, cmd, hdrs, hdrname}). + +parse(Content, {resume, Continuation}) -> Continuation(Content); +parse(Content, none ) -> parser(Content, noframe, #state{}). + +more(Continuation) -> {more, {resume, Continuation}}. + +%% Single-function parser: Term :: noframe | command | headers | hdrname | hdrvalue +%% general more and line-end detection +parser(<<>>, Term , State) -> more(fun(Rest) -> parser(Rest, Term, State) end); +parser(<>, Term , State) -> more(fun(Rest) -> parser(<>, Term, State) end); +parser(<>, Term , State) -> parser(<>, Term, State); +parser(<>, Term , _State) -> {error, {unexpected_chars(Term), [?CR, Ch]}}; +%% escape processing (only in hdrname and hdrvalue terms) +parser(<>, Term , State) -> more(fun(Rest) -> parser(<>, Term, State) end); +parser(<>, Term , State) + when Term == hdrname; + Term == hdrvalue -> unescape(Ch, fun(Ech) -> parser(Rest, Term, accum(Ech, State)) end); +%% inter-frame noise +parser(<>, noframe , State) -> parser(Rest, noframe, State); +parser(<>, noframe , State) -> parser(Rest, noframe, State); +%% detect transitions +parser( Rest, noframe , State) -> goto(noframe, command, Rest, State); +parser(<>, command , State) -> goto(command, headers, Rest, State); +parser(<>, headers , State) -> goto(headers, body, Rest, State); +parser( Rest, headers , State) -> goto(headers, hdrname, Rest, State); +parser(<>, hdrname , State) -> goto(hdrname, hdrvalue, Rest, State); +parser(<>, hdrname , State) -> goto(hdrname, headers, Rest, State); +parser(<>, hdrvalue, State) -> goto(hdrvalue, headers, Rest, State); +%% accumulate +parser(<>, Term , State) -> parser(Rest, Term, accum(Ch, State)). + +%% state transitions +goto(noframe, command, Rest, State ) -> parser(Rest, command, State#state{acc = []}); +goto(command, headers, Rest, State = #state{acc = Acc} ) -> parser(Rest, headers, State#state{cmd = lists:reverse(Acc), hdrs = []}); +goto(headers, body, Rest, #state{cmd = Cmd, hdrs = Hdrs}) -> parse_body(Rest, #stomp_frame{command = Cmd, headers = Hdrs}); +goto(headers, hdrname, Rest, State ) -> parser(Rest, hdrname, State#state{acc = []}); +goto(hdrname, hdrvalue, Rest, State = #state{acc = Acc} ) -> parser(Rest, hdrvalue, State#state{acc = [], hdrname = lists:reverse(Acc)}); +goto(hdrname, headers, _Rest, #state{acc = Acc} ) -> {error, {header_no_value, lists:reverse(Acc)}}; % badly formed header -- fatal error +goto(hdrvalue, headers, Rest, State = #state{acc = Acc, hdrs = Headers, hdrname = HdrName}) -> + parser(Rest, headers, State#state{hdrs = insert_header(Headers, HdrName, lists:reverse(Acc))}). + +%% error atom +unexpected_chars(noframe) -> unexpected_chars_between_frames; +unexpected_chars(command) -> unexpected_chars_in_command; +unexpected_chars(hdrname) -> unexpected_chars_in_header; +unexpected_chars(hdrvalue) -> unexpected_chars_in_header; +unexpected_chars(_Term) -> unexpected_chars. + +%% general accumulation +accum(Ch, State = #state{acc = Acc}) -> State#state{acc = [Ch | Acc]}. + +%% resolve escapes (with error processing) +unescape(?LF_ESC, Fun) -> Fun(?LF); +unescape(?BSL_ESC, Fun) -> Fun(?BSL); +unescape(?COLON_ESC, Fun) -> Fun(?COLON); +unescape(?CR_ESC, Fun) -> Fun(?CR); +unescape(Ch, _Fun) -> {error, {bad_escape, [?BSL, Ch]}}. + +%% insert header unless aleady seen +insert_header(Headers, Name, Value) -> + case lists:keymember(Name, 1, Headers) of + true -> Headers; % first header only + false -> [{Name, Value} | Headers] + end. + +parse_body(Content, Frame) -> + parse_body(Content, Frame, [], + integer_header(Frame, ?HEADER_CONTENT_LENGTH, unknown)). + +parse_body(Content, Frame, Chunks, unknown) -> + parse_body2(Content, Frame, Chunks, case firstnull(Content) of + -1 -> {more, unknown}; + Pos -> {done, Pos} + end); +parse_body(Content, Frame, Chunks, Remaining) -> + Size = byte_size(Content), + parse_body2(Content, Frame, Chunks, case Remaining >= Size of + true -> {more, Remaining - Size}; + false -> {done, Remaining} + end). + +parse_body2(Content, Frame, Chunks, {more, Left}) -> + Chunks1 = finalize_chunk(Content, Chunks), + more(fun(Rest) -> parse_body(Rest, Frame, Chunks1, Left) end); +parse_body2(Content, Frame, Chunks, {done, Pos}) -> + <> = Content, + Body = lists:reverse(finalize_chunk(Chunk, Chunks)), + {ok, Frame#stomp_frame{body_iolist = Body}, Rest}. + +finalize_chunk(<<>>, Chunks) -> Chunks; +finalize_chunk(Chunk, Chunks) -> [Chunk | Chunks]. + +default_value({ok, Value}, _DefaultValue) -> Value; +default_value(not_found, DefaultValue) -> DefaultValue. + +header(#stomp_frame{headers = Headers}, Key) -> + case lists:keysearch(Key, 1, Headers) of + {value, {_, Str}} -> {ok, Str}; + _ -> not_found + end. + +header(F, K, D) -> default_value(header(F, K), D). + +boolean_header(#stomp_frame{headers = Headers}, Key) -> + case lists:keysearch(Key, 1, Headers) of + {value, {_, "true"}} -> {ok, true}; + {value, {_, "false"}} -> {ok, false}; + _ -> not_found + end. + +boolean_header(F, K, D) -> default_value(boolean_header(F, K), D). + +internal_integer_header(Headers, Key) -> + case lists:keysearch(Key, 1, Headers) of + {value, {_, Str}} -> {ok, list_to_integer(string:strip(Str))}; + _ -> not_found + end. + +integer_header(#stomp_frame{headers = Headers}, Key) -> + internal_integer_header(Headers, Key). + +integer_header(F, K, D) -> default_value(integer_header(F, K), D). + +binary_header(F, K) -> + case header(F, K) of + {ok, Str} -> {ok, list_to_binary(Str)}; + not_found -> not_found + end. + +binary_header(F, K, D) -> default_value(binary_header(F, K), D). + +serialize(#stomp_frame{command = Command, + headers = Headers, + body_iolist = BodyFragments}) -> + Len = iolist_size(BodyFragments), + [Command, ?LF, + lists:map(fun serialize_header/1, + lists:keydelete(?HEADER_CONTENT_LENGTH, 1, Headers)), + if + Len > 0 -> [?HEADER_CONTENT_LENGTH ++ ":", integer_to_list(Len), ?LF]; + true -> [] + end, + ?LF, BodyFragments, 0]. + +serialize_header({K, V}) when is_integer(V) -> hdr(escape(K), integer_to_list(V)); +serialize_header({K, V}) when is_list(V) -> hdr(escape(K), escape(V)). + +hdr(K, V) -> [K, ?COLON, V, ?LF]. + +escape(Str) -> [escape1(Ch) || Ch <- Str]. + +escape1(?COLON) -> [?BSL, ?COLON_ESC]; +escape1(?BSL) -> [?BSL, ?BSL_ESC]; +escape1(?LF) -> [?BSL, ?LF_ESC]; +escape1(?CR) -> [?BSL, ?CR_ESC]; +escape1(Ch) -> Ch. + +firstnull(Content) -> firstnull(Content, 0). + +firstnull(<<>>, _N) -> -1; +firstnull(<<0, _Rest/binary>>, N) -> N; +firstnull(<<_Ch, Rest/binary>>, N) -> firstnull(Rest, N+1). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_processor.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_processor.erl new file mode 100644 index 0000000..7d8ce27 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_processor.erl @@ -0,0 +1,1051 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_stomp_processor). +-behaviour(gen_server2). + +-export([start_link/1, init_arg/2, process_frame/2, flush_and_die/1]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + code_change/3, terminate/2]). + +-include_lib("amqp_client/include/amqp_client.hrl"). +-include_lib("amqp_client/include/rabbit_routing_prefixes.hrl"). +-include("rabbit_stomp_frame.hrl"). +-include("rabbit_stomp.hrl"). +-include("rabbit_stomp_headers.hrl"). + +-record(state, {session_id, channel, connection, subscriptions, + version, start_heartbeat_fun, pending_receipts, + config, route_state, reply_queues, frame_transformer, + adapter_info, send_fun, ssl_login_name, peer_addr}). + +-record(subscription, {dest_hdr, ack_mode, multi_ack, description}). + +-define(FLUSH_TIMEOUT, 60000). + +%%---------------------------------------------------------------------------- +%% Public API +%%---------------------------------------------------------------------------- +start_link(Args) -> + gen_server2:start_link(?MODULE, Args, []). + +init_arg(ProcessorPid, InitArgs) -> + gen_server2:cast(ProcessorPid, {init, InitArgs}). + +process_frame(Pid, Frame = #stomp_frame{command = "SEND"}) -> + credit_flow:send(Pid), + gen_server2:cast(Pid, {"SEND", Frame, self()}); +process_frame(Pid, Frame = #stomp_frame{command = Command}) -> + gen_server2:cast(Pid, {Command, Frame, noflow}). + +flush_and_die(Pid) -> + gen_server2:cast(Pid, flush_and_die). + +%%---------------------------------------------------------------------------- +%% Basic gen_server2 callbacks +%%---------------------------------------------------------------------------- + +init(Configuration) -> + process_flag(trap_exit, true), + {ok, + #state { + session_id = none, + channel = none, + connection = none, + subscriptions = dict:new(), + version = none, + pending_receipts = undefined, + config = Configuration, + route_state = rabbit_routing_util:init_state(), + reply_queues = dict:new(), + frame_transformer = undefined}, + hibernate, + {backoff, 1000, 1000, 10000} + }. + +terminate(_Reason, State) -> + close_connection(State). + +handle_cast({init, [SendFun, AdapterInfo, StartHeartbeatFun, SSLLoginName, + PeerAddr]}, + State) -> + {noreply, State #state { send_fun = SendFun, + adapter_info = AdapterInfo, + start_heartbeat_fun = StartHeartbeatFun, + ssl_login_name = SSLLoginName, + peer_addr = PeerAddr}}; + +handle_cast(flush_and_die, State) -> + {stop, normal, close_connection(State)}; + +handle_cast({"STOMP", Frame, noflow}, State) -> + process_connect(no_implicit, Frame, State); + +handle_cast({"CONNECT", Frame, noflow}, State) -> + process_connect(no_implicit, Frame, State); + +handle_cast(Request, State = #state{channel = none, + config = #stomp_configuration{ + implicit_connect = true}}) -> + {noreply, State1 = #state{channel = Ch}, _} = + process_connect(implicit, #stomp_frame{headers = []}, State), + case Ch of + none -> {stop, normal, State1}; + _ -> handle_cast(Request, State1) + end; + +handle_cast(_Request, State = #state{channel = none, + config = #stomp_configuration{ + implicit_connect = false}}) -> + {noreply, + send_error("Illegal command", + "You must log in using CONNECT first", + State), + hibernate}; + +handle_cast({Command, Frame, FlowPid}, + State = #state{frame_transformer = FT}) -> + case FlowPid of + noflow -> ok; + _ -> credit_flow:ack(FlowPid) + end, + Frame1 = FT(Frame), + process_request( + fun(StateN) -> + case validate_frame(Command, Frame1, StateN) of + R = {error, _, _, _} -> R; + _ -> handle_frame(Command, Frame1, StateN) + end + end, + fun(StateM) -> ensure_receipt(Frame1, StateM) end, + State); + +handle_cast(client_timeout, + State = #state{adapter_info = #amqp_adapter_info{name = S}}) -> + rabbit_log:warning("STOMP detected missed client heartbeat(s) " + "on connection ~s, closing it~n", [S]), + {stop, {shutdown, client_heartbeat_timeout}, close_connection(State)}. + +handle_info(#'basic.consume_ok'{}, State) -> + {noreply, State, hibernate}; +handle_info(#'basic.cancel_ok'{}, State) -> + {noreply, State, hibernate}; +handle_info(#'basic.ack'{delivery_tag = Tag, multiple = IsMulti}, State) -> + {noreply, flush_pending_receipts(Tag, IsMulti, State), hibernate}; +handle_info({Delivery = #'basic.deliver'{}, + #amqp_msg{props = Props, payload = Payload}}, State) -> + {noreply, send_delivery(Delivery, Props, Payload, State), hibernate}; +handle_info(#'basic.cancel'{consumer_tag = Ctag}, State) -> + process_request( + fun(StateN) -> server_cancel_consumer(Ctag, StateN) end, State); +handle_info({'EXIT', Conn, + {shutdown, {server_initiated_close, Code, Explanation}}}, + State = #state{connection = Conn}) -> + amqp_death(Code, Explanation, State); +handle_info({'EXIT', Conn, Reason}, State = #state{connection = Conn}) -> + send_error("AMQP connection died", "Reason: ~p", [Reason], State), + {stop, {conn_died, Reason}, State}; +handle_info({inet_reply, _, ok}, State) -> + {noreply, State, hibernate}; +handle_info({bump_credit, Msg}, State) -> + credit_flow:handle_bump_msg(Msg), + {noreply, State, hibernate}; +handle_info({inet_reply, _, Status}, State) -> + {stop, Status, State}. + +process_request(ProcessFun, State) -> + process_request(ProcessFun, fun (StateM) -> StateM end, State). + +process_request(ProcessFun, SuccessFun, State) -> + Res = case catch ProcessFun(State) of + {'EXIT', + {{shutdown, + {server_initiated_close, ReplyCode, Explanation}}, _}} -> + amqp_death(ReplyCode, Explanation, State); + {'EXIT', Reason} -> + priv_error("Processing error", "Processing error", + Reason, State); + Result -> + Result + end, + case Res of + {ok, Frame, NewState} -> + case Frame of + none -> ok; + _ -> send_frame(Frame, NewState) + end, + {noreply, SuccessFun(NewState), hibernate}; + {error, Message, Detail, NewState} -> + {noreply, send_error(Message, Detail, NewState), hibernate}; + {stop, normal, NewState} -> + {stop, normal, SuccessFun(NewState)}; + {stop, R, NewState} -> + {stop, R, NewState} + end. + +process_connect(Implicit, Frame, + State = #state{channel = none, + config = Config, + ssl_login_name = SSLLoginName, + adapter_info = AdapterInfo}) -> + process_request( + fun(StateN) -> + case negotiate_version(Frame) of + {ok, Version} -> + FT = frame_transformer(Version), + Frame1 = FT(Frame), + {Username, Passwd} = creds(Frame1, SSLLoginName, Config), + {ok, DefaultVHost} = application:get_env( + rabbitmq_stomp, default_vhost), + {ProtoName, _} = AdapterInfo#amqp_adapter_info.protocol, + Res = do_login( + Username, Passwd, + login_header(Frame1, ?HEADER_HOST, DefaultVHost), + login_header(Frame1, ?HEADER_HEART_BEAT, "0,0"), + AdapterInfo#amqp_adapter_info{ + protocol = {ProtoName, Version}}, Version, + StateN#state{frame_transformer = FT}), + case {Res, Implicit} of + {{ok, _, StateN1}, implicit} -> ok(StateN1); + _ -> Res + end; + {error, no_common_version} -> + error("Version mismatch", + "Supported versions are ~s~n", + [string:join(?SUPPORTED_VERSIONS, ",")], + StateN) + end + end, + State). + +creds(Frame, SSLLoginName, + #stomp_configuration{default_login = DefLogin, + default_passcode = DefPasscode}) -> + PasswordCreds = {login_header(Frame, ?HEADER_LOGIN, DefLogin), + login_header(Frame, ?HEADER_PASSCODE, DefPasscode)}, + case {rabbit_stomp_frame:header(Frame, ?HEADER_LOGIN), SSLLoginName} of + {not_found, none} -> PasswordCreds; + {not_found, SSLName} -> {SSLName, none}; + _ -> PasswordCreds + end. + +login_header(Frame, Key, Default) when is_binary(Default) -> + login_header(Frame, Key, binary_to_list(Default)); +login_header(Frame, Key, Default) -> + case rabbit_stomp_frame:header(Frame, Key, Default) of + undefined -> undefined; + Hdr -> list_to_binary(Hdr) + end. + +%%---------------------------------------------------------------------------- +%% Frame Transformation +%%---------------------------------------------------------------------------- +frame_transformer("1.0") -> fun rabbit_stomp_util:trim_headers/1; +frame_transformer(_) -> fun(Frame) -> Frame end. + +%%---------------------------------------------------------------------------- +%% Frame Validation +%%---------------------------------------------------------------------------- + +validate_frame(Command, Frame, State) + when Command =:= "SUBSCRIBE" orelse Command =:= "UNSUBSCRIBE" -> + Hdr = fun(Name) -> rabbit_stomp_frame:header(Frame, Name) end, + case {Hdr(?HEADER_PERSISTENT), Hdr(?HEADER_ID)} of + {{ok, "true"}, not_found} -> + error("Missing Header", + "Header 'id' is required for durable subscriptions", State); + _ -> + ok(State) + end; +validate_frame(_Command, _Frame, State) -> + ok(State). + +%%---------------------------------------------------------------------------- +%% Frame handlers +%%---------------------------------------------------------------------------- + +handle_frame("DISCONNECT", _Frame, State) -> + {stop, normal, close_connection(State)}; + +handle_frame("SUBSCRIBE", Frame, State) -> + with_destination("SUBSCRIBE", Frame, State, fun do_subscribe/4); + +handle_frame("UNSUBSCRIBE", Frame, State) -> + ConsumerTag = rabbit_stomp_util:consumer_tag(Frame), + cancel_subscription(ConsumerTag, Frame, State); + +handle_frame("SEND", Frame, State) -> + without_headers(?HEADERS_NOT_ON_SEND, "SEND", Frame, State, + fun (_Command, Frame1, State1) -> + with_destination("SEND", Frame1, State1, fun do_send/4) + end); + +handle_frame("ACK", Frame, State) -> + ack_action("ACK", Frame, State, fun create_ack_method/2); + +handle_frame("NACK", Frame, State) -> + ack_action("NACK", Frame, State, fun create_nack_method/2); + +handle_frame("BEGIN", Frame, State) -> + transactional_action(Frame, "BEGIN", fun begin_transaction/2, State); + +handle_frame("COMMIT", Frame, State) -> + transactional_action(Frame, "COMMIT", fun commit_transaction/2, State); + +handle_frame("ABORT", Frame, State) -> + transactional_action(Frame, "ABORT", fun abort_transaction/2, State); + +handle_frame(Command, _Frame, State) -> + error("Bad command", + "Could not interpret command ~p~n", + [Command], + State). + +%%---------------------------------------------------------------------------- +%% Internal helpers for processing frames callbacks +%%---------------------------------------------------------------------------- + +ack_action(Command, Frame, + State = #state{subscriptions = Subs, + channel = Channel, + version = Version}, MethodFun) -> + AckHeader = rabbit_stomp_util:ack_header_name(Version), + case rabbit_stomp_frame:header(Frame, AckHeader) of + {ok, AckValue} -> + case rabbit_stomp_util:parse_message_id(AckValue) of + {ok, {ConsumerTag, _SessionId, DeliveryTag}} -> + case dict:find(ConsumerTag, Subs) of + {ok, Sub} -> + Method = MethodFun(DeliveryTag, Sub), + case transactional(Frame) of + {yes, Transaction} -> + extend_transaction( + Transaction, {Method}, State); + no -> + amqp_channel:call(Channel, Method), + ok(State) + end; + error -> + error("Subscription not found", + "Message with id ~p has no subscription", + [AckValue], + State) + end; + _ -> + error("Invalid header", + "~p must include a valid ~p header~n", + [Command, AckHeader], + State) + end; + not_found -> + error("Missing header", + "~p must include the ~p header~n", + [Command, AckHeader], + State) + end. + +%%---------------------------------------------------------------------------- +%% Internal helpers for processing frames callbacks +%%---------------------------------------------------------------------------- +server_cancel_consumer(ConsumerTag, State = #state{subscriptions = Subs}) -> + case dict:find(ConsumerTag, Subs) of + error -> + error("Server cancelled unknown subscription", + "Consumer tag ~p is not associated with a subscription.~n", + [ConsumerTag], + State); + {ok, Subscription = #subscription{description = Description}} -> + Id = case rabbit_stomp_util:tag_to_id(ConsumerTag) of + {ok, {_, Id1}} -> Id1; + {error, {_, Id1}} -> "Unknown[" ++ Id1 ++ "]" + end, + send_error_frame("Server cancelled subscription", + [{?HEADER_SUBSCRIPTION, Id}], + "The server has canceled a subscription.~n" + "No more messages will be delivered for ~p.~n", + [Description], + State), + tidy_canceled_subscription(ConsumerTag, Subscription, + #stomp_frame{}, State) + end. + +cancel_subscription({error, invalid_prefix}, _Frame, State) -> + error("Invalid id", + "UNSUBSCRIBE 'id' may not start with ~s~n", + [?TEMP_QUEUE_ID_PREFIX], + State); + +cancel_subscription({error, _}, _Frame, State) -> + error("Missing destination or id", + "UNSUBSCRIBE must include a 'destination' or 'id' header", + State); + +cancel_subscription({ok, ConsumerTag, Description}, Frame, + State = #state{subscriptions = Subs, + channel = Channel}) -> + case dict:find(ConsumerTag, Subs) of + error -> + error("No subscription found", + "UNSUBSCRIBE must refer to an existing subscription.~n" + "Subscription to ~p not found.~n", + [Description], + State); + {ok, Subscription = #subscription{description = Descr}} -> + case amqp_channel:call(Channel, + #'basic.cancel'{ + consumer_tag = ConsumerTag}) of + #'basic.cancel_ok'{consumer_tag = ConsumerTag} -> + tidy_canceled_subscription(ConsumerTag, Subscription, + Frame, State); + _ -> + error("Failed to cancel subscription", + "UNSUBSCRIBE to ~p failed.~n", + [Descr], + State) + end + end. + +tidy_canceled_subscription(ConsumerTag, #subscription{dest_hdr = DestHdr}, + Frame, State = #state{subscriptions = Subs}) -> + Subs1 = dict:erase(ConsumerTag, Subs), + {ok, Dest} = rabbit_routing_util:parse_endpoint(DestHdr), + maybe_delete_durable_sub(Dest, Frame, State#state{subscriptions = Subs1}). + +maybe_delete_durable_sub({topic, Name}, Frame, + State = #state{channel = Channel}) -> + case rabbit_stomp_frame:boolean_header(Frame, + ?HEADER_PERSISTENT, false) of + true -> + {ok, Id} = rabbit_stomp_frame:header(Frame, ?HEADER_ID), + QName = rabbit_stomp_util:durable_subscription_queue(Name, Id), + amqp_channel:call(Channel, + #'queue.delete'{queue = list_to_binary(QName), + nowait = false}), + ok(State); + false -> + ok(State) + end; +maybe_delete_durable_sub(_Destination, _Frame, State) -> + ok(State). + +with_destination(Command, Frame, State, Fun) -> + case rabbit_stomp_frame:header(Frame, ?HEADER_DESTINATION) of + {ok, DestHdr} -> + case rabbit_routing_util:parse_endpoint(DestHdr) of + {ok, Destination} -> + case Fun(Destination, DestHdr, Frame, State) of + {error, invalid_endpoint} -> + error("Invalid destination", + "'~s' is not a valid destination for '~s'~n", + [DestHdr, Command], + State); + {error, {invalid_destination, Msg}} -> + error("Invalid destination", + "~s", + [Msg], + State); + {error, Reason} -> + throw(Reason); + Result -> + Result + end; + {error, {invalid_destination, Type, Content}} -> + error("Invalid destination", + "'~s' is not a valid ~p destination~n", + [Content, Type], + State); + {error, {unknown_destination, Content}} -> + error("Unknown destination", + "'~s' is not a valid destination.~n" + "Valid destination types are: ~s.~n", + [Content, + string:join(rabbit_routing_util:all_dest_prefixes(), + ", ")], State) + end; + not_found -> + error("Missing destination", + "~p must include a 'destination' header~n", + [Command], + State) + end. + +without_headers([Hdr | Hdrs], Command, Frame, State, Fun) -> + case rabbit_stomp_frame:header(Frame, Hdr) of + {ok, _} -> + error("Invalid header", + "'~s' is not allowed on '~s'.~n", + [Hdr, Command], + State); + not_found -> + without_headers(Hdrs, Command, Frame, State, Fun) + end; +without_headers([], Command, Frame, State, Fun) -> + Fun(Command, Frame, State). + +do_login(undefined, _, _, _, _, _, State) -> + error("Bad CONNECT", "Missing login or passcode header(s)", State); +do_login(Username, Passwd, VirtualHost, Heartbeat, AdapterInfo, Version, + State = #state{peer_addr = Addr}) -> + case start_connection( + #amqp_params_direct{username = Username, + password = Passwd, + virtual_host = VirtualHost, + adapter_info = AdapterInfo}, Username, Addr) of + {ok, Connection} -> + link(Connection), + {ok, Channel} = amqp_connection:open_channel(Connection), + SessionId = rabbit_guid:string(rabbit_guid:gen_secure(), "session"), + {{SendTimeout, ReceiveTimeout}, State1} = + ensure_heartbeats(Heartbeat, State), + ok("CONNECTED", + [{?HEADER_SESSION, SessionId}, + {?HEADER_HEART_BEAT, + io_lib:format("~B,~B", [SendTimeout, ReceiveTimeout])}, + {?HEADER_SERVER, server_header()}, + {?HEADER_VERSION, Version}], + "", + State1#state{session_id = SessionId, + channel = Channel, + connection = Connection, + version = Version}); + {error, {auth_failure, _}} -> + rabbit_log:warning("STOMP login failed for user ~p~n", + [binary_to_list(Username)]), + error("Bad CONNECT", "Access refused for user '" ++ + binary_to_list(Username) ++ "'~n", [], State); + {error, access_refused} -> + rabbit_log:warning("STOMP login failed - access_refused " + "(vhost access not allowed)~n"), + error("Bad CONNECT", "Virtual host '" ++ + binary_to_list(VirtualHost) ++ + "' access denied", State); + {error, not_loopback} -> + rabbit_log:warning("STOMP login failed - access_refused " + "(user must access over loopback)~n"), + error("Bad CONNECT", "non-loopback access denied", State) + end. + +start_connection(Params, Username, Addr) -> + case amqp_connection:start(Params) of + {ok, Conn} -> case rabbit_access_control:check_user_loopback( + Username, Addr) of + ok -> {ok, Conn}; + not_allowed -> amqp_connection:close(Conn), + {error, not_loopback} + end; + {error, E} -> {error, E} + end. + +server_header() -> + {ok, Product} = application:get_key(rabbit, id), + {ok, Version} = application:get_key(rabbit, vsn), + rabbit_misc:format("~s/~s", [Product, Version]). + +do_subscribe(Destination, DestHdr, Frame, + State = #state{subscriptions = Subs, + route_state = RouteState, + channel = Channel}) -> + Prefetch = + rabbit_stomp_frame:integer_header(Frame, ?HEADER_PREFETCH_COUNT, + undefined), + {AckMode, IsMulti} = rabbit_stomp_util:ack_mode(Frame), + case ensure_endpoint(source, Destination, Frame, Channel, RouteState) of + {ok, Queue, RouteState1} -> + {ok, ConsumerTag, Description} = + rabbit_stomp_util:consumer_tag(Frame), + case Prefetch of + undefined -> ok; + _ -> amqp_channel:call( + Channel, #'basic.qos'{prefetch_count = Prefetch}) + end, + ExchangeAndKey = rabbit_routing_util:parse_routing(Destination), + try + amqp_channel:subscribe(Channel, + #'basic.consume'{ + queue = Queue, + consumer_tag = ConsumerTag, + no_local = false, + no_ack = (AckMode == auto), + exclusive = false, + arguments = []}, + self()), + ok = rabbit_routing_util:ensure_binding( + Queue, ExchangeAndKey, Channel) + catch exit:Err -> + %% it's safe to delete this queue, it was server-named + %% and declared by us + case Destination of + {exchange, _} -> + ok = maybe_clean_up_queue(Queue, State); + {topic, _} -> + ok = maybe_clean_up_queue(Queue, State); + _ -> + ok + end, + exit(Err) + end, + ok(State#state{subscriptions = + dict:store( + ConsumerTag, + #subscription{dest_hdr = DestHdr, + ack_mode = AckMode, + multi_ack = IsMulti, + description = Description}, + Subs), + route_state = RouteState1}); + {error, _} = Err -> + Err + end. + +maybe_clean_up_queue(Queue, #state{connection = Connection}) -> + {ok, Channel} = amqp_connection:open_channel(Connection), + catch amqp_channel:call(Channel, #'queue.delete'{queue = Queue}), + catch amqp_channel:close(Channel), + ok. + +do_send(Destination, _DestHdr, + Frame = #stomp_frame{body_iolist = BodyFragments}, + State = #state{channel = Channel, route_state = RouteState}) -> + case ensure_endpoint(dest, Destination, Frame, Channel, RouteState) of + + {ok, _Q, RouteState1} -> + + {Frame1, State1} = + ensure_reply_to(Frame, State#state{route_state = RouteState1}), + + Props = rabbit_stomp_util:message_properties(Frame1), + + {Exchange, RoutingKey} = + rabbit_routing_util:parse_routing(Destination), + + Method = #'basic.publish'{ + exchange = list_to_binary(Exchange), + routing_key = list_to_binary(RoutingKey), + mandatory = false, + immediate = false}, + + case transactional(Frame1) of + {yes, Transaction} -> + extend_transaction( + Transaction, + fun(StateN) -> + maybe_record_receipt(Frame1, StateN) + end, + {Method, Props, BodyFragments}, + State1); + no -> + ok(send_method(Method, Props, BodyFragments, + maybe_record_receipt(Frame1, State1))) + end; + + {error, _} = Err -> + + Err + end. + +create_ack_method(DeliveryTag, #subscription{multi_ack = IsMulti}) -> + #'basic.ack'{delivery_tag = DeliveryTag, + multiple = IsMulti}. + +create_nack_method(DeliveryTag, #subscription{multi_ack = IsMulti}) -> + #'basic.nack'{delivery_tag = DeliveryTag, + multiple = IsMulti}. + +negotiate_version(Frame) -> + ClientVers = re:split(rabbit_stomp_frame:header( + Frame, ?HEADER_ACCEPT_VERSION, "1.0"), + ",", [{return, list}]), + rabbit_stomp_util:negotiate_version(ClientVers, ?SUPPORTED_VERSIONS). + + +send_delivery(Delivery = #'basic.deliver'{consumer_tag = ConsumerTag}, + Properties, Body, + State = #state{session_id = SessionId, + subscriptions = Subs, + version = Version}) -> + case dict:find(ConsumerTag, Subs) of + {ok, #subscription{ack_mode = AckMode}} -> + send_frame( + "MESSAGE", + rabbit_stomp_util:headers(SessionId, Delivery, Properties, + AckMode, Version), + Body, + State); + error -> + send_error("Subscription not found", + "There is no current subscription with tag '~s'.", + [ConsumerTag], + State) + end. + +send_method(Method, Channel, State) -> + amqp_channel:call(Channel, Method), + State. + +send_method(Method, State = #state{channel = Channel}) -> + send_method(Method, Channel, State). + +send_method(Method, Properties, BodyFragments, + State = #state{channel = Channel}) -> + send_method(Method, Channel, Properties, BodyFragments, State). + +send_method(Method = #'basic.publish'{}, Channel, Properties, BodyFragments, + State) -> + amqp_channel:cast_flow( + Channel, Method, + #amqp_msg{props = Properties, + payload = list_to_binary(BodyFragments)}), + State. + +close_connection(State = #state{connection = none}) -> + State; +%% Closing the connection will close the channel and subchannels +close_connection(State = #state{connection = Connection}) -> + %% ignore noproc or other exceptions to avoid debris + catch amqp_connection:close(Connection), + State#state{channel = none, connection = none, subscriptions = none}. + +%%---------------------------------------------------------------------------- +%% Reply-To +%%---------------------------------------------------------------------------- +ensure_reply_to(Frame = #stomp_frame{headers = Headers}, State) -> + case rabbit_stomp_frame:header(Frame, ?HEADER_REPLY_TO) of + not_found -> + {Frame, State}; + {ok, ReplyTo} -> + {ok, Destination} = rabbit_routing_util:parse_endpoint(ReplyTo), + case rabbit_routing_util:dest_temp_queue(Destination) of + none -> + {Frame, State}; + TempQueueId -> + {ReplyQueue, State1} = + ensure_reply_queue(TempQueueId, State), + {Frame#stomp_frame{ + headers = lists:keyreplace( + ?HEADER_REPLY_TO, 1, Headers, + {?HEADER_REPLY_TO, ReplyQueue})}, + State1} + end + end. + +ensure_reply_queue(TempQueueId, State = #state{channel = Channel, + reply_queues = RQS, + subscriptions = Subs}) -> + case dict:find(TempQueueId, RQS) of + {ok, RQ} -> + {binary_to_list(RQ), State}; + error -> + #'queue.declare_ok'{queue = Queue} = + amqp_channel:call(Channel, + #'queue.declare'{auto_delete = true, + exclusive = true}), + + ConsumerTag = rabbit_stomp_util:consumer_tag_reply_to(TempQueueId), + #'basic.consume_ok'{} = + amqp_channel:subscribe(Channel, + #'basic.consume'{ + queue = Queue, + consumer_tag = ConsumerTag, + no_ack = true, + nowait = false}, + self()), + + Destination = binary_to_list(Queue), + + %% synthesise a subscription to the reply queue destination + Subs1 = dict:store(ConsumerTag, + #subscription{dest_hdr = Destination, + multi_ack = false}, + Subs), + + {Destination, State#state{ + reply_queues = dict:store(TempQueueId, Queue, RQS), + subscriptions = Subs1}} + end. + +%%---------------------------------------------------------------------------- +%% Receipt Handling +%%---------------------------------------------------------------------------- + +ensure_receipt(Frame = #stomp_frame{command = Command}, State) -> + case rabbit_stomp_frame:header(Frame, ?HEADER_RECEIPT) of + {ok, Id} -> do_receipt(Command, Id, State); + not_found -> State + end. + +do_receipt("SEND", _, State) -> + %% SEND frame receipts are handled when messages are confirmed + State; +do_receipt(_Frame, ReceiptId, State) -> + send_frame("RECEIPT", [{"receipt-id", ReceiptId}], "", State). + +maybe_record_receipt(Frame, State = #state{channel = Channel, + pending_receipts = PR}) -> + case rabbit_stomp_frame:header(Frame, ?HEADER_RECEIPT) of + {ok, Id} -> + PR1 = case PR of + undefined -> + amqp_channel:register_confirm_handler( + Channel, self()), + #'confirm.select_ok'{} = + amqp_channel:call(Channel, #'confirm.select'{}), + gb_trees:empty(); + _ -> + PR + end, + SeqNo = amqp_channel:next_publish_seqno(Channel), + State#state{pending_receipts = gb_trees:insert(SeqNo, Id, PR1)}; + not_found -> + State + end. + +flush_pending_receipts(DeliveryTag, IsMulti, + State = #state{pending_receipts = PR}) -> + {Receipts, PR1} = accumulate_receipts(DeliveryTag, IsMulti, PR), + State1 = lists:foldl(fun(ReceiptId, StateN) -> + do_receipt(none, ReceiptId, StateN) + end, State, Receipts), + State1#state{pending_receipts = PR1}. + +accumulate_receipts(DeliveryTag, false, PR) -> + case gb_trees:lookup(DeliveryTag, PR) of + {value, ReceiptId} -> {[ReceiptId], gb_trees:delete(DeliveryTag, PR)}; + none -> {[], PR} + end; + +accumulate_receipts(DeliveryTag, true, PR) -> + case gb_trees:is_empty(PR) of + true -> {[], PR}; + false -> accumulate_receipts1(DeliveryTag, + gb_trees:take_smallest(PR), []) + end. + +accumulate_receipts1(DeliveryTag, {Key, Value, PR}, Acc) + when Key > DeliveryTag -> + {lists:reverse(Acc), gb_trees:insert(Key, Value, PR)}; +accumulate_receipts1(DeliveryTag, {_Key, Value, PR}, Acc) -> + Acc1 = [Value | Acc], + case gb_trees:is_empty(PR) of + true -> {lists:reverse(Acc1), PR}; + false -> accumulate_receipts1(DeliveryTag, + gb_trees:take_smallest(PR), Acc1) + end. + + +%%---------------------------------------------------------------------------- +%% Transaction Support +%%---------------------------------------------------------------------------- + +transactional(Frame) -> + case rabbit_stomp_frame:header(Frame, ?HEADER_TRANSACTION) of + {ok, Transaction} -> {yes, Transaction}; + not_found -> no + end. + +transactional_action(Frame, Name, Fun, State) -> + case transactional(Frame) of + {yes, Transaction} -> + Fun(Transaction, State); + no -> + error("Missing transaction", + "~p must include a 'transaction' header~n", + [Name], + State) + end. + +with_transaction(Transaction, State, Fun) -> + case get({transaction, Transaction}) of + undefined -> + error("Bad transaction", + "Invalid transaction identifier: ~p~n", + [Transaction], + State); + Actions -> + Fun(Actions, State) + end. + +begin_transaction(Transaction, State) -> + put({transaction, Transaction}, []), + ok(State). + +extend_transaction(Transaction, Callback, Action, State) -> + extend_transaction(Transaction, {callback, Callback, Action}, State). + +extend_transaction(Transaction, Action, State0) -> + with_transaction( + Transaction, State0, + fun (Actions, State) -> + put({transaction, Transaction}, [Action | Actions]), + ok(State) + end). + +commit_transaction(Transaction, State0) -> + with_transaction( + Transaction, State0, + fun (Actions, State) -> + FinalState = lists:foldr(fun perform_transaction_action/2, + State, + Actions), + erase({transaction, Transaction}), + ok(FinalState) + end). + +abort_transaction(Transaction, State0) -> + with_transaction( + Transaction, State0, + fun (_Actions, State) -> + erase({transaction, Transaction}), + ok(State) + end). + +perform_transaction_action({callback, Callback, Action}, State) -> + perform_transaction_action(Action, Callback(State)); +perform_transaction_action({Method}, State) -> + send_method(Method, State); +perform_transaction_action({Method, Props, BodyFragments}, State) -> + send_method(Method, Props, BodyFragments, State). + +%%-------------------------------------------------------------------- +%% Heartbeat Management +%%-------------------------------------------------------------------- + +ensure_heartbeats(Heartbeats, + State = #state{start_heartbeat_fun = SHF, + send_fun = RawSendFun}) -> + [CX, CY] = [list_to_integer(X) || + X <- re:split(Heartbeats, ",", [{return, list}])], + + SendFun = fun() -> RawSendFun(sync, <<$\n>>) end, + Pid = self(), + ReceiveFun = fun() -> gen_server2:cast(Pid, client_timeout) end, + + {SendTimeout, ReceiveTimeout} = + {millis_to_seconds(CY), millis_to_seconds(CX)}, + + SHF(SendTimeout, SendFun, ReceiveTimeout, ReceiveFun), + + {{SendTimeout * 1000 , ReceiveTimeout * 1000}, State}. + +millis_to_seconds(M) when M =< 0 -> 0; +millis_to_seconds(M) when M < 1000 -> 1; +millis_to_seconds(M) -> M div 1000. + +%%---------------------------------------------------------------------------- +%% Queue Setup +%%---------------------------------------------------------------------------- + +ensure_endpoint(_Direction, {queue, []}, _Frame, _Channel, _State) -> + {error, {invalid_destination, "Destination cannot be blank"}}; + +ensure_endpoint(source, EndPoint, Frame, Channel, State) -> + Params = + case rabbit_stomp_frame:boolean_header( + Frame, ?HEADER_PERSISTENT, false) of + true -> + [{subscription_queue_name_gen, + fun () -> + {ok, Id} = rabbit_stomp_frame:header(Frame, ?HEADER_ID), + {_, Name} = rabbit_routing_util:parse_routing(EndPoint), + list_to_binary( + rabbit_stomp_util:durable_subscription_queue(Name, + Id)) + end}, + {durable, true}]; + false -> + [{durable, false}] + end, + rabbit_routing_util:ensure_endpoint(source, Channel, EndPoint, Params, State); + +ensure_endpoint(Direction, Endpoint, _Frame, Channel, State) -> + rabbit_routing_util:ensure_endpoint(Direction, Channel, Endpoint, State). + +%%---------------------------------------------------------------------------- +%% Success/error handling +%%---------------------------------------------------------------------------- + +ok(State) -> + {ok, none, State}. + +ok(Command, Headers, BodyFragments, State) -> + {ok, #stomp_frame{command = Command, + headers = Headers, + body_iolist = BodyFragments}, State}. + +amqp_death(ReplyCode, Explanation, State) -> + ErrorName = amqp_connection:error_atom(ReplyCode), + ErrorDesc = rabbit_misc:format("~s~n", [Explanation]), + log_error(ErrorName, ErrorDesc, none), + {stop, normal, close_connection(send_error(atom_to_list(ErrorName), ErrorDesc, State))}. + +error(Message, Detail, State) -> + priv_error(Message, Detail, none, State). + +error(Message, Format, Args, State) -> + priv_error(Message, Format, Args, none, State). + +priv_error(Message, Detail, ServerPrivateDetail, State) -> + log_error(Message, Detail, ServerPrivateDetail), + {error, Message, Detail, State}. + +priv_error(Message, Format, Args, ServerPrivateDetail, State) -> + priv_error(Message, rabbit_misc:format(Format, Args), ServerPrivateDetail, + State). + +log_error(Message, Detail, ServerPrivateDetail) -> + rabbit_log:error("STOMP error frame sent:~n" + "Message: ~p~n" + "Detail: ~p~n" + "Server private detail: ~p~n", + [Message, Detail, ServerPrivateDetail]). + +%%---------------------------------------------------------------------------- +%% Frame sending utilities +%%---------------------------------------------------------------------------- +send_frame(Command, Headers, BodyFragments, State) -> + send_frame(#stomp_frame{command = Command, + headers = Headers, + body_iolist = BodyFragments}, + State). + +send_frame(Frame, State = #state{send_fun = SendFun}) -> + SendFun(async, rabbit_stomp_frame:serialize(Frame)), + State. + +send_error_frame(Message, ExtraHeaders, Format, Args, State) -> + send_error_frame(Message, ExtraHeaders, rabbit_misc:format(Format, Args), + State). + +send_error_frame(Message, ExtraHeaders, Detail, State) -> + send_frame("ERROR", [{"message", Message}, + {"content-type", "text/plain"}, + {"version", string:join(?SUPPORTED_VERSIONS, ",")}] ++ + ExtraHeaders, + Detail, State). + +send_error(Message, Detail, State) -> + send_error_frame(Message, [], Detail, State). + +send_error(Message, Format, Args, State) -> + send_error(Message, rabbit_misc:format(Format, Args), State). + +%%---------------------------------------------------------------------------- +%% Skeleton gen_server2 callbacks +%%---------------------------------------------------------------------------- +handle_call(_Msg, _From, State) -> + {noreply, State}. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_reader.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_reader.erl new file mode 100644 index 0000000..f4b0175 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_reader.erl @@ -0,0 +1,222 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_stomp_reader). + +-export([start_link/3]). +-export([init/3]). +-export([conserve_resources/3]). + +-include("rabbit_stomp.hrl"). +-include("rabbit_stomp_frame.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +-record(reader_state, {socket, parse_state, processor, state, + conserve_resources, recv_outstanding}). + +%%---------------------------------------------------------------------------- + +start_link(SupHelperPid, ProcessorPid, Configuration) -> + {ok, proc_lib:spawn_link(?MODULE, init, + [SupHelperPid, ProcessorPid, Configuration])}. + +log(Level, Fmt, Args) -> rabbit_log:log(connection, Level, Fmt, Args). + +init(SupHelperPid, ProcessorPid, Configuration) -> + Reply = go(SupHelperPid, ProcessorPid, Configuration), + rabbit_stomp_processor:flush_and_die(ProcessorPid), + Reply. + +go(SupHelperPid, ProcessorPid, Configuration) -> + process_flag(trap_exit, true), + receive + {go, Sock0, SockTransform} -> + case rabbit_net:connection_string(Sock0, inbound) of + {ok, ConnStr} -> + case SockTransform(Sock0) of + {ok, Sock} -> + + ProcInitArgs = processor_args(SupHelperPid, + Configuration, + Sock), + rabbit_stomp_processor:init_arg(ProcessorPid, + ProcInitArgs), + log(info, "accepting STOMP connection ~p (~s)~n", + [self(), ConnStr]), + + ParseState = rabbit_stomp_frame:initial_state(), + try + mainloop( + register_resource_alarm( + #reader_state{socket = Sock, + parse_state = ParseState, + processor = ProcessorPid, + state = running, + conserve_resources = false, + recv_outstanding = false})), + log(info, "closing STOMP connection ~p (~s)~n", + [self(), ConnStr]) + catch _:Ex -> + log_network_error(ConnStr, Ex), + rabbit_net:fast_close(Sock), + exit(normal) + end, + done; + {error, enotconn} -> + rabbit_net:fast_close(Sock0), + exit(normal); + {error, Reason} -> + log_network_error(ConnStr, Reason), + rabbit_net:fast_close(Sock0), + exit(normal) + end + end + end. + +mainloop(State0 = #reader_state{socket = Sock}) -> + State = run_socket(control_throttle(State0)), + receive + {inet_async, Sock, _Ref, {ok, Data}} -> + mainloop(process_received_bytes( + Data, State#reader_state{recv_outstanding = false})); + {inet_async, _Sock, _Ref, {error, closed}} -> + ok; + {inet_async, _Sock, _Ref, {error, Reason}} -> + throw({inet_error, Reason}); + {conserve_resources, Conserve} -> + mainloop(State#reader_state{conserve_resources = Conserve}); + {bump_credit, Msg} -> + credit_flow:handle_bump_msg(Msg), + mainloop(State); + {'EXIT', _From, shutdown} -> + ok; + Other -> + log(warning, "STOMP connection ~p received " + "an unexpected message ~p~n", [Other]), + ok + end. + +process_received_bytes([], State) -> + State; +process_received_bytes(Bytes, + State = #reader_state{ + processor = Processor, + parse_state = ParseState, + state = S}) -> + case rabbit_stomp_frame:parse(Bytes, ParseState) of + {more, ParseState1} -> + State#reader_state{parse_state = ParseState1}; + {ok, Frame, Rest} -> + rabbit_stomp_processor:process_frame(Processor, Frame), + PS = rabbit_stomp_frame:initial_state(), + process_received_bytes(Rest, State#reader_state{ + parse_state = PS, + state = next_state(S, Frame)}) + end. + +conserve_resources(Pid, _Source, Conserve) -> + Pid ! {conserve_resources, Conserve}, + ok. + +register_resource_alarm(State) -> + rabbit_alarm:register(self(), {?MODULE, conserve_resources, []}), State. + +control_throttle(State = #reader_state{state = CS, + conserve_resources = Mem}) -> + case {CS, Mem orelse credit_flow:blocked()} of + {running, true} -> State#reader_state{state = blocking}; + {blocking, false} -> State#reader_state{state = running}; + {blocked, false} -> State#reader_state{state = running}; + {_, _} -> State + end. + +next_state(blocking, #stomp_frame{command = "SEND"}) -> + blocked; +next_state(S, _) -> + S. + +run_socket(State = #reader_state{state = blocked}) -> + State; +run_socket(State = #reader_state{recv_outstanding = true}) -> + State; +run_socket(State = #reader_state{socket = Sock}) -> + rabbit_net:async_recv(Sock, 0, infinity), + State#reader_state{recv_outstanding = true}. + +%%---------------------------------------------------------------------------- + +processor_args(SupPid, Configuration, Sock) -> + SendFun = fun (sync, IoData) -> + %% no messages emitted + catch rabbit_net:send(Sock, IoData); + (async, IoData) -> + %% {inet_reply, _, _} will appear soon + %% We ignore certain errors here, as we will be + %% receiving an asynchronous notification of the + %% same (or a related) fault shortly anyway. See + %% bug 21365. + catch rabbit_net:port_command(Sock, IoData) + end, + + StartHeartbeatFun = + fun (SendTimeout, SendFin, ReceiveTimeout, ReceiveFun) -> + rabbit_heartbeat:start(SupPid, Sock, SendTimeout, + SendFin, ReceiveTimeout, ReceiveFun) + end, + {ok, {PeerAddr, _PeerPort}} = rabbit_net:sockname(Sock), + [SendFun, adapter_info(Sock), StartHeartbeatFun, + ssl_login_name(Sock, Configuration), PeerAddr]. + +adapter_info(Sock) -> + amqp_connection:socket_adapter_info(Sock, {'STOMP', 0}). + +ssl_login_name(_Sock, #stomp_configuration{ssl_cert_login = false}) -> + none; +ssl_login_name(Sock, #stomp_configuration{ssl_cert_login = true}) -> + case rabbit_net:peercert(Sock) of + {ok, C} -> case rabbit_ssl:peer_cert_auth_name(C) of + unsafe -> none; + not_found -> none; + Name -> Name + end; + {error, no_peercert} -> none; + nossl -> none + end. + +%%---------------------------------------------------------------------------- + +log_network_error(ConnStr, {ssl_upgrade_error, + {tls_alert, "handshake failure"}}) -> + log(error, "STOMP detected TLS upgrade error on " + "~p (~s): handshake failure~n", [self(), ConnStr]); + +log_network_error(ConnStr, {ssl_upgrade_error, + {tls_alert, "unknown ca"}}) -> + log(error, "STOMP detected TLS certificate " + "verification error on " + "~p (~s): alert 'unknown CA'~n", [self(), ConnStr]); + +log_network_error(ConnStr, {ssl_upgrade_error, {tls_alert, Alert}}) -> + log(error, "STOMP detected TLS upgrade error on " + "~p (~s): alert ~s~n", [self(), ConnStr, Alert]); + +log_network_error(ConnStr, {ssl_upgrade_error, closed}) -> + log(error, "STOMP detected TLS upgrade error on " + "~p (~s): connection closed~n", [self(), ConnStr]); + +log_network_error(ConnStr, Ex) -> + log(error, "STOMP detected network error on " + "~p (~s):~n~p~n", [self(), ConnStr, Ex]). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_sup.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_sup.erl new file mode 100644 index 0000000..01fa595 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_sup.erl @@ -0,0 +1,80 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_stomp_sup). +-behaviour(supervisor). + +-export([start_link/2, init/1]). + +-export([start_client/2, start_ssl_client/3]). + +start_link(Listeners, Configuration) -> + supervisor:start_link({local, ?MODULE}, ?MODULE, + [Listeners, Configuration]). + +init([{Listeners, SslListeners}, Configuration]) -> + {ok, SocketOpts} = application:get_env(rabbitmq_stomp, tcp_listen_options), + + SslOpts = case SslListeners of + [] -> none; + _ -> rabbit_networking:ensure_ssl() + end, + + {ok, {{one_for_all, 10, 10}, + [{rabbit_stomp_client_sup_sup, + {rabbit_client_sup, start_link, + [{local, rabbit_stomp_client_sup_sup}, + {rabbit_stomp_client_sup, start_link,[]}]}, + transient, infinity, supervisor, [rabbit_client_sup]} | + listener_specs(fun tcp_listener_spec/1, + [SocketOpts, Configuration], Listeners) ++ + listener_specs(fun ssl_listener_spec/1, + [SocketOpts, SslOpts, Configuration], SslListeners)]}}. + +listener_specs(Fun, Args, Listeners) -> + [Fun([Address | Args]) || + Listener <- Listeners, + Address <- rabbit_networking:tcp_listener_addresses(Listener)]. + +tcp_listener_spec([Address, SocketOpts, Configuration]) -> + rabbit_networking:tcp_listener_spec( + rabbit_stomp_listener_sup, Address, SocketOpts, + stomp, "STOMP TCP Listener", + {?MODULE, start_client, [Configuration]}). + +ssl_listener_spec([Address, SocketOpts, SslOpts, Configuration]) -> + rabbit_networking:tcp_listener_spec( + rabbit_stomp_listener_sup, Address, SocketOpts, + 'stomp/ssl', "STOMP SSL Listener", + {?MODULE, start_ssl_client, [Configuration, SslOpts]}). + +start_client(Configuration, Sock, SockTransform) -> + {ok, _Child, Reader} = supervisor:start_child(rabbit_stomp_client_sup_sup, + [Configuration]), + ok = rabbit_net:controlling_process(Sock, Reader), + Reader ! {go, Sock, SockTransform}, + + %% see comment in rabbit_networking:start_client/2 + gen_event:which_handlers(error_logger), + + Reader. + +start_client(Configuration, Sock) -> + start_client(Configuration, Sock, fun (S) -> {ok, S} end). + +start_ssl_client(Configuration, SslOpts, Sock) -> + Transform = rabbit_networking:ssl_transform_fun(SslOpts), + start_client(Configuration, Sock, Transform). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_util.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_util.erl new file mode 100644 index 0000000..d91ba43 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbit_stomp_util.erl @@ -0,0 +1,320 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_stomp_util). + +-export([parse_message_id/1, durable_subscription_queue/2]). +-export([longstr_field/2]). +-export([ack_mode/1, consumer_tag_reply_to/1, consumer_tag/1, message_headers/1, + headers_post_process/1, headers/5, message_properties/1, tag_to_id/1, + msg_header_name/1, ack_header_name/1]). +-export([negotiate_version/2]). +-export([trim_headers/1]). + +-include_lib("amqp_client/include/amqp_client.hrl"). +-include_lib("amqp_client/include/rabbit_routing_prefixes.hrl"). +-include("rabbit_stomp_frame.hrl"). +-include("rabbit_stomp_headers.hrl"). + +-define(INTERNAL_TAG_PREFIX, "T_"). +-define(QUEUE_TAG_PREFIX, "Q_"). + +%%-------------------------------------------------------------------- +%% Frame and Header Parsing +%%-------------------------------------------------------------------- + +consumer_tag_reply_to(QueueId) -> + internal_tag(?TEMP_QUEUE_ID_PREFIX ++ QueueId). + +consumer_tag(Frame) -> + case rabbit_stomp_frame:header(Frame, ?HEADER_ID) of + {ok, Id} -> + case lists:prefix(?TEMP_QUEUE_ID_PREFIX, Id) of + false -> {ok, internal_tag(Id), "id='" ++ Id ++ "'"}; + true -> {error, invalid_prefix} + end; + not_found -> + case rabbit_stomp_frame:header(Frame, ?HEADER_DESTINATION) of + {ok, DestHdr} -> + {ok, queue_tag(DestHdr), + "destination='" ++ DestHdr ++ "'"}; + not_found -> + {error, missing_destination_header} + end + end. + +ack_mode(Frame) -> + case rabbit_stomp_frame:header(Frame, ?HEADER_ACK, "auto") of + "auto" -> {auto, false}; + "client" -> {client, true}; + "client-individual" -> {client, false} + end. + +message_properties(Frame = #stomp_frame{headers = Headers}) -> + BinH = fun(K) -> rabbit_stomp_frame:binary_header(Frame, K, undefined) end, + IntH = fun(K) -> rabbit_stomp_frame:integer_header(Frame, K, undefined) end, + + DeliveryMode = case rabbit_stomp_frame:boolean_header( + Frame, ?HEADER_PERSISTENT, false) of + true -> 2; + false -> undefined + end, + + #'P_basic'{ content_type = BinH(?HEADER_CONTENT_TYPE), + content_encoding = BinH(?HEADER_CONTENT_ENCODING), + headers = [longstr_field(K, V) || + {K, V} <- Headers, user_header(K)], + delivery_mode = DeliveryMode, + priority = IntH(?HEADER_PRIORITY), + correlation_id = BinH(?HEADER_CORRELATION_ID), + reply_to = BinH(?HEADER_REPLY_TO), + expiration = BinH(?HEADER_EXPIRATION), + message_id = BinH(?HEADER_AMQP_MESSAGE_ID), + timestamp = IntH(?HEADER_TIMESTAMP), + type = BinH(?HEADER_TYPE), + user_id = BinH(?HEADER_USER_ID), + app_id = BinH(?HEADER_APP_ID) }. + +message_headers(Props = #'P_basic'{headers = Headers}) -> + adhoc_convert_headers( + Headers, + lists:foldl(fun({Header, Index}, Acc) -> + maybe_header(Header, element(Index, Props), Acc) + end, [], + [{?HEADER_CONTENT_TYPE, #'P_basic'.content_type}, + {?HEADER_CONTENT_ENCODING, #'P_basic'.content_encoding}, + {?HEADER_PERSISTENT, #'P_basic'.delivery_mode}, + {?HEADER_PRIORITY, #'P_basic'.priority}, + {?HEADER_CORRELATION_ID, #'P_basic'.correlation_id}, + {?HEADER_REPLY_TO, #'P_basic'.reply_to}, + {?HEADER_EXPIRATION, #'P_basic'.expiration}, + {?HEADER_AMQP_MESSAGE_ID, #'P_basic'.message_id}, + {?HEADER_TIMESTAMP, #'P_basic'.timestamp}, + {?HEADER_TYPE, #'P_basic'.type}, + {?HEADER_USER_ID, #'P_basic'.user_id}, + {?HEADER_APP_ID, #'P_basic'.app_id}])). + +adhoc_convert_headers(undefined, Existing) -> + Existing; +adhoc_convert_headers(Headers, Existing) -> + lists:foldr(fun ({K, longstr, V}, Acc) -> + [{binary_to_list(K), binary_to_list(V)} | Acc]; + ({K, signedint, V}, Acc) -> + [{binary_to_list(K), integer_to_list(V)} | Acc]; + (_, Acc) -> + Acc + end, Existing, Headers). + +headers_extra(SessionId, AckMode, Version, + #'basic.deliver'{consumer_tag = ConsumerTag, + delivery_tag = DeliveryTag, + exchange = ExchangeBin, + routing_key = RoutingKeyBin}) -> + case tag_to_id(ConsumerTag) of + {ok, {internal, Id}} -> [{?HEADER_SUBSCRIPTION, Id}]; + _ -> [] + end ++ + [{?HEADER_DESTINATION, + format_destination(binary_to_list(ExchangeBin), + binary_to_list(RoutingKeyBin))}, + {?HEADER_MESSAGE_ID, + create_message_id(ConsumerTag, SessionId, DeliveryTag)}] ++ + case AckMode == client andalso Version == "1.2" of + true -> [{?HEADER_ACK, + create_message_id(ConsumerTag, SessionId, DeliveryTag)}]; + false -> [] + end. + +headers_post_process(Headers) -> + Prefixes = rabbit_routing_util:dest_prefixes(), + [case Header of + {?HEADER_REPLY_TO, V} -> + case lists:any(fun (P) -> lists:prefix(P, V) end, Prefixes) of + true -> {?HEADER_REPLY_TO, V}; + false -> {?HEADER_REPLY_TO, ?REPLY_QUEUE_PREFIX ++ V} + end; + {_, _} -> + Header + end || Header <- Headers]. + +headers(SessionId, Delivery, Properties, AckMode, Version) -> + headers_extra(SessionId, AckMode, Version, Delivery) ++ + headers_post_process(message_headers(Properties)). + +tag_to_id(<>) -> + {ok, {internal, binary_to_list(Id)}}; +tag_to_id(<>) -> + {ok, {queue, binary_to_list(Id)}}; +tag_to_id(Other) when is_binary(Other) -> + {error, {unknown, binary_to_list(Other)}}. + +user_header(Hdr) + when Hdr =:= ?HEADER_CONTENT_TYPE orelse + Hdr =:= ?HEADER_CONTENT_ENCODING orelse + Hdr =:= ?HEADER_PERSISTENT orelse + Hdr =:= ?HEADER_PRIORITY orelse + Hdr =:= ?HEADER_CORRELATION_ID orelse + Hdr =:= ?HEADER_REPLY_TO orelse + Hdr =:= ?HEADER_EXPIRATION orelse + Hdr =:= ?HEADER_AMQP_MESSAGE_ID orelse + Hdr =:= ?HEADER_TIMESTAMP orelse + Hdr =:= ?HEADER_TYPE orelse + Hdr =:= ?HEADER_USER_ID orelse + Hdr =:= ?HEADER_APP_ID orelse + Hdr =:= ?HEADER_DESTINATION -> + false; +user_header(_) -> + true. + +parse_message_id(MessageId) -> + case split(MessageId, ?MESSAGE_ID_SEPARATOR) of + [ConsumerTag, SessionId, DeliveryTag] -> + {ok, {list_to_binary(ConsumerTag), + SessionId, + list_to_integer(DeliveryTag)}}; + _ -> + {error, invalid_message_id} + end. + +negotiate_version(ClientVers, ServerVers) -> + Common = lists:filter(fun(Ver) -> + lists:member(Ver, ServerVers) + end, ClientVers), + case Common of + [] -> + {error, no_common_version}; + [H|T] -> + {ok, lists:foldl(fun(Ver, AccN) -> + max_version(Ver, AccN) + end, H, T)} + end. + +max_version(V, V) -> + V; +max_version(V1, V2) -> + Split = fun(X) -> re:split(X, "\\.", [{return, list}]) end, + find_max_version({V1, Split(V1)}, {V2, Split(V2)}). + +find_max_version({V1, [X|T1]}, {V2, [X|T2]}) -> + find_max_version({V1, T1}, {V2, T2}); +find_max_version({V1, [X]}, {V2, [Y]}) -> + case list_to_integer(X) >= list_to_integer(Y) of + true -> V1; + false -> V2 + end; +find_max_version({_V1, []}, {V2, Y}) when length(Y) > 0 -> + V2; +find_max_version({V1, X}, {_V2, []}) when length(X) > 0 -> + V1. + +%% ---- Header processing helpers ---- + +longstr_field(K, V) -> + {list_to_binary(K), longstr, list_to_binary(V)}. + +maybe_header(_Key, undefined, Acc) -> + Acc; +maybe_header(?HEADER_PERSISTENT, 2, Acc) -> + [{?HEADER_PERSISTENT, "true"} | Acc]; +maybe_header(Key, Value, Acc) when is_binary(Value) -> + [{Key, binary_to_list(Value)} | Acc]; +maybe_header(Key, Value, Acc) when is_integer(Value) -> + [{Key, integer_to_list(Value)}| Acc]; +maybe_header(_Key, _Value, Acc) -> + Acc. + +create_message_id(ConsumerTag, SessionId, DeliveryTag) -> + [ConsumerTag, + ?MESSAGE_ID_SEPARATOR, + SessionId, + ?MESSAGE_ID_SEPARATOR, + integer_to_list(DeliveryTag)]. + +trim_headers(Frame = #stomp_frame{headers = Hdrs}) -> + Frame#stomp_frame{headers = [{K, string:strip(V, left)} || {K, V} <- Hdrs]}. + +internal_tag(Base) -> + list_to_binary(?INTERNAL_TAG_PREFIX ++ Base). + +queue_tag(Base) -> + list_to_binary(?QUEUE_TAG_PREFIX ++ Base). + +ack_header_name("1.2") -> ?HEADER_ID; +ack_header_name("1.1") -> ?HEADER_MESSAGE_ID; +ack_header_name("1.0") -> ?HEADER_MESSAGE_ID. + +msg_header_name("1.2") -> ?HEADER_ACK; +msg_header_name("1.1") -> ?HEADER_MESSAGE_ID; +msg_header_name("1.0") -> ?HEADER_MESSAGE_ID. + +%%-------------------------------------------------------------------- +%% Destination Formatting +%%-------------------------------------------------------------------- + +format_destination("", RoutingKey) -> + ?QUEUE_PREFIX ++ "/" ++ escape(RoutingKey); +format_destination("amq.topic", RoutingKey) -> + ?TOPIC_PREFIX ++ "/" ++ escape(RoutingKey); +format_destination(Exchange, "") -> + ?EXCHANGE_PREFIX ++ "/" ++ escape(Exchange); +format_destination(Exchange, RoutingKey) -> + ?EXCHANGE_PREFIX ++ "/" ++ escape(Exchange) ++ "/" ++ escape(RoutingKey). + +%%-------------------------------------------------------------------- +%% Destination Parsing +%%-------------------------------------------------------------------- + +durable_subscription_queue(Destination, SubscriptionId) -> + %% We need a queue name that a) can be derived from the + %% Destination and SubscriptionId, and b) meets the constraints on + %% AMQP queue names. It doesn't need to be secure; we use md5 here + %% simply as a convenient means to bound the length. + rabbit_guid:string( + erlang:md5(term_to_binary({Destination, SubscriptionId})), + "stomp.dsub"). + +%% ---- Helpers ---- + +split([], _Splitter) -> []; +split(Content, []) -> Content; +split(Content, Splitter) -> split(Content, [], [], Splitter). + +split([], RPart, RParts, _Splitter) -> + lists:reverse([lists:reverse(RPart) | RParts]); +split(Content = [Elem | Rest1], RPart, RParts, Splitter) -> + case take_prefix(Splitter, Content) of + {ok, Rest2} -> + split(Rest2, [], [lists:reverse(RPart) | RParts], Splitter); + not_found -> + split(Rest1, [Elem | RPart], RParts, Splitter) + end. + +take_prefix([Char | Prefix], [Char | List]) -> take_prefix(Prefix, List); +take_prefix([], List) -> {ok, List}; +take_prefix(_Prefix, _List) -> not_found. + +escape(Str) -> escape(Str, []). + +escape([$/ | Str], Acc) -> escape(Str, "F2%" ++ Acc); %% $/ == '2F'x +escape([$% | Str], Acc) -> escape(Str, "52%" ++ Acc); %% $% == '25'x +escape([X | Str], Acc) when X < 32 orelse X > 127 -> + escape(Str, revhex(X) ++ "%" ++ Acc); +escape([C | Str], Acc) -> escape(Str, [C | Acc]); +escape([], Acc) -> lists:reverse(Acc). + +revhex(I) -> hexdig(I) ++ hexdig(I bsr 4). + +hexdig(I) -> erlang:integer_to_list(I band 15, 16). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbitmq_stomp.app.src b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbitmq_stomp.app.src new file mode 100644 index 0000000..f0f4718 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/src/rabbitmq_stomp.app.src @@ -0,0 +1,20 @@ +{application, rabbitmq_stomp, + [{description, "Embedded Rabbit Stomp Adapter"}, + {vsn, "%%VSN%%"}, + {modules, []}, + {registered, []}, + {mod, {rabbit_stomp, []}}, + {env, [{default_user, + [{login, "guest"}, + {passcode, "guest"}]}, + {default_vhost, <<"/">>}, + {ssl_cert_login, false}, + {implicit_connect, false}, + {tcp_listeners, [61613]}, + {ssl_listeners, []}, + {tcp_listen_options, [binary, + {packet, raw}, + {reuseaddr, true}, + {backlog, 128}, + {nodelay, true}]}]}, + {applications, [kernel, stdlib, rabbit, amqp_client]}]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/ack.py b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/ack.py new file mode 100644 index 0000000..0b24c42 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/ack.py @@ -0,0 +1,197 @@ +import unittest +import stomp +import base +import time + +class TestAck(base.BaseTest): + + def test_ack_client(self): + d = "/queue/ack-test" + + # subscribe and send message + self.listener.reset(2) ## expecting 2 messages + self.conn.subscribe(destination=d, ack='client', + headers={'prefetch-count': '10'}) + self.conn.send("test1", destination=d) + self.conn.send("test2", destination=d) + self.assertTrue(self.listener.await(4), "initial message not received") + self.assertEquals(2, len(self.listener.messages)) + + # disconnect with no ack + self.conn.disconnect() + + # now reconnect + conn2 = self.create_connection() + try: + listener2 = base.WaitableListener() + listener2.reset(2) + conn2.set_listener('', listener2) + conn2.subscribe(destination=d, ack='client', + headers={'prefetch-count': '10'}) + self.assertTrue(listener2.await(), "message not received again") + self.assertEquals(2, len(listener2.messages)) + + # now ack only the last message - expecting cumulative behaviour + mid = listener2.messages[1]['headers']['message-id'] + conn2.ack({'message-id':mid}) + finally: + conn2.stop() + + # now reconnect again, shouldn't see the message + conn3 = self.create_connection() + try: + listener3 = base.WaitableListener() + conn3.set_listener('', listener3) + conn3.subscribe(destination=d) + self.assertFalse(listener3.await(3), + "unexpected message. ACK not working?") + finally: + conn3.stop() + + def test_ack_client_individual(self): + d = "/queue/ack-test-individual" + + # subscribe and send message + self.listener.reset(2) ## expecting 2 messages + self.conn.subscribe(destination=d, ack='client-individual', + headers={'prefetch-count': '10'}) + self.conn.send("test1", destination=d) + self.conn.send("test2", destination=d) + self.assertTrue(self.listener.await(4), "Both initial messages not received") + self.assertEquals(2, len(self.listener.messages)) + + # disconnect without acks + self.conn.disconnect() + + # now reconnect + conn2 = self.create_connection() + try: + listener2 = base.WaitableListener() + listener2.reset(2) ## expect 2 messages + conn2.set_listener('', listener2) + conn2.subscribe(destination=d, ack='client-individual', + headers={'prefetch-count': '10'}) + self.assertTrue(listener2.await(2.5), "Did not receive 2 messages") + self.assertEquals(2, len(listener2.messages), "Not exactly 2 messages received") + + # now ack only the 'test2' message - expecting individual behaviour + nummsgs = len(listener2.messages) + mid = None + for ind in range(nummsgs): + if listener2.messages[ind]['message']=="test2": + mid = listener2.messages[ind]['headers']['message-id'] + self.assertEquals(1, ind, 'Expecting test2 to be second message') + break + self.assertTrue(mid, "Did not find test2 message id.") + conn2.ack({'message-id':mid}) + finally: + conn2.stop() + + # now reconnect again, shouldn't see the message + conn3 = self.create_connection() + try: + listener3 = base.WaitableListener() + listener3.reset(2) ## expecting a single message, but wait for two + conn3.set_listener('', listener3) + conn3.subscribe(destination=d) + self.assertFalse(listener3.await(2.5), + "Expected to see only one message. ACK not working?") + self.assertEquals(1, len(listener3.messages), "Expecting exactly one message") + self.assertEquals("test1", listener3.messages[0]['message'], "Unexpected message remains") + finally: + conn3.stop() + + def test_ack_client_tx(self): + d = "/queue/ack-test-tx" + + # subscribe and send message + self.listener.reset() + self.conn.subscribe(destination=d, ack='client') + self.conn.send("test", destination=d) + self.assertTrue(self.listener.await(3), "initial message not received") + self.assertEquals(1, len(self.listener.messages)) + + # disconnect with no ack + self.conn.disconnect() + + # now reconnect + conn2 = self.create_connection() + try: + tx = "abc" + listener2 = base.WaitableListener() + conn2.set_listener('', listener2) + conn2.begin(transaction=tx) + conn2.subscribe(destination=d, ack='client') + self.assertTrue(listener2.await(), "message not received again") + self.assertEquals(1, len(listener2.messages)) + + # now ack + mid = listener2.messages[0]['headers']['message-id'] + conn2.ack({'message-id':mid, 'transaction':tx}) + + #now commit + conn2.commit(transaction=tx) + finally: + conn2.stop() + + # now reconnect again, shouldn't see the message + conn3 = self.create_connection() + try: + listener3 = base.WaitableListener() + conn3.set_listener('', listener3) + conn3.subscribe(destination=d) + self.assertFalse(listener3.await(3), + "unexpected message. TX ACK not working?") + finally: + conn3.stop() + + def test_topic_prefetch(self): + d = "/topic/prefetch-test" + + # subscribe and send message + self.listener.reset(6) ## expect 6 messages + self.conn.subscribe(destination=d, ack='client', + headers={'prefetch-count': '5'}) + + for x in range(10): + self.conn.send("test" + str(x), destination=d) + + self.assertFalse(self.listener.await(3), + "Should not have been able to see 6 messages") + self.assertEquals(5, len(self.listener.messages)) + + def test_nack(self): + d = "/queue/nack-test" + + #subscribe and send + self.conn.subscribe(destination=d, ack='client-individual') + self.conn.send("nack-test", destination=d) + + self.assertTrue(self.listener.await(), "Not received message") + message_id = self.listener.messages[0]['headers']['message-id'] + self.listener.reset() + + self.conn.send_frame("NACK", {"message-id" : message_id}) + self.assertTrue(self.listener.await(), "Not received message after NACK") + message_id = self.listener.messages[0]['headers']['message-id'] + self.conn.ack({'message-id' : message_id}) + + def test_nack_multi(self): + d = "/queue/nack-multi" + + self.listener.reset(2) + + #subscribe and send + self.conn.subscribe(destination=d, ack='client', + headers = {'prefetch-count' : '10'}) + self.conn.send("nack-test1", destination=d) + self.conn.send("nack-test2", destination=d) + + self.assertTrue(self.listener.await(), "Not received messages") + message_id = self.listener.messages[1]['headers']['message-id'] + self.listener.reset(2) + + self.conn.send_frame("NACK", {"message-id" : message_id}) + self.assertTrue(self.listener.await(), "Not received message again") + message_id = self.listener.messages[1]['headers']['message-id'] + self.conn.ack({'message-id' : message_id}) diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/base.py b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/base.py new file mode 100644 index 0000000..4db8433 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/base.py @@ -0,0 +1,175 @@ +import unittest +import stomp +import sys +import threading + + +class BaseTest(unittest.TestCase): + + def create_connection(self, version=None, heartbeat=None): + conn = stomp.Connection(user="guest", passcode="guest", + version=version, heartbeat=heartbeat) + conn.start() + conn.connect() + return conn + + def create_subscriber_connection(self, dest): + conn = self.create_connection() + listener = WaitableListener() + conn.set_listener('', listener) + conn.subscribe(destination=dest, receipt="sub.receipt") + listener.await() + self.assertEquals(1, len(listener.receipts)) + listener.reset() + return conn, listener + + def setUp(self): + self.conn = self.create_connection() + self.listener = WaitableListener() + self.conn.set_listener('', self.listener) + + def tearDown(self): + if self.conn.is_connected(): + self.conn.stop() + + def simple_test_send_rec(self, dest, route = None): + self.listener.reset() + + self.conn.subscribe(destination=dest) + self.conn.send("foo", destination=dest) + + self.assertTrue(self.listener.await(), "Timeout, no message received") + + # assert no errors + if len(self.listener.errors) > 0: + self.fail(self.listener.errors[0]['message']) + + # check header content + msg = self.listener.messages[0] + self.assertEquals("foo", msg['message']) + self.assertEquals(dest, msg['headers']['destination']) + + def assertListener(self, errMsg, numMsgs=0, numErrs=0, numRcts=0, timeout=10): + if numMsgs + numErrs + numRcts > 0: + self._assertTrue(self.listener.await(timeout), errMsg + " (#awaiting)") + else: + self._assertFalse(self.listener.await(timeout), errMsg + " (#awaiting)") + self._assertEquals(numMsgs, len(self.listener.messages), errMsg + " (#messages)") + self._assertEquals(numErrs, len(self.listener.errors), errMsg + " (#errors)") + self._assertEquals(numRcts, len(self.listener.receipts), errMsg + " (#receipts)") + + def _assertTrue(self, bool, msg): + if not bool: + self.listener.print_state(msg, True) + self.assertTrue(bool, msg) + + def _assertFalse(self, bool, msg): + if bool: + self.listener.print_state(msg, True) + self.assertFalse(bool, msg) + + def _assertEquals(self, expected, actual, msg): + if expected != actual: + self.listener.print_state(msg, True) + self.assertEquals(expected, actual, msg) + + def assertListenerAfter(self, verb, errMsg="", numMsgs=0, numErrs=0, numRcts=0, timeout=5): + num = numMsgs + numErrs + numRcts + self.listener.reset(num if num>0 else 1) + verb() + self.assertListener(errMsg=errMsg, numMsgs=numMsgs, numErrs=numErrs, numRcts=numRcts, timeout=timeout) + +class WaitableListener(object): + + def __init__(self): + self.debug = False + if self.debug: + print '(listener) init' + self.messages = [] + self.errors = [] + self.receipts = [] + self.latch = Latch(1) + self.msg_no = 0 + + def _next_msg_no(self): + self.msg_no += 1 + return self.msg_no + + def _append(self, array, msg, hdrs): + mno = self._next_msg_no() + array.append({'message' : msg, 'headers' : hdrs, 'msg_no' : mno}) + self.latch.countdown() + + def on_receipt(self, headers, message): + if self.debug: + print '(on_receipt) message:', message, 'headers:', headers + self._append(self.receipts, message, headers) + + def on_error(self, headers, message): + if self.debug: + print '(on_error) message:', message, 'headers:', headers + self._append(self.errors, message, headers) + + def on_message(self, headers, message): + if self.debug: + print '(on_message) message:', message, 'headers:', headers + self._append(self.messages, message, headers) + + def reset(self, count=1): + if self.debug: + self.print_state('(reset listener--old state)') + self.messages = [] + self.errors = [] + self.receipts = [] + self.latch = Latch(count) + self.msg_no = 0 + if self.debug: + self.print_state('(reset listener--new state)') + + def await(self, timeout=10): + return self.latch.await(timeout) + + def print_state(self, hdr="", full=False): + print hdr, + print '#messages:', len(self.messages), + print '#errors:', len(self.errors), + print '#receipts:', len(self.receipts), + print 'Remaining count:', self.latch.get_count() + if full: + if len(self.messages) != 0: print 'Messages:', self.messages + if len(self.errors) != 0: print 'Messages:', self.errors + if len(self.receipts) != 0: print 'Messages:', self.receipts + +class Latch(object): + + def __init__(self, count=1): + self.cond = threading.Condition() + self.cond.acquire() + self.count = count + self.cond.release() + + def countdown(self): + self.cond.acquire() + if self.count > 0: + self.count -= 1 + if self.count == 0: + self.cond.notify_all() + self.cond.release() + + def await(self, timeout=None): + try: + self.cond.acquire() + if self.count == 0: + return True + else: + self.cond.wait(timeout) + return self.count == 0 + finally: + self.cond.release() + + def get_count(self): + try: + self.cond.acquire() + return self.count + finally: + self.cond.release() diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/connect_options.py b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/connect_options.py new file mode 100644 index 0000000..c9e4ad5 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/connect_options.py @@ -0,0 +1,42 @@ +import unittest +import stomp +import base +import test_util + +class TestConnectOptions(base.BaseTest): + + def test_implicit_connect(self): + ''' Implicit connect with receipt on first command ''' + self.conn.disconnect() + test_util.enable_implicit_connect() + listener = base.WaitableListener() + new_conn = stomp.Connection() + new_conn.set_listener('', listener) + + new_conn.start() # not going to issue connect + new_conn.subscribe(destination="/topic/implicit", id='sub_implicit', receipt='implicit') + + try: + self.assertTrue(listener.await(5)) + self.assertEquals(1, len(listener.receipts), + 'Missing receipt. Likely not connected') + self.assertEquals('implicit', listener.receipts[0]['headers']['receipt-id']) + finally: + new_conn.disconnect() + test_util.disable_implicit_connect() + + def test_default_user(self): + ''' Default user connection ''' + self.conn.disconnect() + test_util.enable_default_user() + listener = base.WaitableListener() + new_conn = stomp.Connection() + new_conn.set_listener('', listener) + new_conn.start() + new_conn.connect() + try: + self.assertFalse(listener.await(3)) # no error back + self.assertTrue(new_conn.is_connected()) + finally: + new_conn.disconnect() + test_util.disable_default_user() diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/destinations.py b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/destinations.py new file mode 100644 index 0000000..18dec83 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/destinations.py @@ -0,0 +1,486 @@ +import unittest +import stomp +import base +import time + +class TestExchange(base.BaseTest): + + + def test_amq_direct(self): + ''' Test basic send/receive for /exchange/amq.direct ''' + self.__test_exchange_send_rec("amq.direct", "route") + + def test_amq_topic(self): + ''' Test basic send/receive for /exchange/amq.topic ''' + self.__test_exchange_send_rec("amq.topic", "route") + + def test_amq_fanout(self): + ''' Test basic send/receive for /exchange/amq.fanout ''' + self.__test_exchange_send_rec("amq.fanout", "route") + + def test_amq_fanout_no_route(self): + ''' Test basic send/receive, /exchange/amq.direct, no routing key''' + self.__test_exchange_send_rec("amq.fanout") + + def test_invalid_exchange(self): + ''' Test invalid exchange error ''' + self.listener.reset(1) + self.conn.subscribe(destination="/exchange/does.not.exist") + self.assertListener("Expecting an error", numErrs=1) + err = self.listener.errors[0] + self.assertEquals("not_found", err['headers']['message']) + self.assertEquals( + "NOT_FOUND - no exchange 'does.not.exist' in vhost '/'\n", + err['message']) + time.sleep(1) + self.assertFalse(self.conn.is_connected()) + + def __test_exchange_send_rec(self, exchange, route = None): + if exchange != "amq.topic": + dest = "/exchange/" + exchange + else: + dest = "/topic" + if route != None: + dest += "/" + route + + self.simple_test_send_rec(dest) + +class TestQueue(base.BaseTest): + + def test_send_receive(self): + ''' Test basic send/receive for /queue ''' + d = '/queue/test' + self.simple_test_send_rec(d) + + def test_send_receive_in_other_conn(self): + ''' Test send in one connection, receive in another ''' + d = '/queue/test2' + + # send + self.conn.send("hello", destination=d) + + # now receive + conn2 = self.create_connection() + try: + listener2 = base.WaitableListener() + conn2.set_listener('', listener2) + + conn2.subscribe(destination=d) + self.assertTrue(listener2.await(10), "no receive") + finally: + conn2.stop() + + def test_send_receive_in_other_conn_with_disconnect(self): + ''' Test send, disconnect, receive ''' + d = '/queue/test3' + + # send + self.conn.send("hello thar", destination=d, receipt="foo") + self.listener.await(3) + self.conn.stop() + + # now receive + conn2 = self.create_connection() + try: + listener2 = base.WaitableListener() + conn2.set_listener('', listener2) + + conn2.subscribe(destination=d) + self.assertTrue(listener2.await(10), "no receive") + finally: + conn2.stop() + + + def test_multi_subscribers(self): + ''' Test multiple subscribers against a single /queue destination ''' + d = '/queue/test-multi' + + ## set up two subscribers + conn1, listener1 = self.create_subscriber_connection(d) + conn2, listener2 = self.create_subscriber_connection(d) + + try: + ## now send + self.conn.send("test1", destination=d) + self.conn.send("test2", destination=d) + + ## expect both consumers to get a message? + self.assertTrue(listener1.await(2)) + self.assertEquals(1, len(listener1.messages), + "unexpected message count") + self.assertTrue(listener2.await(2)) + self.assertEquals(1, len(listener2.messages), + "unexpected message count") + finally: + conn1.stop() + conn2.stop() + + def test_send_with_receipt(self): + d = '/queue/test-receipt' + def noop(): pass + self.__test_send_receipt(d, noop, noop) + + def test_send_with_receipt_tx(self): + d = '/queue/test-receipt-tx' + tx = 'receipt.tx' + + def before(): + self.conn.begin(transaction=tx) + + def after(): + self.assertFalse(self.listener.await(1)) + self.conn.commit(transaction=tx) + + self.__test_send_receipt(d, before, after, {'transaction': tx}) + + def test_interleaved_receipt_no_receipt(self): + ''' Test i-leaved receipt/no receipt, no-r bracketed by rs ''' + + d = '/queue/ir' + + self.listener.reset(5) + + self.conn.subscribe(destination=d) + self.conn.send('first', destination=d, receipt='a') + self.conn.send('second', destination=d) + self.conn.send('third', destination=d, receipt='b') + + self.assertListener("Missing messages/receipts", numMsgs=3, numRcts=2, timeout=3) + + self.assertEquals(set(['a','b']), self.__gather_receipts()) + + def test_interleaved_receipt_no_receipt_tx(self): + ''' Test i-leaved receipt/no receipt, no-r bracketed by r+xactions ''' + + d = '/queue/ir' + tx = 'tx.ir' + + # three messages and two receipts + self.listener.reset(5) + + self.conn.subscribe(destination=d) + self.conn.begin(transaction=tx) + + self.conn.send('first', destination=d, receipt='a', transaction=tx) + self.conn.send('second', destination=d, transaction=tx) + self.conn.send('third', destination=d, receipt='b', transaction=tx) + self.conn.commit(transaction=tx) + + self.assertListener("Missing messages/receipts", numMsgs=3, numRcts=2, timeout=40) + + expected = set(['a', 'b']) + missing = expected.difference(self.__gather_receipts()) + + self.assertEquals(set(), missing, "Missing receipts: " + str(missing)) + + def test_interleaved_receipt_no_receipt_inverse(self): + ''' Test i-leaved receipt/no receipt, r bracketed by no-rs ''' + + d = '/queue/ir' + + self.listener.reset(4) + + self.conn.subscribe(destination=d) + self.conn.send('first', destination=d) + self.conn.send('second', destination=d, receipt='a') + self.conn.send('third', destination=d) + + self.assertListener("Missing messages/receipt", numMsgs=3, numRcts=1, timeout=3) + + self.assertEquals(set(['a']), self.__gather_receipts()) + + def __test_send_receipt(self, destination, before, after, headers = {}): + count = 50 + self.listener.reset(count) + + before() + expected_receipts = set() + + for x in range(0, count): + receipt = "test" + str(x) + expected_receipts.add(receipt) + self.conn.send("test receipt", destination=destination, + receipt=receipt, headers=headers) + after() + + self.assertTrue(self.listener.await(5)) + + missing_receipts = expected_receipts.difference( + self.__gather_receipts()) + + self.assertEquals(set(), missing_receipts, + "missing receipts: " + str(missing_receipts)) + + def __gather_receipts(self): + result = set() + for r in self.listener.receipts: + result.add(r['headers']['receipt-id']) + return result + +class TestTopic(base.BaseTest): + + def test_send_receive(self): + ''' Test basic send/receive for /topic ''' + d = '/topic/test' + self.simple_test_send_rec(d) + + def test_send_multiple(self): + ''' Test /topic with multiple consumers ''' + d = '/topic/multiple' + + ## set up two subscribers + conn1, listener1 = self.create_subscriber_connection(d) + conn2, listener2 = self.create_subscriber_connection(d) + + try: + ## listeners are expecting 2 messages + listener1.reset(2) + listener2.reset(2) + + ## now send + self.conn.send("test1", destination=d) + self.conn.send("test2", destination=d) + + ## expect both consumers to get both messages + self.assertTrue(listener1.await(5)) + self.assertEquals(2, len(listener1.messages), + "unexpected message count") + self.assertTrue(listener2.await(5)) + self.assertEquals(2, len(listener2.messages), + "unexpected message count") + finally: + conn1.stop() + conn2.stop() + +class TestReplyQueue(base.BaseTest): + + def test_reply_queue(self): + ''' Test with two separate clients. Client 1 sends + message to a known destination with a defined reply + queue. Client 2 receives on known destination and replies + on the reply destination. Client 1 gets the reply message''' + + known = '/queue/known' + reply = '/temp-queue/0' + + ## Client 1 uses pre-supplied connection and listener + ## Set up client 2 + conn2, listener2 = self.create_subscriber_connection(known) + + try: + self.conn.send("test", destination=known, + headers = {"reply-to": reply}) + + self.assertTrue(listener2.await(5)) + self.assertEquals(1, len(listener2.messages)) + + reply_to = listener2.messages[0]['headers']['reply-to'] + self.assertTrue(reply_to.startswith('/reply-queue/')) + + conn2.send("reply", destination=reply_to) + self.assertTrue(self.listener.await(5)) + self.assertEquals("reply", self.listener.messages[0]['message']) + finally: + conn2.stop() + + def test_reuse_reply_queue(self): + ''' Test re-use of reply-to queue ''' + + known2 = '/queue/known2' + known3 = '/queue/known3' + reply = '/temp-queue/foo' + + def respond(cntn, listna): + self.assertTrue(listna.await(5)) + self.assertEquals(1, len(listna.messages)) + reply_to = listna.messages[0]['headers']['reply-to'] + self.assertTrue(reply_to.startswith('/reply-queue/')) + cntn.send("reply", destination=reply_to) + + ## Client 1 uses pre-supplied connection and listener + ## Set up clients 2 and 3 + conn2, listener2 = self.create_subscriber_connection(known2) + conn3, listener3 = self.create_subscriber_connection(known3) + try: + self.listener.reset(2) + self.conn.send("test2", destination=known2, + headers = {"reply-to": reply}) + self.conn.send("test3", destination=known3, + headers = {"reply-to": reply}) + respond(conn2, listener2) + respond(conn3, listener3) + + self.assertTrue(self.listener.await(5)) + self.assertEquals(2, len(self.listener.messages)) + self.assertEquals("reply", self.listener.messages[0]['message']) + self.assertEquals("reply", self.listener.messages[1]['message']) + finally: + conn2.stop() + conn3.stop() + + def test_perm_reply_queue(self): + '''As test_reply_queue, but with a non-temp reply queue''' + + known = '/queue/known' + reply = '/queue/reply' + + ## Client 1 uses pre-supplied connection and listener + ## Set up client 2 + conn1, listener1 = self.create_subscriber_connection(reply) + conn2, listener2 = self.create_subscriber_connection(known) + + try: + conn1.send("test", destination=known, + headers = {"reply-to": reply}) + + self.assertTrue(listener2.await(5)) + self.assertEquals(1, len(listener2.messages)) + + reply_to = listener2.messages[0]['headers']['reply-to'] + self.assertTrue(reply_to == reply) + + conn2.send("reply", destination=reply_to) + self.assertTrue(listener1.await(5)) + self.assertEquals("reply", listener1.messages[0]['message']) + finally: + conn1.stop() + conn2.stop() + +class TestDurableSubscription(base.BaseTest): + + ID = 'test.subscription' + + def __subscribe(self, dest, conn=None, id=None): + if not conn: + conn = self.conn + if not id: + id = TestDurableSubscription.ID + + conn.subscribe(destination=dest, + headers ={'persistent': 'true', + 'receipt': 1, + 'id': id}) + + def __assert_receipt(self, listener=None, pos=None): + if not listener: + listener = self.listener + + self.assertTrue(listener.await(5)) + self.assertEquals(1, len(self.listener.receipts)) + if pos is not None: + self.assertEquals(pos, self.listener.receipts[0]['msg_no']) + + def __assert_message(self, msg, listener=None, pos=None): + if not listener: + listener = self.listener + + self.assertTrue(listener.await(5)) + self.assertEquals(1, len(listener.messages)) + self.assertEquals(msg, listener.messages[0]['message']) + if pos is not None: + self.assertEquals(pos, self.listener.messages[0]['msg_no']) + + def test_durable_subscription(self): + d = '/topic/durable' + + self.__subscribe(d) + self.__assert_receipt() + + # send first message without unsubscribing + self.listener.reset(1) + self.conn.send("first", destination=d) + self.__assert_message("first") + + # now unsubscribe (disconnect only) + self.conn.unsubscribe(id=TestDurableSubscription.ID) + + # send again + self.listener.reset(2) + self.conn.send("second", destination=d) + + # resubscribe and expect receipt + self.__subscribe(d) + self.__assert_receipt(pos=1) + # and message + self.__assert_message("second", pos=2) + + # now unsubscribe (cancel) + self.conn.unsubscribe(id=TestDurableSubscription.ID, + headers={'persistent': 'true'}) + + # send again + self.listener.reset(1) + self.conn.send("third", destination=d) + + # resubscribe and expect no message + self.__subscribe(d) + self.assertTrue(self.listener.await(3)) + self.assertEquals(0, len(self.listener.messages)) + self.assertEquals(1, len(self.listener.receipts)) + + def test_share_subscription(self): + d = '/topic/durable-shared' + + conn2 = self.create_connection() + conn2.set_listener('', self.listener) + + try: + self.__subscribe(d) + self.__assert_receipt() + self.listener.reset(1) + self.__subscribe(d, conn2) + self.__assert_receipt() + + self.listener.reset(100) + + # send 100 messages + for x in xrange(0, 100): + self.conn.send("msg" + str(x), destination=d) + + self.assertTrue(self.listener.await(5)) + self.assertEquals(100, len(self.listener.messages)) + finally: + conn2.stop() + + def test_separate_ids(self): + d = '/topic/durable-separate' + + conn2 = self.create_connection() + listener2 = base.WaitableListener() + conn2.set_listener('', listener2) + + try: + # ensure durable subscription exists for each ID + self.__subscribe(d) + self.__assert_receipt() + self.__subscribe(d, conn2, "other.id") + self.__assert_receipt(listener2) + self.conn.unsubscribe(id=TestDurableSubscription.ID) + conn2.unsubscribe(id="other.id") + + self.listener.reset(101) + listener2.reset(101) ## 100 messages and 1 receipt + + # send 100 messages + for x in xrange(0, 100): + self.conn.send("msg" + str(x), destination=d) + + self.__subscribe(d) + self.__subscribe(d, conn2, "other.id") + + for l in [self.listener, listener2]: + self.assertTrue(l.await(10)) + self.assertEquals(100, len(l.messages)) + + finally: + conn2.stop() + + def test_durable_subscribe_no_id(self): + d = '/topic/durable-invalid' + + self.conn.subscribe(destination=d, headers={'persistent':'true'}), + self.listener.await(3) + self.assertEquals(1, len(self.listener.errors)) + self.assertEquals("Missing Header", self.listener.errors[0]['headers']['message']) + + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/errors.py b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/errors.py new file mode 100644 index 0000000..9175429 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/errors.py @@ -0,0 +1,67 @@ +import unittest +import stomp +import base +import time + +class TestErrors(base.BaseTest): + + def test_invalid_queue_destination(self): + self.__test_invalid_destination("queue", "/bah/baz") + + def test_invalid_empty_queue_destination(self): + self.__test_invalid_destination("queue", "") + + def test_invalid_topic_destination(self): + self.__test_invalid_destination("topic", "/bah/baz") + + def test_invalid_empty_topic_destination(self): + self.__test_invalid_destination("topic", "") + + def test_invalid_exchange_destination(self): + self.__test_invalid_destination("exchange", "/bah/baz/boo") + + def test_invalid_empty_exchange_destination(self): + self.__test_invalid_destination("exchange", "") + + def test_invalid_default_exchange_destination(self): + self.__test_invalid_destination("exchange", "//foo") + + def test_unknown_destination(self): + self.listener.reset() + self.conn.send(destination="/something/interesting") + + self.assertTrue(self.listener.await()) + self.assertEquals(1, len(self.listener.errors)) + + err = self.listener.errors[0] + self.assertEquals("Unknown destination", err['headers']['message']) + + def test_send_missing_destination(self): + self.__test_missing_destination("SEND") + + def test_send_missing_destination(self): + self.__test_missing_destination("SUBSCRIBE") + + def __test_missing_destination(self, command): + self.listener.reset() + self.conn.send_frame(command) + + self.assertTrue(self.listener.await()) + self.assertEquals(1, len(self.listener.errors)) + + err = self.listener.errors[0] + self.assertEquals("Missing destination", err['headers']['message']) + + def __test_invalid_destination(self, dtype, content): + self.listener.reset() + self.conn.send(destination="/" + dtype + content) + + self.assertTrue(self.listener.await()) + self.assertEquals(1, len(self.listener.errors)) + + err = self.listener.errors[0] + self.assertEquals("Invalid destination", err['headers']['message']) + self.assertEquals("'" + content + "' is not a valid " + + dtype + " destination\n", + err['message']) + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/lifecycle.py b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/lifecycle.py new file mode 100644 index 0000000..902994e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/lifecycle.py @@ -0,0 +1,164 @@ +import unittest +import stomp +import base +import time + +class TestLifecycle(base.BaseTest): + + def test_unsubscribe_exchange_destination(self): + ''' Test UNSUBSCRIBE command with exchange''' + d = "/exchange/amq.fanout" + self.unsub_test(d, self.sub_and_send(d)) + + def test_unsubscribe_exchange_destination_with_receipt(self): + ''' Test receipted UNSUBSCRIBE command with exchange''' + d = "/exchange/amq.fanout" + self.unsub_test(d, self.sub_and_send(d, receipt="unsub.rct"), numRcts=1) + + def test_unsubscribe_queue_destination(self): + ''' Test UNSUBSCRIBE command with queue''' + d = "/queue/unsub01" + self.unsub_test(d, self.sub_and_send(d)) + + def test_unsubscribe_queue_destination_with_receipt(self): + ''' Test receipted UNSUBSCRIBE command with queue''' + d = "/queue/unsub02" + self.unsub_test(d, self.sub_and_send(d, receipt="unsub.rct"), numRcts=1) + + def test_unsubscribe_exchange_id(self): + ''' Test UNSUBSCRIBE command with exchange by id''' + d = "/exchange/amq.fanout" + self.unsub_test(d, self.sub_and_send(d, subid="exchid")) + + def test_unsubscribe_exchange_id_with_receipt(self): + ''' Test receipted UNSUBSCRIBE command with exchange by id''' + d = "/exchange/amq.fanout" + self.unsub_test(d, self.sub_and_send(d, subid="exchid", receipt="unsub.rct"), numRcts=1) + + def test_unsubscribe_queue_id(self): + ''' Test UNSUBSCRIBE command with queue by id''' + d = "/queue/unsub03" + self.unsub_test(d, self.sub_and_send(d, subid="queid")) + + def test_unsubscribe_queue_id_with_receipt(self): + ''' Test receipted UNSUBSCRIBE command with queue by id''' + d = "/queue/unsub04" + self.unsub_test(d, self.sub_and_send(d, subid="queid", receipt="unsub.rct"), numRcts=1) + + def test_connect_version_1_1(self): + ''' Test CONNECT with version 1.1''' + self.conn.disconnect() + new_conn = self.create_connection(version="1.1") + try: + self.assertTrue(new_conn.is_connected()) + finally: + new_conn.disconnect() + self.assertFalse(new_conn.is_connected()) + + def test_heartbeat_disconnects_client(self): + ''' Test heart-beat disconnection''' + self.conn.disconnect() + new_conn = self.create_connection(heartbeat="1500,0") + try: + self.assertTrue(new_conn.is_connected()) + time.sleep(1) + self.assertTrue(new_conn.is_connected()) + time.sleep(3) + self.assertFalse(new_conn.is_connected()) + finally: + if new_conn.is_connected(): + new_conn.disconnect() + + def test_unsupported_version(self): + ''' Test unsupported version on CONNECT command''' + self.bad_connect(stomp.Connection(user="guest", + passcode="guest", + version="100.1"), + "Supported versions are 1.0,1.1,1.2\n") + + def test_bad_username(self): + ''' Test bad username''' + self.bad_connect(stomp.Connection(user="gust", + passcode="guest"), + "Access refused for user 'gust'\n") + + def test_bad_password(self): + ''' Test bad password''' + self.bad_connect(stomp.Connection(user="guest", + passcode="gust"), + "Access refused for user 'guest'\n") + + def test_bad_vhost(self): + ''' Test bad virtual host''' + self.bad_connect(stomp.Connection(user="guest", + passcode="guest", + virtual_host="//"), + "Virtual host '//' access denied") + + def bad_connect(self, new_conn, expected): + self.conn.disconnect() + listener = base.WaitableListener() + new_conn.set_listener('', listener) + try: + new_conn.start() + new_conn.connect() + self.assertTrue(listener.await()) + self.assertEquals(expected, listener.errors[0]['message']) + finally: + if new_conn.is_connected(): + new_conn.disconnect() + + def test_bad_header_on_send(self): + ''' Test disallowed header on SEND ''' + self.listener.reset(1) + self.conn.send_frame("SEND", {"destination":"a", "message-id":"1"}) + self.assertTrue(self.listener.await()) + self.assertEquals(1, len(self.listener.errors)) + errorReceived = self.listener.errors[0] + self.assertEquals("Invalid header", errorReceived['headers']['message']) + self.assertEquals("'message-id' is not allowed on 'SEND'.\n", errorReceived['message']) + + def test_disconnect(self): + ''' Test DISCONNECT command''' + self.conn.disconnect() + self.assertFalse(self.conn.is_connected()) + + def test_disconnect_with_receipt(self): + ''' Test the DISCONNECT command with receipts ''' + time.sleep(3) + self.listener.reset(1) + self.conn.send_frame("DISCONNECT", {"receipt": "test"}) + self.assertTrue(self.listener.await()) + self.assertEquals(1, len(self.listener.receipts)) + receiptReceived = self.listener.receipts[0]['headers']['receipt-id'] + self.assertEquals("test", receiptReceived + , "Wrong receipt received: '" + receiptReceived + "'") + + def unsub_test(self, dest, verbs, numRcts=0): + def afterfun(): + self.conn.send("after-test", destination=dest) + subverb, unsubverb = verbs + self.assertListenerAfter(subverb, numMsgs=1, + errMsg="FAILED to subscribe and send") + self.assertListenerAfter(unsubverb, numRcts=numRcts, + errMsg="Incorrect responses from UNSUBSCRIBE") + self.assertListenerAfter(afterfun, + errMsg="Still receiving messages") + + def sub_and_send(self, dest, subid="", receipt=""): + def subfun(): + if subid=="": + self.conn.subscribe(destination=dest) + else: + self.conn.subscribe(destination=dest, id=subid) + self.conn.send("test", destination=dest) + def unsubfun(): + if subid=="" and receipt=="": + self.conn.unsubscribe(destination=dest) + elif receipt=="": + self.conn.unsubscribe(id=subid) + elif subid=="": + self.conn.unsubscribe(destination=dest, receipt=receipt) + else: + self.conn.unsubscribe(id=subid, receipt=receipt) + return subfun, unsubfun diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/non_ssl.config b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/non_ssl.config new file mode 100644 index 0000000..f0c2ca7 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/non_ssl.config @@ -0,0 +1,6 @@ +[{rabbitmq_stomp, [{default_user, [{login, "guest"}, + {passcode, "guest"} + ]}, + {implicit_connect, true} + ]} +]. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/parsing.py b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/parsing.py new file mode 100644 index 0000000..58d5bcf --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/parsing.py @@ -0,0 +1,351 @@ +''' +Few tests for a rabbitmq-stomp adaptor. They intend to increase code coverage +of the erlang stomp code. +''' +import unittest +import re +import socket +import functools +import time +import sys + +def connect(cnames): + ''' Decorator that creates stomp connections and issues CONNECT ''' + cmd=('CONNECT\n' + 'login:guest\n' + 'passcode:guest\n' + '\n' + '\n\0') + resp = ('CONNECTED\n' + 'session:(.*)\n' + 'heart-beat:0,0\n' + 'server:RabbitMQ/(.*)\n' + 'version:1.0\n' + '\n\x00') + def w(m): + @functools.wraps(m) + def wrapper(self, *args, **kwargs): + for cname in cnames: + sd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sd.settimeout(30000) + sd.connect((self.host, self.port)) + sd.sendall(cmd) + self.match(resp, sd.recv(4096)) + setattr(self, cname, sd) + try: + r = m(self, *args, **kwargs) + finally: + for cname in cnames: + try: + getattr(self, cname).close() + except IOError: + pass + return r + return wrapper + return w + + +class TestParsing(unittest.TestCase): + host='127.0.0.1' + port=61613 + + + def match(self, pattern, data): + ''' helper: try to match 'pattern' regexp with 'data' string. + Fail test if they don't match. + ''' + matched = re.match(pattern, data) + if matched: + return matched.groups() + self.assertTrue(False, 'No match:\n%r\n%r' % (pattern, data) ) + + def recv_atleast(self, bufsize): + recvhead = [] + rl = bufsize + while rl > 0: + buf = self.cd.recv(rl) + bl = len(buf) + if bl==0: break + recvhead.append( buf ) + rl -= bl + return ''.join(recvhead) + + + @connect(['cd']) + def test_newline_after_nul(self): + self.cd.sendall('\n' + 'SUBSCRIBE\n' + 'destination:/exchange/amq.fanout\n' + '\n\x00\n' + 'SEND\n' + 'content-type:text/plain\n' + 'destination:/exchange/amq.fanout\n\n' + 'hello\n\x00\n') + resp = ('MESSAGE\n' + 'destination:/exchange/amq.fanout\n' + 'message-id:Q_/exchange/amq.fanout@@session-(.*)\n' + 'content-type:text/plain\n' + 'content-length:6\n' + '\n' + 'hello\n\0') + self.match(resp, self.cd.recv(4096)) + + @connect(['cd']) + def test_send_without_content_type(self): + self.cd.sendall('\n' + 'SUBSCRIBE\n' + 'destination:/exchange/amq.fanout\n' + '\n\x00\n' + 'SEND\n' + 'destination:/exchange/amq.fanout\n\n' + 'hello\n\x00') + resp = ('MESSAGE\n' + 'destination:/exchange/amq.fanout\n' + 'message-id:Q_/exchange/amq.fanout@@session-(.*)\n' + 'content-length:6\n' + '\n' + 'hello\n\0') + self.match(resp, self.cd.recv(4096)) + + @connect(['cd']) + def test_send_without_content_type_binary(self): + msg = u'\u0ca0\ufffd\x00\n\x01hello\x00'.encode('utf-8') + self.cd.sendall('\n' + 'SUBSCRIBE\n' + 'destination:/exchange/amq.fanout\n' + '\n\x00\n' + 'SEND\n' + 'destination:/exchange/amq.fanout\n' + 'content-length:'+str(len(msg))+'\n\n' + + msg + '\x00') + resp = ('MESSAGE\n' + 'destination:/exchange/amq.fanout\n' + 'message-id:Q_/exchange/amq.fanout@@session-(.*)\n' + 'content-length:'+str(len(msg))+'\n' + '\n' + + msg + '\0') + self.match(resp, self.cd.recv(4096)) + + @connect(['cd']) + def test_newline_after_nul_and_leading_nul(self): + self.cd.sendall('\n' + '\x00SUBSCRIBE\n' + 'destination:/exchange/amq.fanout\n' + '\n\x00\n' + '\x00SEND\n' + 'destination:/exchange/amq.fanout\n' + 'content-type:text/plain\n' + '\nhello\n\x00\n') + resp = ('MESSAGE\n' + 'destination:/exchange/amq.fanout\n' + 'message-id:Q_/exchange/amq.fanout@@session-(.*)\n' + 'content-type:text/plain\n' + 'content-length:6\n' + '\n' + 'hello\n\0') + self.match(resp, self.cd.recv(4096)) + + @connect(['cd']) + def test_bad_command(self): + ''' Trigger an error message. ''' + self.cd.sendall('WRONGCOMMAND\n' + 'destination:a\n' + 'exchange:amq.fanout\n' + '\n\0') + resp = ('ERROR\n' + 'message:Bad command\n' + 'content-type:text/plain\n' + 'version:1.0,1.1,1.2\n' + 'content-length:43\n' + '\n' + 'Could not interpret command "WRONGCOMMAND"\n' + '\0') + self.match(resp, self.cd.recv(4096)) + + @connect(['sd', 'cd1', 'cd2']) + def test_broadcast(self): + ''' Single message should be delivered to two consumers: + amq.topic --routing_key--> first_queue --> first_connection + \--routing_key--> second_queue--> second_connection + ''' + subscribe=( 'SUBSCRIBE\n' + 'id: XsKNhAf\n' + 'destination:/exchange/amq.topic/da9d4779\n' + '\n\0') + for cd in [self.cd1, self.cd2]: + cd.sendall(subscribe) + + time.sleep(0.1) + + self.sd.sendall('SEND\n' + 'content-type:text/plain\n' + 'destination:/exchange/amq.topic/da9d4779\n' + '\n' + 'message' + '\n\0') + + resp=('MESSAGE\n' + 'subscription:(.*)\n' + 'destination:/topic/da9d4779\n' + 'message-id:(.*)\n' + 'content-type:text/plain\n' + 'content-length:8\n' + '\n' + 'message' + '\n\x00') + for cd in [self.cd1, self.cd2]: + self.match(resp, cd.recv(4096)) + + + @connect(['cd']) + def test_huge_message(self): + ''' Test sending/receiving huge (16MB) message. ''' + subscribe=( 'SUBSCRIBE\n' + 'id: xxx\n' + 'destination:/exchange/amq.topic/test_huge_message\n' + '\n\0') + self.cd.sendall(subscribe) + + message = 'x' * 1024*1024*16 + + self.cd.sendall('SEND\n' + 'destination:/exchange/amq.topic/test_huge_message\n' + 'content-type:text/plain\n' + '\n' + '%s' + '\0' % message) + + resp=('MESSAGE\n' + 'subscription:(.*)\n' + 'destination:/topic/test_huge_message\n' + 'message-id:(.*)\n' + 'content-type:text/plain\n' + 'content-length:%i\n' + '\n' + '%s(.*)' + % (len(message), message[:8000]) ) + + recv = [] + s = 0 + while len(recv) < 1 or recv[-1][-1] != '\0': + buf = self.cd.recv(4096*16) + s += len(buf) + recv.append( buf ) + buf = ''.join(recv) + + # matching 100MB regexp is way too expensive. + self.match(resp, buf[:8192]) + self.assertEqual(len(buf) > len(message), True) + + @connect(['cd']) + def test_message_with_embedded_nulls(self): + ''' Test sending/receiving message with embedded nulls. ''' + dest='destination:/exchange/amq.topic/test_embed_nulls_message\n' + resp_dest='destination:/topic/test_embed_nulls_message\n' + subscribe=( 'SUBSCRIBE\n' + 'id:xxx\n' + +dest+ + '\n\0') + self.cd.sendall(subscribe) + + boilerplate = '0123456789'*1024 # large enough boilerplate + message = '01' + oldi = 2 + for i in [5, 90, 256-1, 384-1, 512, 1024, 1024+256+64+32]: + message = message + '\0' + boilerplate[oldi+1:i] + oldi = i + msg_len = len(message) + + self.cd.sendall('SEND\n' + +dest+ + 'content-type:text/plain\n' + 'content-length:%i\n' + '\n' + '%s' + '\0' % (len(message), message) ) + + headresp=('MESSAGE\n' # 8 + 'subscription:(.*)\n' # 14 + subscription + +resp_dest+ # 44 + 'message-id:(.*)\n' # 12 + message-id + 'content-type:text/plain\n' # 24 + 'content-length:%i\n' # 16 + 4==len('1024') + '\n' # 1 + '(.*)$' # prefix of body+null (potentially) + % len(message) ) + headlen = 8 + 24 + 14 + (3) + 44 + 12 + (48) + 16 + (4) + 1 + (1) + + headbuf = self.recv_atleast(headlen) + self.assertFalse(len(headbuf) == 0) + + (sub, msg_id, bodyprefix) = self.match(headresp, headbuf) + bodyresp=( '%s\0' % message ) + bodylen = len(bodyresp); + + bodybuf = ''.join([bodyprefix, + self.recv_atleast(bodylen - len(bodyprefix))]) + + self.assertEqual(len(bodybuf), msg_len+1, + "body received not the same length as message sent") + self.assertEqual(bodybuf, bodyresp, + " body (...'%s')\nincorrectly returned as (...'%s')" + % (bodyresp[-10:], bodybuf[-10:])) + + @connect(['cd']) + def test_message_in_packets(self): + ''' Test sending/receiving message in packets. ''' + base_dest='topic/test_embed_nulls_message\n' + dest='destination:/exchange/amq.' + base_dest + resp_dest='destination:/'+ base_dest + subscribe=( 'SUBSCRIBE\n' + 'id:xxx\n' + +dest+ + '\n\0') + self.cd.sendall(subscribe) + + boilerplate = '0123456789'*1024 # large enough boilerplate + + message = boilerplate[:1024 + 512 + 256 + 32] + msg_len = len(message) + + msg_to_send = ('SEND\n' + +dest+ + 'content-type:text/plain\n' + '\n' + '%s' + '\0' % (message) ) + packet_size = 191 + part_index = 0 + msg_to_send_len = len(msg_to_send) + while part_index < msg_to_send_len: + part = msg_to_send[part_index:part_index+packet_size] + time.sleep(0.1) + self.cd.sendall(part) + part_index += packet_size + + headresp=('MESSAGE\n' # 8 + 'subscription:(.*)\n' # 14 + subscription + +resp_dest+ # 44 + 'message-id:(.*)\n' # 12 + message-id + 'content-type:text/plain\n' # 24 + 'content-length:%i\n' # 16 + 4==len('1024') + '\n' # 1 + '(.*)$' # prefix of body+null (potentially) + % len(message) ) + headlen = 8 + 24 + 14 + (3) + 44 + 12 + (48) + 16 + (4) + 1 + (1) + + headbuf = self.recv_atleast(headlen) + self.assertFalse(len(headbuf) == 0) + + (sub, msg_id, bodyprefix) = self.match(headresp, headbuf) + bodyresp=( '%s\0' % message ) + bodylen = len(bodyresp); + + bodybuf = ''.join([bodyprefix, + self.recv_atleast(bodylen - len(bodyprefix))]) + + self.assertEqual(len(bodybuf), msg_len+1, + "body received not the same length as message sent") + self.assertEqual(bodybuf, bodyresp, + " body ('%s')\nincorrectly returned as ('%s')" + % (bodyresp, bodybuf)) diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_amqqueue_test.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_amqqueue_test.erl new file mode 100644 index 0000000..013f7cb --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_amqqueue_test.erl @@ -0,0 +1,225 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_stomp_amqqueue_test). +-export([all_tests/0]). +-compile(export_all). + +-include_lib("eunit/include/eunit.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). +-include("rabbit_stomp.hrl"). +-include("rabbit_stomp_frame.hrl"). +-include("rabbit_stomp_headers.hrl"). + +-define(QUEUE, <<"TestQueue">>). +-define(DESTINATION, "/amq/queue/TestQueue"). + +all_tests() -> + [[ok = run_test(TestFun, Version) + || TestFun <- [fun test_subscribe_error/3, + fun test_subscribe/3, + fun test_unsubscribe_ack/3, + fun test_subscribe_ack/3, + fun test_send/3, + fun test_delete_queue_subscribe/3, + fun test_temp_destination_queue/3, + fun test_temp_destination_in_send/3, + fun test_blank_destination_in_send/3]] + || Version <- ?SUPPORTED_VERSIONS], + ok. + +run_test(TestFun, Version) -> + {ok, Connection} = amqp_connection:start(#amqp_params_direct{}), + {ok, Channel} = amqp_connection:open_channel(Connection), + {ok, Client} = rabbit_stomp_client:connect(Version), + + Result = (catch TestFun(Channel, Client, Version)), + + rabbit_stomp_client:disconnect(Client), + amqp_channel:close(Channel), + amqp_connection:close(Connection), + Result. + +test_subscribe_error(_Channel, Client, _Version) -> + %% SUBSCRIBE to missing queue + rabbit_stomp_client:send( + Client, "SUBSCRIBE", [{"destination", ?DESTINATION}]), + {ok, _Client1, Hdrs, _} = stomp_receive(Client, "ERROR"), + "not_found" = proplists:get_value("message", Hdrs), + ok. + +test_subscribe(Channel, Client, _Version) -> + #'queue.declare_ok'{} = + amqp_channel:call(Channel, #'queue.declare'{queue = ?QUEUE, + auto_delete = true}), + + %% subscribe and wait for receipt + rabbit_stomp_client:send( + Client, "SUBSCRIBE", [{"destination", ?DESTINATION}, {"receipt", "foo"}]), + {ok, Client1, _, _} = stomp_receive(Client, "RECEIPT"), + + %% send from amqp + Method = #'basic.publish'{exchange = <<"">>, routing_key = ?QUEUE}, + + amqp_channel:call(Channel, Method, #amqp_msg{props = #'P_basic'{}, + payload = <<"hello">>}), + + {ok, _Client2, _, [<<"hello">>]} = stomp_receive(Client1, "MESSAGE"), + ok. + +test_unsubscribe_ack(Channel, Client, Version) -> + #'queue.declare_ok'{} = + amqp_channel:call(Channel, #'queue.declare'{queue = ?QUEUE, + auto_delete = true}), + %% subscribe and wait for receipt + rabbit_stomp_client:send( + Client, "SUBSCRIBE", [{"destination", ?DESTINATION}, + {"receipt", "rcpt1"}, + {"ack", "client"}, + {"id", "subscription-id"}]), + {ok, Client1, _, _} = stomp_receive(Client, "RECEIPT"), + + %% send from amqp + Method = #'basic.publish'{exchange = <<"">>, routing_key = ?QUEUE}, + + amqp_channel:call(Channel, Method, #amqp_msg{props = #'P_basic'{}, + payload = <<"hello">>}), + + {ok, Client2, Hdrs1, [<<"hello">>]} = stomp_receive(Client1, "MESSAGE"), + + rabbit_stomp_client:send( + Client2, "UNSUBSCRIBE", [{"destination", ?DESTINATION}, + {"id", "subscription-id"}]), + + rabbit_stomp_client:send( + Client2, "ACK", [{rabbit_stomp_util:ack_header_name(Version), + proplists:get_value( + rabbit_stomp_util:msg_header_name(Version), Hdrs1)}, + {"receipt", "rcpt2"}]), + + {ok, _Client3, Hdrs2, _Body2} = stomp_receive(Client2, "ERROR"), + ?assertEqual("Subscription not found", + proplists:get_value("message", Hdrs2)), + ok. + +test_subscribe_ack(Channel, Client, Version) -> + #'queue.declare_ok'{} = + amqp_channel:call(Channel, #'queue.declare'{queue = ?QUEUE, + auto_delete = true}), + + %% subscribe and wait for receipt + rabbit_stomp_client:send( + Client, "SUBSCRIBE", [{"destination", ?DESTINATION}, + {"receipt", "foo"}, + {"ack", "client"}]), + {ok, Client1, _, _} = stomp_receive(Client, "RECEIPT"), + + %% send from amqp + Method = #'basic.publish'{exchange = <<"">>, routing_key = ?QUEUE}, + + amqp_channel:call(Channel, Method, #amqp_msg{props = #'P_basic'{}, + payload = <<"hello">>}), + + {ok, _Client2, Headers, [<<"hello">>]} = stomp_receive(Client1, "MESSAGE"), + false = (Version == "1.2") xor proplists:is_defined(?HEADER_ACK, Headers), + + MsgHeader = rabbit_stomp_util:msg_header_name(Version), + AckValue = proplists:get_value(MsgHeader, Headers), + AckHeader = rabbit_stomp_util:ack_header_name(Version), + + rabbit_stomp_client:send(Client, "ACK", [{AckHeader, AckValue}]), + #'basic.get_empty'{} = + amqp_channel:call(Channel, #'basic.get'{queue = ?QUEUE}), + ok. + +test_send(Channel, Client, _Version) -> + #'queue.declare_ok'{} = + amqp_channel:call(Channel, #'queue.declare'{queue = ?QUEUE, + auto_delete = true}), + + %% subscribe and wait for receipt + rabbit_stomp_client:send( + Client, "SUBSCRIBE", [{"destination", ?DESTINATION}, {"receipt", "foo"}]), + {ok, Client1, _, _} = stomp_receive(Client, "RECEIPT"), + + %% send from stomp + rabbit_stomp_client:send( + Client1, "SEND", [{"destination", ?DESTINATION}], ["hello"]), + + {ok, _Client2, _, [<<"hello">>]} = stomp_receive(Client1, "MESSAGE"), + ok. + +test_delete_queue_subscribe(Channel, Client, _Version) -> + #'queue.declare_ok'{} = + amqp_channel:call(Channel, #'queue.declare'{queue = ?QUEUE, + auto_delete = true}), + + %% subscribe and wait for receipt + rabbit_stomp_client:send( + Client, "SUBSCRIBE", [{"destination", ?DESTINATION}, {"receipt", "bah"}]), + {ok, Client1, _, _} = stomp_receive(Client, "RECEIPT"), + + %% delete queue while subscribed + #'queue.delete_ok'{} = + amqp_channel:call(Channel, #'queue.delete'{queue = ?QUEUE}), + + {ok, _Client2, Headers, _} = stomp_receive(Client1, "ERROR"), + + ?DESTINATION = proplists:get_value("subscription", Headers), + + % server closes connection + ok. + +test_temp_destination_queue(Channel, Client, _Version) -> + #'queue.declare_ok'{} = + amqp_channel:call(Channel, #'queue.declare'{queue = ?QUEUE, + auto_delete = true}), + rabbit_stomp_client:send( Client, "SEND", [{"destination", ?DESTINATION}, + {"reply-to", "/temp-queue/foo"}], + ["ping"]), + amqp_channel:call(Channel,#'basic.consume'{queue = ?QUEUE, no_ack = true}), + receive #'basic.consume_ok'{consumer_tag = _Tag} -> ok end, + receive {#'basic.deliver'{delivery_tag = _DTag}, + #'amqp_msg'{payload = <<"ping">>, + props = #'P_basic'{reply_to = ReplyTo}}} -> ok + end, + ok = amqp_channel:call(Channel, + #'basic.publish'{routing_key = ReplyTo}, + #amqp_msg{payload = <<"pong">>}), + {ok, _Client1, _, [<<"pong">>]} = stomp_receive(Client, "MESSAGE"), + ok. + +test_temp_destination_in_send(_Channel, Client, _Version) -> + rabbit_stomp_client:send( Client, "SEND", [{"destination", "/temp-queue/foo"}], + ["poing"]), + {ok, _Client1, Hdrs, _} = stomp_receive(Client, "ERROR"), + "Invalid destination" = proplists:get_value("message", Hdrs), + ok. + +test_blank_destination_in_send(_Channel, Client, _Version) -> + rabbit_stomp_client:send( Client, "SEND", [{"destination", ""}], + ["poing"]), + {ok, _Client1, Hdrs, _} = stomp_receive(Client, "ERROR"), + "Invalid destination" = proplists:get_value("message", Hdrs), + ok. + +stomp_receive(Client, Command) -> + {#stomp_frame{command = Command, + headers = Hdrs, + body_iolist = Body}, Client1} = + rabbit_stomp_client:recv(Client), + {ok, Client1, Hdrs, Body}. + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_client.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_client.erl new file mode 100644 index 0000000..ee67807 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_client.erl @@ -0,0 +1,81 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Console. +%% +%% The Initial Developers of the Original Code are Rabbit Technologies Ltd. +%% +%% Copyright (C) 2011 Rabbit Technologies Ltd. +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +%% The stupidest client imaginable, just for testing. + +-module(rabbit_stomp_client). + +-export([connect/0, connect/1, disconnect/1, send/2, send/3, send/4, recv/1]). + +-include("rabbit_stomp_frame.hrl"). + +-define(TIMEOUT, 1000). % milliseconds + +connect() -> connect0([]). +connect(V) -> connect0([{"accept-version", V}]). + +connect0(Version) -> + {ok, Sock} = gen_tcp:connect(localhost, 61613, [{active, false}, binary]), + Client0 = recv_state(Sock), + send(Client0, "CONNECT", [{"login", "guest"}, + {"passcode", "guest"} | Version]), + {#stomp_frame{command = "CONNECTED"}, Client1} = recv(Client0), + {ok, Client1}. + +disconnect(Client = {Sock, _}) -> + send(Client, "DISCONNECT"), + gen_tcp:close(Sock). + +send(Client, Command) -> + send(Client, Command, []). + +send(Client, Command, Headers) -> + send(Client, Command, Headers, []). + +send({Sock, _}, Command, Headers, Body) -> + Frame = rabbit_stomp_frame:serialize( + #stomp_frame{command = list_to_binary(Command), + headers = Headers, + body_iolist = Body}), + gen_tcp:send(Sock, Frame). + +recv_state(Sock) -> + {Sock, []}. + +recv({_Sock, []} = Client) -> + recv(Client, rabbit_stomp_frame:initial_state(), 0); +recv({Sock, [Frame | Frames]}) -> + {Frame, {Sock, Frames}}. + +recv(Client = {Sock, _}, FrameState, Length) -> + {ok, Payload} = gen_tcp:recv(Sock, Length, ?TIMEOUT), + parse(Payload, Client, FrameState, Length). + +parse(Payload, Client = {Sock, FramesRev}, FrameState, Length) -> + case rabbit_stomp_frame:parse(Payload, FrameState) of + {ok, Frame, <<>>} -> + recv({Sock, lists:reverse([Frame | FramesRev])}); + {ok, Frame, Rest} -> + parse(Rest, {Sock, [Frame | FramesRev]}, + rabbit_stomp_frame:initial_state(), Length); + {more, NewState} -> + recv(Client, NewState, 0) + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_publish_test.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_publish_test.erl new file mode 100644 index 0000000..615c6a7 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_publish_test.erl @@ -0,0 +1,88 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Console. +%% +%% The Initial Developers of the Original Code are Rabbit Technologies Ltd. +%% +%% Copyright (C) 2011 Rabbit Technologies Ltd. +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% +-module(rabbit_stomp_publish_test). + +-export([run/0]). + +-include("rabbit_stomp_frame.hrl"). + +-define(DESTINATION, "/queue/test"). + +-define(MICROS_PER_UPDATE, 5000000). +-define(MICROS_PER_UPDATE_MSG, 100000). +-define(MICROS_PER_SECOND, 1000000). + +%% A very simple publish-and-consume-as-fast-as-you-can test. + +run() -> + [put(K, 0) || K <- [sent, recd, last_sent, last_recd]], + put(last_ts, erlang:now()), + {ok, Pub} = rabbit_stomp_client:connect(), + {ok, Recv} = rabbit_stomp_client:connect(), + Self = self(), + spawn(fun() -> publish(Self, Pub, 0, erlang:now()) end), + rabbit_stomp_client:send( + Recv, "SUBSCRIBE", [{"destination", ?DESTINATION}]), + spawn(fun() -> recv(Self, Recv, 0, erlang:now()) end), + report(). + +report() -> + receive + {sent, C} -> put(sent, C); + {recd, C} -> put(recd, C) + end, + Diff = timer:now_diff(erlang:now(), get(last_ts)), + case Diff > ?MICROS_PER_UPDATE of + true -> S = get(sent) - get(last_sent), + R = get(recd) - get(last_recd), + put(last_sent, get(sent)), + put(last_recd, get(recd)), + put(last_ts, erlang:now()), + io:format("Send ~p msg/s | Recv ~p msg/s~n", + [trunc(S * ?MICROS_PER_SECOND / Diff), + trunc(R * ?MICROS_PER_SECOND / Diff)]); + false -> ok + end, + report(). + +publish(Owner, Client, Count, TS) -> + rabbit_stomp_client:send( + Client, "SEND", [{"destination", ?DESTINATION}], + [integer_to_list(Count)]), + Diff = timer:now_diff(erlang:now(), TS), + case Diff > ?MICROS_PER_UPDATE_MSG of + true -> Owner ! {sent, Count + 1}, + publish(Owner, Client, Count + 1, erlang:now()); + false -> publish(Owner, Client, Count + 1, TS) + end. + +recv(Owner, Client0, Count, TS) -> + {#stomp_frame{body_iolist = Body}, Client1} = + rabbit_stomp_client:recv(Client0), + BodyInt = list_to_integer(binary_to_list(iolist_to_binary(Body))), + Count = BodyInt, + Diff = timer:now_diff(erlang:now(), TS), + case Diff > ?MICROS_PER_UPDATE_MSG of + true -> Owner ! {recd, Count + 1}, + recv(Owner, Client1, Count + 1, erlang:now()); + false -> recv(Owner, Client1, Count + 1, TS) + end. + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_test.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_test.erl new file mode 100644 index 0000000..2fdca2d --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_test.erl @@ -0,0 +1,41 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_stomp_test). +-export([all_tests/0]). +-import(rabbit_misc, [pget/2]). + +-include_lib("amqp_client/include/amqp_client.hrl"). +-include("rabbit_stomp_frame.hrl"). +-define(DESTINATION, "/queue/bulk-test"). + +all_tests() -> + test_messages_not_dropped_on_disconnect(), + ok. + +test_messages_not_dropped_on_disconnect() -> + {ok, Client} = rabbit_stomp_client:connect(), + [rabbit_stomp_client:send( + Client, "SEND", [{"destination", ?DESTINATION}], + [integer_to_list(Count)]) || Count <- lists:seq(1, 1000)], + rabbit_stomp_client:disconnect(Client), + QName = rabbit_misc:r(<<"/">>, queue, <<"bulk-test">>), + timer:sleep(3000), + rabbit_amqqueue:with( + QName, fun(Q) -> + 1000 = pget(messages, rabbit_amqqueue:info(Q, [messages])) + end), + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_test_frame.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_test_frame.erl new file mode 100644 index 0000000..34b7472 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_test_frame.erl @@ -0,0 +1,193 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_stomp_test_frame). + +-include_lib("eunit/include/eunit.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). +-include("rabbit_stomp_frame.hrl"). +-include("rabbit_stomp_headers.hrl"). + +parse_simple_frame_test() -> + parse_simple_frame_gen("\n"). + +parse_simple_frame_crlf_test() -> + parse_simple_frame_gen("\r\n"). + +parse_simple_frame_gen(Term) -> + Headers = [{"header1", "value1"}, {"header2", "value2"}], + Content = frame_string("COMMAND", + Headers, + "Body Content", + Term), + {"COMMAND", Frame, _State} = parse_complete(Content), + [?assertEqual({ok, Value}, + rabbit_stomp_frame:header(Frame, Key)) || + {Key, Value} <- Headers], + #stomp_frame{body_iolist = Body} = Frame, + ?assertEqual(<<"Body Content">>, iolist_to_binary(Body)). + +parse_simple_frame_with_null_test() -> + Headers = [{"header1", "value1"}, {"header2", "value2"}, + {?HEADER_CONTENT_LENGTH, "12"}], + Content = frame_string("COMMAND", + Headers, + "Body\0Content"), + {"COMMAND", Frame, _State} = parse_complete(Content), + [?assertEqual({ok, Value}, + rabbit_stomp_frame:header(Frame, Key)) || + {Key, Value} <- Headers], + #stomp_frame{body_iolist = Body} = Frame, + ?assertEqual(<<"Body\0Content">>, iolist_to_binary(Body)). + +parse_large_content_frame_with_nulls_test() -> + BodyContent = string:copies("012345678\0", 1024), + Headers = [{"header1", "value1"}, {"header2", "value2"}, + {?HEADER_CONTENT_LENGTH, integer_to_list(string:len(BodyContent))}], + Content = frame_string("COMMAND", + Headers, + BodyContent), + {"COMMAND", Frame, _State} = parse_complete(Content), + [?assertEqual({ok, Value}, + rabbit_stomp_frame:header(Frame, Key)) || + {Key, Value} <- Headers], + #stomp_frame{body_iolist = Body} = Frame, + ?assertEqual(list_to_binary(BodyContent), iolist_to_binary(Body)). + +parse_command_only_test() -> + {ok, #stomp_frame{command = "COMMAND"}, _Rest} = parse("COMMAND\n\n\0"). + +parse_ignore_empty_frames_test() -> + {ok, #stomp_frame{command = "COMMAND"}, _Rest} = parse("\0\0COMMAND\n\n\0"). + +parse_heartbeat_interframe_test() -> + {ok, #stomp_frame{command = "COMMAND"}, _Rest} = parse("\nCOMMAND\n\n\0"). + +parse_crlf_interframe_test() -> + {ok, #stomp_frame{command = "COMMAND"}, _Rest} = parse("\r\nCOMMAND\n\n\0"). + +parse_carriage_return_not_ignored_interframe_test() -> + {error, {unexpected_chars_between_frames, "\rC"}} = parse("\rCOMMAND\n\n\0"). + +parse_carriage_return_mid_command_test() -> + {error, {unexpected_chars_in_command, "\rA"}} = parse("COMM\rAND\n\n\0"). + +parse_carriage_return_end_command_test() -> + {error, {unexpected_chars_in_command, "\r\r"}} = parse("COMMAND\r\r\n\n\0"). + +parse_resume_mid_command_test() -> + First = "COMM", + Second = "AND\n\n\0", + {more, Resume} = parse(First), + {ok, #stomp_frame{command = "COMMAND"}, _Rest} = parse(Second, Resume). + +parse_resume_mid_header_key_test() -> + First = "COMMAND\nheade", + Second = "r1:value1\n\n\0", + {more, Resume} = parse(First), + {ok, Frame = #stomp_frame{command = "COMMAND"}, _Rest} = + parse(Second, Resume), + ?assertEqual({ok, "value1"}, + rabbit_stomp_frame:header(Frame, "header1")). + +parse_resume_mid_header_val_test() -> + First = "COMMAND\nheader1:val", + Second = "ue1\n\n\0", + {more, Resume} = parse(First), + {ok, Frame = #stomp_frame{command = "COMMAND"}, _Rest} = + parse(Second, Resume), + ?assertEqual({ok, "value1"}, + rabbit_stomp_frame:header(Frame, "header1")). + +parse_resume_mid_body_test() -> + First = "COMMAND\n\nABC", + Second = "DEF\0", + {more, Resume} = parse(First), + {ok, #stomp_frame{command = "COMMAND", body_iolist = Body}, _Rest} = + parse(Second, Resume), + ?assertEqual([<<"ABC">>, <<"DEF">>], Body). + +parse_no_header_stripping_test() -> + Content = "COMMAND\nheader: foo \n\n\0", + {ok, Frame, _} = parse(Content), + {ok, Val} = rabbit_stomp_frame:header(Frame, "header"), + ?assertEqual(" foo ", Val). + +parse_multiple_headers_test() -> + Content = "COMMAND\nheader:correct\nheader:incorrect\n\n\0", + {ok, Frame, _} = parse(Content), + {ok, Val} = rabbit_stomp_frame:header(Frame, "header"), + ?assertEqual("correct", Val). + +header_no_colon_test() -> + Content = "COMMAND\n" + "hdr1:val1\n" + "hdrerror\n" + "hdr2:val2\n" + "\n\0", + ?assertEqual(parse(Content), {error, {header_no_value, "hdrerror"}}). + +no_nested_escapes_test() -> + Content = "COM\\\\rAND\n" % no escapes + "hdr\\\\rname:" % one escape + "hdr\\\\rval\n\n\0", % one escape + {ok, Frame, _} = parse(Content), + ?assertEqual(Frame, + #stomp_frame{command = "COM\\\\rAND", + headers = [{"hdr\\rname", "hdr\\rval"}], + body_iolist = []}). + +header_name_with_cr_test() -> + Content = "COMMAND\nhead\rer:val\n\n\0", + {error, {unexpected_chars_in_header, "\re"}} = parse(Content). + +header_value_with_cr_test() -> + Content = "COMMAND\nheader:val\rue\n\n\0", + {error, {unexpected_chars_in_header, "\ru"}} = parse(Content). + +header_value_with_colon_test() -> + Content = "COMMAND\nheader:val:ue\n\n\0", + {ok, Frame, _} = parse(Content), + ?assertEqual(Frame, + #stomp_frame{ command = "COMMAND", + headers = [{"header", "val:ue"}], + body_iolist = []}). + +headers_escaping_roundtrip_test() -> + Content = "COMMAND\nhead\\r\\c\\ner:\\c\\n\\r\\\\\n\n\0", + {ok, Frame, _} = parse(Content), + {ok, Val} = rabbit_stomp_frame:header(Frame, "head\r:\ner"), + ?assertEqual(":\n\r\\", Val), + Serialized = lists:flatten(rabbit_stomp_frame:serialize(Frame)), + ?assertEqual(Content, rabbit_misc:format("~s", [Serialized])). + +parse(Content) -> + parse(Content, rabbit_stomp_frame:initial_state()). +parse(Content, State) -> + rabbit_stomp_frame:parse(list_to_binary(Content), State). + +parse_complete(Content) -> + {ok, Frame = #stomp_frame{command = Command}, State} = parse(Content), + {Command, Frame, State}. + +frame_string(Command, Headers, BodyContent) -> + frame_string(Command, Headers, BodyContent, "\n"). + +frame_string(Command, Headers, BodyContent, Term) -> + HeaderString = + lists:flatten([Key ++ ":" ++ Value ++ Term || {Key, Value} <- Headers]), + Command ++ Term ++ HeaderString ++ Term ++ BodyContent ++ "\0". + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_test_util.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_test_util.erl new file mode 100644 index 0000000..e7459e3 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/rabbit_stomp_test_util.erl @@ -0,0 +1,226 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_stomp_test_util). + +-include_lib("eunit/include/eunit.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). +-include_lib("amqp_client/include/rabbit_routing_prefixes.hrl"). +-include("rabbit_stomp_frame.hrl"). + +%%-------------------------------------------------------------------- +%% Header Processing Tests +%%-------------------------------------------------------------------- + +longstr_field_test() -> + {<<"ABC">>, longstr, <<"DEF">>} = + rabbit_stomp_util:longstr_field("ABC", "DEF"). + +message_properties_test() -> + Headers = [ + {"content-type", "text/plain"}, + {"content-encoding", "UTF-8"}, + {"persistent", "true"}, + {"priority", "1"}, + {"correlation-id", "123"}, + {"reply-to", "something"}, + {"expiration", "my-expiration"}, + {"amqp-message-id", "M123"}, + {"timestamp", "123456"}, + {"type", "freshly-squeezed"}, + {"user-id", "joe"}, + {"app-id", "joe's app"}, + {"str", "foo"}, + {"int", "123"} + ], + + #'P_basic'{ + content_type = <<"text/plain">>, + content_encoding = <<"UTF-8">>, + delivery_mode = 2, + priority = 1, + correlation_id = <<"123">>, + reply_to = <<"something">>, + expiration = <<"my-expiration">>, + message_id = <<"M123">>, + timestamp = 123456, + type = <<"freshly-squeezed">>, + user_id = <<"joe">>, + app_id = <<"joe's app">>, + headers = [{<<"str">>, longstr, <<"foo">>}, + {<<"int">>, longstr, <<"123">>}] + } = + rabbit_stomp_util:message_properties(#stomp_frame{headers = Headers}). + +message_headers_test() -> + Properties = #'P_basic'{ + headers = [{<<"str">>, longstr, <<"foo">>}, + {<<"int">>, signedint, 123}], + content_type = <<"text/plain">>, + content_encoding = <<"UTF-8">>, + delivery_mode = 2, + priority = 1, + correlation_id = 123, + reply_to = <<"something">>, + message_id = <<"M123">>, + timestamp = 123456, + type = <<"freshly-squeezed">>, + user_id = <<"joe">>, + app_id = <<"joe's app">>}, + + Headers = rabbit_stomp_util:message_headers(Properties), + + Expected = [ + {"content-type", "text/plain"}, + {"content-encoding", "UTF-8"}, + {"persistent", "true"}, + {"priority", "1"}, + {"correlation-id", "123"}, + {"reply-to", "something"}, + {"expiration", "my-expiration"}, + {"amqp-message-id", "M123"}, + {"timestamp", "123456"}, + {"type", "freshly-squeezed"}, + {"user-id", "joe"}, + {"app-id", "joe's app"}, + {"str", "foo"}, + {"int", "123"} + ], + + [] = lists:subtract(Headers, Expected). + +minimal_message_headers_with_no_custom_test() -> + Delivery = #'basic.deliver'{ + consumer_tag = <<"Q_123">>, + delivery_tag = 123, + exchange = <<"">>, + routing_key = <<"foo">>}, + + Properties = #'P_basic'{}, + + Headers = rabbit_stomp_util:message_headers(Properties), + Expected = [ + {"content-type", "text/plain"}, + {"content-encoding", "UTF-8"}, + {"amqp-message-id", "M123"} + ], + + [] = lists:subtract(Headers, Expected). + +headers_post_process_test() -> + Headers = [{"header1", "1"}, + {"header2", "12"}, + {"reply-to", "something"}], + Expected = [{"header1", "1"}, + {"header2", "12"}, + {"reply-to", "/reply-queue/something"}], + [] = lists:subtract( + rabbit_stomp_util:headers_post_process(Headers), Expected). + +headers_post_process_noop_replyto_test() -> + [begin + Headers = [{"reply-to", Prefix ++ "/something"}], + Headers = rabbit_stomp_util:headers_post_process(Headers) + end || Prefix <- rabbit_routing_util:dest_prefixes()]. + +headers_post_process_noop2_test() -> + Headers = [{"header1", "1"}, + {"header2", "12"}], + Expected = [{"header1", "1"}, + {"header2", "12"}], + [] = lists:subtract( + rabbit_stomp_util:headers_post_process(Headers), Expected). + +negotiate_version_both_empty_test() -> + {error, no_common_version} = rabbit_stomp_util:negotiate_version([],[]). + +negotiate_version_no_common_test() -> + {error, no_common_version} = + rabbit_stomp_util:negotiate_version(["1.2"],["1.3"]). + +negotiate_version_simple_common_test() -> + {ok, "1.2"} = + rabbit_stomp_util:negotiate_version(["1.2"],["1.2"]). + +negotiate_version_two_choice_common_test() -> + {ok, "1.3"} = + rabbit_stomp_util:negotiate_version(["1.2", "1.3"],["1.2", "1.3"]). + +negotiate_version_two_choice_common_out_of_order_test() -> + {ok, "1.3"} = + rabbit_stomp_util:negotiate_version(["1.3", "1.2"],["1.2", "1.3"]). + +negotiate_version_two_choice_big_common_test() -> + {ok, "1.20.23"} = + rabbit_stomp_util:negotiate_version(["1.20.23", "1.30.456"], + ["1.20.23", "1.30.457"]). +negotiate_version_choice_mismatched_length_test() -> + {ok, "1.2.3"} = + rabbit_stomp_util:negotiate_version(["1.2", "1.2.3"], + ["1.2.3", "1.2"]). +negotiate_version_choice_duplicates_test() -> + {ok, "1.2"} = + rabbit_stomp_util:negotiate_version(["1.2", "1.2"], + ["1.2", "1.2"]). +trim_headers_test() -> + #stomp_frame{headers = [{"one", "foo"}, {"two", "baz "}]} = + rabbit_stomp_util:trim_headers( + #stomp_frame{headers = [{"one", " foo"}, {"two", " baz "}]}). + +%%-------------------------------------------------------------------- +%% Frame Parsing Tests +%%-------------------------------------------------------------------- + +ack_mode_auto_test() -> + Frame = #stomp_frame{headers = [{"ack", "auto"}]}, + {auto, _} = rabbit_stomp_util:ack_mode(Frame). + +ack_mode_auto_default_test() -> + Frame = #stomp_frame{headers = []}, + {auto, _} = rabbit_stomp_util:ack_mode(Frame). + +ack_mode_client_test() -> + Frame = #stomp_frame{headers = [{"ack", "client"}]}, + {client, true} = rabbit_stomp_util:ack_mode(Frame). + +ack_mode_client_individual_test() -> + Frame = #stomp_frame{headers = [{"ack", "client-individual"}]}, + {client, false} = rabbit_stomp_util:ack_mode(Frame). + +consumer_tag_id_test() -> + Frame = #stomp_frame{headers = [{"id", "foo"}]}, + {ok, <<"T_foo">>, _} = rabbit_stomp_util:consumer_tag(Frame). + +consumer_tag_destination_test() -> + Frame = #stomp_frame{headers = [{"destination", "foo"}]}, + {ok, <<"Q_foo">>, _} = rabbit_stomp_util:consumer_tag(Frame). + +consumer_tag_invalid_test() -> + Frame = #stomp_frame{headers = []}, + {error, missing_destination_header} = rabbit_stomp_util:consumer_tag(Frame). + +%%-------------------------------------------------------------------- +%% Message ID Parsing Tests +%%-------------------------------------------------------------------- + +parse_valid_message_id_test() -> + {ok, {<<"bar">>, "abc", 123}} = + rabbit_stomp_util:parse_message_id("bar@@abc@@123"). + +parse_invalid_message_id_test() -> + {error, invalid_message_id} = + rabbit_stomp_util:parse_message_id("blah"). + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/reliability.py b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/reliability.py new file mode 100644 index 0000000..08476d5 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/reliability.py @@ -0,0 +1,34 @@ +import base +import stomp +import unittest +import time + +class TestReliability(base.BaseTest): + + def test_send_and_disconnect(self): + ''' Test close socket after send does not lose messages ''' + d = "/queue/reliability" + pub_conn = self.create_connection() + try: + msg = "0" * (128) + + count = 10000 + + listener = base.WaitableListener() + listener.reset(count) + self.conn.set_listener('', listener) + self.conn.subscribe(destination=d) + + for x in range(0, count): + pub_conn.send(msg + str(x), destination=d) + time.sleep(2.0) + pub_conn.close_socket() + + if listener.await(30): + self.assertEquals(count, len(listener.messages)) + else: + listener.print_state("Final state of listener:") + self.fail("Did not receive %s messages in time" % count) + finally: + if pub_conn.is_connected(): + pub_conn.disconnect() diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/ssl.config b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/ssl.config new file mode 100644 index 0000000..4fd77fb --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/ssl.config @@ -0,0 +1,12 @@ +[{rabbitmq_stomp, [{default_user, []}, + {ssl_cert_login, true}, + {ssl_listeners, [61614]} + ]}, + {rabbit, [{ssl_options, [{cacertfile,"%%CERTS_DIR%%/testca/cacert.pem"}, + {certfile,"%%CERTS_DIR%%/server/cert.pem"}, + {keyfile,"%%CERTS_DIR%%/server/key.pem"}, + {verify,verify_peer}, + {fail_if_no_peer_cert,true} + ]} + ]} +]. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/ssl_lifecycle.py b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/ssl_lifecycle.py new file mode 100644 index 0000000..8a89a7a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/ssl_lifecycle.py @@ -0,0 +1,70 @@ +import unittest +import os + +import stomp +import base + +ssl_key_file = os.path.abspath("test/certs/client/key.pem") +ssl_cert_file = os.path.abspath("test/certs/client/cert.pem") +ssl_ca_certs = os.path.abspath("test/certs/testca/cacert.pem") + +class TestSslClient(unittest.TestCase): + + def __ssl_connect(self): + conn = stomp.Connection(user="guest", passcode="guest", + host_and_ports = [ ('localhost', 61614) ], + use_ssl = True, ssl_key_file = ssl_key_file, + ssl_cert_file = ssl_cert_file, + ssl_ca_certs = ssl_ca_certs) + + conn.start() + conn.connect() + return conn + + def __ssl_auth_connect(self): + conn = stomp.Connection(host_and_ports = [ ('localhost', 61614) ], + use_ssl = True, ssl_key_file = ssl_key_file, + ssl_cert_file = ssl_cert_file, + ssl_ca_certs = ssl_ca_certs) + conn.start() + conn.connect() + return conn + + def test_ssl_connect(self): + conn = self.__ssl_connect() + conn.stop() + + def test_ssl_auth_connect(self): + conn = self.__ssl_auth_connect() + conn.stop() + + def test_ssl_send_receive(self): + conn = self.__ssl_connect() + self.__test_conn(conn) + + def test_ssl_auth_send_receive(self): + conn = self.__ssl_auth_connect() + self.__test_conn(conn) + + def __test_conn(self, conn): + try: + listener = base.WaitableListener() + + conn.set_listener('', listener) + + d = "/topic/ssl.test" + conn.subscribe(destination=d, receipt="sub") + + self.assertTrue(listener.await(1)) + + self.assertEquals("sub", + listener.receipts[0]['headers']['receipt-id']) + + listener.reset(1) + conn.send("Hello SSL!", destination=d) + + self.assertTrue(listener.await()) + + self.assertEquals("Hello SSL!", listener.messages[0]['message']) + finally: + conn.disconnect() diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/test.py b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/test.py new file mode 100755 index 0000000..4979552 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/test.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +import test_runner + +if __name__ == '__main__': + modules = ['parsing', 'destinations', 'lifecycle', 'transactions', + 'ack', 'errors', 'reliability'] + test_runner.run_unittests(modules) + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/test_connect_options.py b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/test_connect_options.py new file mode 100755 index 0000000..6822f97 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/test_connect_options.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +import test_runner + +if __name__ == '__main__': + modules = ['connect_options'] + test_runner.run_unittests(modules) + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/test_runner.py b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/test_runner.py new file mode 100644 index 0000000..7216865 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/test_runner.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +import unittest +import sys +import os + +def add_deps_to_path(): + deps_dir = os.path.realpath(os.path.join(__file__, "..", "..", "..", "deps")) + sys.path.append(os.path.join(deps_dir, "stomppy", "stomppy")) + +def run_unittests(modules): + add_deps_to_path() + + suite = unittest.TestSuite() + for m in modules: + mod = __import__(m) + for name in dir(mod): + obj = getattr(mod, name) + if name.startswith("Test") and issubclass(obj, unittest.TestCase): + suite.addTest(unittest.TestLoader().loadTestsFromTestCase(obj)) + + ts = unittest.TextTestRunner().run(unittest.TestSuite(suite)) + if ts.errors or ts.failures: + sys.exit(1) + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/test_ssl.py b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/test_ssl.py new file mode 100755 index 0000000..e96be6a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/test_ssl.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +import test_runner +import test_util + +if __name__ == '__main__': + modules = ['ssl_lifecycle'] + test_util.ensure_ssl_auth_user() + test_runner.run_unittests(modules) + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/test_util.py b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/test_util.py new file mode 100644 index 0000000..f22fd66 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/test_util.py @@ -0,0 +1,42 @@ +import subprocess +import socket +import sys +import os +import os.path + +def ensure_ssl_auth_user(): + user = 'O=client,CN=%s' % socket.gethostname() + rabbitmqctl(['stop_app']) + rabbitmqctl(['reset']) + rabbitmqctl(['start_app']) + rabbitmqctl(['add_user', user, 'foo']) + rabbitmqctl(['clear_password', user]) + rabbitmqctl(['set_permissions', user, '.*', '.*', '.*']) + +def enable_implicit_connect(): + switch_config(implicit_connect='true', default_user='[{login, "guest"}, {passcode, "guest"}]') + +def disable_implicit_connect(): + switch_config(implicit_connect='false', default_user='[]') + +def enable_default_user(): + switch_config(default_user='[{login, "guest"}, {passcode, "guest"}]') + +def disable_default_user(): + switch_config(default_user='[]') + +def switch_config(implicit_connect='', default_user=''): + cmd = 'application:stop(rabbitmq_stomp),' + if implicit_connect: + cmd += 'application:set_env(rabbitmq_stomp,implicit_connect,' + implicit_connect + '),' + if default_user: + cmd += 'application:set_env(rabbitmq_stomp,default_user,' + default_user + '),' + cmd += 'application:start(rabbitmq_stomp).' + rabbitmqctl(['eval', cmd]) + +def rabbitmqctl(args): + ctl = os.path.normpath(os.path.join(os.getcwd(), sys.argv[0], '../../../../rabbitmq-server/scripts/rabbitmqctl')) + cmdline = [ctl, '-n', 'rabbit-test'] + cmdline.extend(args) + subprocess.check_call(cmdline) + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/transactions.py b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/transactions.py new file mode 100644 index 0000000..1d95f7e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-stomp/test/src/transactions.py @@ -0,0 +1,54 @@ +import unittest +import stomp +import base +import time + +class TestTransactions(base.BaseTest): + + def test_tx_commit(self): + ''' Test TX with a COMMIT and ensure messages are delivered ''' + d = "/exchange/amq.fanout" + tx = "test.tx" + + self.listener.reset() + self.conn.subscribe(destination=d) + self.conn.begin(transaction=tx) + self.conn.send("hello!", destination=d, transaction=tx) + self.conn.send("again!", destination=d) + + ## should see the second message + self.assertTrue(self.listener.await(3)) + self.assertEquals(1, len(self.listener.messages)) + self.assertEquals("again!", self.listener.messages[0]['message']) + + ## now look for the first message + self.listener.reset() + self.conn.commit(transaction=tx) + self.assertTrue(self.listener.await(3)) + self.assertEquals(1, len(self.listener.messages), + "Missing committed message") + self.assertEquals("hello!", self.listener.messages[0]['message']) + + def test_tx_abort(self): + ''' Test TX with an ABORT and ensure messages are discarded ''' + d = "/exchange/amq.fanout" + tx = "test.tx" + + self.listener.reset() + self.conn.subscribe(destination=d) + self.conn.begin(transaction=tx) + self.conn.send("hello!", destination=d, transaction=tx) + self.conn.send("again!", destination=d) + + ## should see the second message + self.assertTrue(self.listener.await(3)) + self.assertEquals(1, len(self.listener.messages)) + self.assertEquals("again!", self.listener.messages[0]['message']) + + ## now look for the first message to be discarded + self.listener.reset() + self.conn.abort(transaction=tx) + self.assertFalse(self.listener.await(3)) + self.assertEquals(0, len(self.listener.messages), + "Unexpected committed message") + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/.srcdist_done b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/.srcdist_done new file mode 100644 index 0000000..e69de29 diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/Makefile b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/Makefile new file mode 100644 index 0000000..74a96ae --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/Makefile @@ -0,0 +1,172 @@ +.PHONY: all full lite conformance16 update-qpid-testsuite run-qpid-testsuite \ + prepare restart-app stop-app start-app \ + start-secondary-app stop-secondary-app \ + restart-secondary-node cleanup force-snapshot \ + enable-ha disable-ha + +include ../umbrella.mk + +BROKER_DIR=../rabbitmq-server +TEST_DIR=../rabbitmq-java-client + +TEST_RABBIT_PORT=5672 +TEST_HARE_PORT=5673 +TEST_RABBIT_SSL_PORT=5671 +TEST_HARE_SSL_PORT=5670 + +COVER=true + +ifeq ($(COVER), true) +COVER_START=start-cover +COVER_STOP=stop-cover +else +COVER_START= +COVER_STOP= +endif + +# we actually want to test for ssl above 3.9 (eg >= 3.10), but this +# comparison is buggy because it doesn't believe 10 > 9, so it doesn't +# believe 3.10 > 3.9. As a result, we cheat, and use the erts version +# instead. SSL 3.10 came out with R13B, which included erts 5.7.1, so +# we require > 5.7.0. +SSL_VERIFY=$(shell if [ $$(erl -noshell -eval 'io:format(erlang:system_info(version)), halt().') \> "5.7.0" ]; then echo "true"; else echo "false"; fi) +ifeq (true,$(SSL_VERIFY)) +SSL_VERIFY_OPTION :={verify,verify_peer},{fail_if_no_peer_cert,false} +else +SSL_VERIFY_OPTION :={verify_code,1} +endif +export SSL_CERTS_DIR := $(realpath certs) +export PASSWORD := test +RABBIT_BROKER_OPTIONS := "-rabbit ssl_listeners [{\\\"0.0.0.0\\\",$(TEST_RABBIT_SSL_PORT)}] -rabbit ssl_options [{cacertfile,\\\"$(SSL_CERTS_DIR)/testca/cacert.pem\\\"},{certfile,\\\"$(SSL_CERTS_DIR)/server/cert.pem\\\"},{keyfile,\\\"$(SSL_CERTS_DIR)/server/key.pem\\\"},$(SSL_VERIFY_OPTION)] -rabbit auth_mechanisms ['PLAIN','AMQPLAIN','EXTERNAL','RABBIT-CR-DEMO']" +HARE_BROKER_OPTIONS := "-rabbit ssl_listeners [{\\\"0.0.0.0\\\",$(TEST_HARE_SSL_PORT)}] -rabbit ssl_options [{cacertfile,\\\"$(SSL_CERTS_DIR)/testca/cacert.pem\\\"},{certfile,\\\"$(SSL_CERTS_DIR)/server/cert.pem\\\"},{keyfile,\\\"$(SSL_CERTS_DIR)/server/key.pem\\\"},$(SSL_VERIFY_OPTION)] -rabbit auth_mechanisms ['PLAIN','AMQPLAIN','EXTERNAL','RABBIT-CR-DEMO']" + +TESTS_FAILED := echo '\n============'\ + '\nTESTS FAILED'\ + '\n============\n' + +all: full test + +full: + OK=true && \ + $(MAKE) prepare && \ + { $(MAKE) -C $(BROKER_DIR) run-tests || { OK=false; $(TESTS_FAILED); } } && \ + { $(MAKE) run-qpid-testsuite || { OK=false; $(TESTS_FAILED); } } && \ + { ( cd $(TEST_DIR) && ant test-suite ) || { OK=false; $(TESTS_FAILED); } } && \ + $(MAKE) cleanup && { $$OK || $(TESTS_FAILED); } && $$OK + +lite: + OK=true && \ + $(MAKE) prepare && \ + { $(MAKE) -C $(BROKER_DIR) run-tests || OK=false; } && \ + { ( cd $(TEST_DIR) && ant test-suite ) || OK=false; } && \ + $(MAKE) cleanup && $$OK + +conformance16: + OK=true && \ + $(MAKE) prepare && \ + { $(MAKE) -C $(BROKER_DIR) run-tests || OK=false; } && \ + { ( cd $(TEST_DIR) && ant test-suite ) || OK=false; } && \ + $(MAKE) cleanup && $$OK + +qpid_testsuite: + $(MAKE) update-qpid-testsuite + +update-qpid-testsuite: + svn co -r 906960 http://svn.apache.org/repos/asf/qpid/trunk/qpid/python qpid_testsuite + # hg clone http://rabbit-hg.eng.vmware.com/mirrors/qpid_testsuite + - patch -N -r - -p0 -d qpid_testsuite/ < qpid_patch + +prepare-qpid-patch: + cd qpid_testsuite && svn diff > ../qpid_patch && cd .. + +run-qpid-testsuite: qpid_testsuite + AMQP_SPEC=../rabbitmq-docs/specs/amqp0-8.xml qpid_testsuite/qpid-python-test -m tests_0-8 -I rabbit_failing.txt + AMQP_SPEC=../rabbitmq-docs/specs/amqp0-9-1.xml qpid_testsuite/qpid-python-test -m tests_0-9 -I rabbit_failing.txt + +clean: + rm -rf qpid_testsuite + +prepare: create_ssl_certs + $(MAKE) -C $(BROKER_DIR) \ + RABBITMQ_NODENAME=hare \ + RABBITMQ_NODE_IP_ADDRESS=0.0.0.0 \ + RABBITMQ_NODE_PORT=${TEST_HARE_PORT} \ + RABBITMQ_SERVER_START_ARGS=$(HARE_BROKER_OPTIONS) \ + RABBITMQ_CONFIG_FILE=/does-not-exist \ + RABBITMQ_ENABLED_PLUGINS_FILE=/does-not-exist \ + stop-node cleandb start-background-node + $(MAKE) -C $(BROKER_DIR) \ + RABBITMQ_NODE_IP_ADDRESS=0.0.0.0 \ + RABBITMQ_NODE_PORT=${TEST_RABBIT_PORT} \ + RABBITMQ_SERVER_START_ARGS=$(RABBIT_BROKER_OPTIONS) \ + RABBITMQ_CONFIG_FILE=/does-not-exist \ + RABBITMQ_ENABLED_PLUGINS_FILE=/does-not-exist \ + stop-node cleandb start-background-node ${COVER_START} start-rabbit-on-node + $(MAKE) -C $(BROKER_DIR) RABBITMQ_NODENAME=hare start-rabbit-on-node + +start-app: + $(MAKE) -C $(BROKER_DIR) \ + RABBITMQ_NODE_IP_ADDRESS=0.0.0.0 \ + RABBITMQ_NODE_PORT=${TEST_RABBIT_PORT} \ + RABBITMQ_SERVER_START_ARGS=$(RABBIT_BROKER_OPTIONS) \ + RABBITMQ_CONFIG_FILE=/does-not-exist \ + RABBITMQ_ENABLED_PLUGINS_FILE=/does-not-exist \ + start-rabbit-on-node + +stop-app: + $(MAKE) -C $(BROKER_DIR) stop-rabbit-on-node + +restart-app: stop-app start-app + +start-secondary-app: + $(MAKE) -C $(BROKER_DIR) RABBITMQ_NODENAME=hare start-rabbit-on-node + +stop-secondary-app: + $(MAKE) -C $(BROKER_DIR) RABBITMQ_NODENAME=hare stop-rabbit-on-node + +restart-secondary-node: + $(MAKE) -C $(BROKER_DIR) \ + RABBITMQ_NODENAME=hare \ + RABBITMQ_NODE_IP_ADDRESS=0.0.0.0 \ + RABBITMQ_NODE_PORT=${TEST_HARE_PORT} \ + RABBITMQ_SERVER_START_ARGS=$(HARE_BROKER_OPTIONS) \ + RABBITMQ_CONFIG_FILE=/does-not-exist \ + RABBITMQ_ENABLED_PLUGINS_FILE=/does-not-exist \ + stop-node start-background-node + $(MAKE) -C $(BROKER_DIR) RABBITMQ_NODENAME=hare start-rabbit-on-node + +force-snapshot: + $(MAKE) -C $(BROKER_DIR) force-snapshot + +set-resource-alarm: + $(MAKE) -C $(BROKER_DIR) set-resource-alarm SOURCE=$(SOURCE) + +clear-resource-alarm: + $(MAKE) -C $(BROKER_DIR) clear-resource-alarm SOURCE=$(SOURCE) + +enable-ha: + $(BROKER_DIR)/scripts/rabbitmqctl set_policy HA \ + ".*" '{"ha-mode": "all"}' + +disable-ha: + $(BROKER_DIR)/scripts/rabbitmqctl clear_policy HA + +cleanup: + -$(MAKE) -C $(BROKER_DIR) \ + RABBITMQ_NODENAME=hare \ + RABBITMQ_NODE_IP_ADDRESS=0.0.0.0 \ + RABBITMQ_NODE_PORT=${TEST_HARE_PORT} \ + RABBITMQ_SERVER_START_ARGS=$(HARE_BROKER_OPTIONS) \ + RABBITMQ_CONFIG_FILE=/does-not-exist \ + RABBITMQ_ENABLED_PLUGINS_FILE=/does-not-exist \ + stop-rabbit-on-node stop-node + -$(MAKE) -C $(BROKER_DIR) \ + RABBITMQ_NODE_IP_ADDRESS=0.0.0.0 \ + RABBITMQ_NODE_PORT=${TEST_RABBIT_PORT} \ + RABBITMQ_SERVER_START_ARGS=$(RABBIT_BROKER_OPTIONS) \ + RABBITMQ_CONFIG_FILE=/does-not-exist \ + RABBITMQ_ENABLED_PLUGINS_FILE=/does-not-exist \ + stop-rabbit-on-node ${COVER_STOP} stop-node + +create_ssl_certs: + $(MAKE) -C certs DIR=$(SSL_CERTS_DIR) clean all diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/README b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/README new file mode 100644 index 0000000..3afe826 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/README @@ -0,0 +1,18 @@ +Useful targets: + +$ make lite # runs the Erlang unit tests and the Java client / functional tests +$ make full # runs both the above plus the QPid test suite +$ make test # runs the Erlang multi-node integration tests +$ make all # runs all of the above + +The multi-node tests take a long time, so you might want to run a subset: + +$ make test FILTER=dynamic_ha # <- run just one suite +$ make test FILTER=dynamic_ha:change_policy # <- run just one test + +The multi-node tests also default to coverage off, to turn it on: + +$ make test COVER=true + +This repository is not related to plugin tests; run "make test" in a +plugin directory to test that plugin. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/certs/Makefile b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/certs/Makefile new file mode 100644 index 0000000..32db63f --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/certs/Makefile @@ -0,0 +1,58 @@ +OPENSSL=openssl + +ifndef DIR +DIR := . +endif + +ifdef PASSWORD +P12PASS := true +else +P12PASS := @echo No PASSWORD defined. && false +endif + +.PRECIOUS: %/testca +.PHONY: %/clean target all p12pass + +all: client server + +client: p12pass + echo $(DIR) + $(MAKE) target DIR=$(DIR) TARGET=client EXTENSIONS=client_ca_extensions + +server: p12pass + $(MAKE) target DIR=$(DIR) TARGET=server EXTENSIONS=server_ca_extensions + +p12pass: + $(P12PASS) + +target: $(DIR)/testca + mkdir $(DIR)/$(TARGET) + { ( cd $(DIR)/$(TARGET) && \ + openssl genrsa -out key.pem 2048 &&\ + openssl req -new -key key.pem -out req.pem -outform PEM\ + -subj /CN=$$(hostname)/O=$(TARGET)/L=$$$$/ -nodes &&\ + cd ../testca && \ + openssl ca -config openssl.cnf -in ../$(TARGET)/req.pem -out \ + ../$(TARGET)/cert.pem -notext -batch -extensions \ + $(EXTENSIONS) && \ + cd ../$(TARGET) && \ + openssl pkcs12 -export -out keycert.p12 -in cert.pem -inkey key.pem \ + -passout pass:$(PASSWORD) ) || (rm -rf $(DIR)/$(TARGET) && false); } + +$(DIR)/testca: + mkdir $(DIR)/testca + cp openssl.cnf $(DIR)/testca/openssl.cnf + { ( cd $(DIR)/testca && \ + mkdir certs private && \ + chmod 700 private && \ + echo 01 > serial && \ + touch index.txt && \ + openssl req -x509 -config openssl.cnf -newkey rsa:2048 -days 365 \ + -out cacert.pem -outform PEM -subj /CN=MyTestCA/L=$$$$/ -nodes && \ + openssl x509 -in cacert.pem -out cacert.cer -outform DER ) \ + || (rm -rf $@ && false); } + +clean: + rm -rf $(DIR)/testca + rm -rf $(DIR)/server + rm -rf $(DIR)/client diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/certs/openssl.cnf b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/certs/openssl.cnf new file mode 100644 index 0000000..93ffb2f --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/certs/openssl.cnf @@ -0,0 +1,54 @@ +[ ca ] +default_ca = testca + +[ testca ] +dir = . +certificate = $dir/cacert.pem +database = $dir/index.txt +new_certs_dir = $dir/certs +private_key = $dir/private/cakey.pem +serial = $dir/serial + +default_crl_days = 7 +default_days = 365 +default_md = sha1 + +policy = testca_policy +x509_extensions = certificate_extensions + +[ testca_policy ] +commonName = supplied +stateOrProvinceName = optional +countryName = optional +emailAddress = optional +organizationName = optional +organizationalUnitName = optional +domainComponent = optional + +[ certificate_extensions ] +basicConstraints = CA:false + +[ req ] +default_bits = 2048 +default_keyfile = ./private/cakey.pem +default_md = sha1 +prompt = yes +distinguished_name = root_ca_distinguished_name +x509_extensions = root_ca_extensions + +[ root_ca_distinguished_name ] +commonName = hostname + +[ root_ca_extensions ] +basicConstraints = CA:true +keyUsage = keyCertSign, cRLSign + +[ client_ca_extensions ] +basicConstraints = CA:false +keyUsage = digitalSignature +extendedKeyUsage = 1.3.6.1.5.5.7.3.2 + +[ server_ca_extensions ] +basicConstraints = CA:false +keyUsage = keyEncipherment +extendedKeyUsage = 1.3.6.1.5.5.7.3.1 diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/package.mk b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/package.mk new file mode 100644 index 0000000..d9b0f97 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/package.mk @@ -0,0 +1,10 @@ +DEPS:=rabbitmq-erlang-client +FILTER:=all +COVER:=false +STANDALONE_TEST_COMMANDS:=rabbit_test_runner:run_multi(\"$(UMBRELLA_BASE_DIR)/rabbitmq-server\",\"$(PACKAGE_DIR)/test/ebin\",\"$(FILTER)\",$(COVER),none) + +## Require R15B to compile inet_proxy_dist since it requires includes +## introduced there. +ifeq ($(shell erl -noshell -eval 'io:format([list_to_integer(X) || X <- string:tokens(erlang:system_info(version), ".")] >= [5,9]), halt().'),true) +PACKAGE_ERLC_OPTS+=-Derlang_r15b_or_later +endif diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/qpid_config.py b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/qpid_config.py new file mode 100644 index 0000000..16388a6 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/qpid_config.py @@ -0,0 +1,26 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import os + +AMQP_SPEC_DIR=os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "../rabbitmq-docs/specs") +amqp_spec = os.path.join(AMQP_SPEC_DIR, "amqp.0-10-qpid-errata.xml") +amqp_spec_0_8 = os.path.join(AMQP_SPEC_DIR, "amqp0-8.xml") +amqp_spec_0_9 = os.path.join(AMQP_SPEC_DIR, "amqp0-9.xml") +amqp_spec = 'file://'+os.path.join(AMQP_SPEC_DIR, 'amqp.0-10.xml') diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/qpid_patch b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/qpid_patch new file mode 100644 index 0000000..2c4b590 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/qpid_patch @@ -0,0 +1,142 @@ +Index: tests_0-8/basic.py +=================================================================== +--- tests_0-8/basic.py (revision 906960) ++++ tests_0-8/basic.py (working copy) +@@ -98,7 +98,7 @@ + channel.basic_consume(queue="") + self.fail("Expected failure when consuming from unspecified queue") + except Closed, e: +- self.assertConnectionException(530, e.args[0]) ++ self.assertChannelException(404, e.args[0]) + + def test_consume_unique_consumers(self): + """ +Index: tests_0-8/exchange.py +=================================================================== +--- tests_0-8/exchange.py (revision 906960) ++++ tests_0-8/exchange.py (working copy) +@@ -138,8 +138,6 @@ + # Test automatic binding by queue name. + self.queue_declare(queue="d") + self.assertPublishConsume(queue="d", routing_key="d") +- # Test explicit bind to default queue +- self.verifyDirectExchange("") + + + # TODO aconway 2006-09-27: Fill in empty tests: +@@ -318,7 +316,7 @@ + self.channel.exchange_declare(exchange="test_different_declared_type_exchange", type="topic") + self.fail("Expected 530 for redeclaration of exchange with different type.") + except Closed, e: +- self.assertConnectionException(530, e.args[0]) ++ self.assertChannelException(406, e.args[0]) + #cleanup + other = self.connect() + c2 = other.channel(1) +Index: tests_0-8/queue.py +=================================================================== +--- tests_0-8/queue.py (revision 906960) ++++ tests_0-8/queue.py (working copy) +@@ -37,14 +37,10 @@ + channel.basic_publish(exchange="test-exchange", routing_key="key", content=Content("two")) + channel.basic_publish(exchange="test-exchange", routing_key="key", content=Content("three")) + +- #check that the queue now reports 3 messages: +- reply = channel.queue_declare(queue="test-queue") +- self.assertEqual(3, reply.message_count) +- + #now do the purge, then test that three messages are purged and the count drops to 0 + reply = channel.queue_purge(queue="test-queue"); + self.assertEqual(3, reply.message_count) +- reply = channel.queue_declare(queue="test-queue") ++ reply = channel.queue_declare(queue="test-queue", exclusive=True) + self.assertEqual(0, reply.message_count) + + #send a further message and consume it, ensuring that the other messages are really gone +@@ -71,7 +67,7 @@ + channel.queue_purge() + self.fail("Expected failure when purging unspecified queue") + except Closed, e: +- self.assertConnectionException(530, e.args[0]) ++ self.assertChannelException(404, e.args[0]) + + #cleanup + other = self.connect() +@@ -174,11 +170,7 @@ + #check attempted deletion of non-existant queue is handled correctly: + channel = self.client.channel(2) + channel.channel_open() +- try: +- channel.queue_delete(queue="i-dont-exist", if_empty="True") +- self.fail("Expected delete of non-existant queue to fail") +- except Closed, e: +- self.assertChannelException(404, e.args[0]) ++ channel.queue_delete(queue="i-dont-exist", if_empty="True") + + + +Index: qpid/codec.py +=================================================================== +--- qpid/codec.py (revision 906960) ++++ qpid/codec.py (working copy) +@@ -76,6 +76,7 @@ + if not self.types: + self.typecode(ord('S'), "longstr") + self.typecode(ord('I'), "long") ++ self.typecode(ord('t'), "bool") + + def typecode(self, code, type): + self.types[code] = type +@@ -206,6 +207,22 @@ + """ + return self.unpack("!B") + ++ def encode_bool(self, b): ++ """ ++ encodes bool (8 bits) data 't' in network byte order ++ """ ++ ++ if ((b is not True) and (b is not False)): ++ raise ValueError('Valid range of bool is True or False') ++ ++ self.pack("!B", int(b)) ++ ++ def decode_bool(self): ++ """ ++ decodes a bool (8 bits) encoded in network byte order ++ """ ++ return bool(self.unpack("!B")) ++ + def encode_short(self, o): + """ + encodes short (16 bits) data 'o' in network byte order +Index: qpid/testlib.py +=================================================================== +--- qpid/testlib.py (revision 906960) ++++ qpid/testlib.py (working copy) +@@ -67,8 +67,7 @@ + + if not self.client.closed: + self.client.channel(0).connection_close(reply_code=200) +- else: +- self.client.close() ++ self.client.close() + + def connect(self, host=None, port=None, user=None, password=None, tune_params=None): + """Create a new connction, return the Client object""" +Index: qpid_config.py +=================================================================== +--- qpid_config.py (revision 906960) ++++ qpid_config.py (working copy) +@@ -19,7 +19,8 @@ + + import os + +-AMQP_SPEC_DIR=os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "specs") ++AMQP_SPEC_DIR=os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "../rabbitmq-docs/specs") + amqp_spec = os.path.join(AMQP_SPEC_DIR, "amqp.0-10-qpid-errata.xml") +-amqp_spec_0_8 = os.path.join(AMQP_SPEC_DIR, "amqp.0-8.xml") +-amqp_spec_0_9 = os.path.join(AMQP_SPEC_DIR, "amqp.0-9.xml") ++amqp_spec_0_8 = os.path.join(AMQP_SPEC_DIR, "amqp0-8.xml") ++amqp_spec_0_9 = os.path.join(AMQP_SPEC_DIR, "amqp0-9.xml") ++amqp_spec = 'file://'+os.path.join(AMQP_SPEC_DIR, 'amqp.0-10.xml') diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/rabbit_failing.txt b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/rabbit_failing.txt new file mode 100644 index 0000000..be4eccf --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/rabbit_failing.txt @@ -0,0 +1,9 @@ +tests_0-8.basic.BasicTests.test_ack +tests_0-8.basic.BasicTests.test_consume_no_local +tests_0-8.basic.BasicTests.test_qos_prefetch_count +tests_0-8.basic.BasicTests.test_qos_prefetch_size +tests_0-8.broker.BrokerTests.test_basic_delivery_immediate +tests_0-8.broker.BrokerTests.test_channel_flow +tests_0-8.tx.TxTests.test_auto_rollback +tests_0-8.tx.TxTests.test_rollback +tests_0-9.query.* diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/inet_proxy_dist.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/inet_proxy_dist.erl new file mode 100644 index 0000000..458966c --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/inet_proxy_dist.erl @@ -0,0 +1,199 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% +-module(inet_proxy_dist). + +%% A distribution plugin that uses the usual inet_tcp_dist but allows +%% insertion of a proxy at the receiving end. + +%% inet_*_dist "behaviour" +-export([listen/1, accept/1, accept_connection/5, + setup/5, close/1, select/1, is_node_name/1]). + +%% For copypasta from inet_tcp_dist +-export([do_setup/6]). +-import(error_logger,[error_msg/2]). + +-define(REAL, inet_tcp_dist). + +%%---------------------------------------------------------------------------- + +listen(Name) -> ?REAL:listen(Name). +select(Node) -> ?REAL:select(Node). +accept(Listen) -> ?REAL:accept(Listen). +close(Socket) -> ?REAL:close(Socket). +is_node_name(Node) -> ?REAL:is_node_name(Node). + +accept_connection(AcceptPid, Socket, MyNode, Allowed, SetupTime) -> + ?REAL:accept_connection(AcceptPid, Socket, MyNode, Allowed, SetupTime). + +%% This is copied from inet_tcp_dist, in order to change the +%% output of erl_epmd:port_please/2. + +-ifdef(erlang_r15b_or_later). + +-include_lib("kernel/include/net_address.hrl"). +-include_lib("kernel/include/dist_util.hrl"). + +setup(Node, Type, MyNode, LongOrShortNames,SetupTime) -> + spawn_opt(?MODULE, do_setup, + [self(), Node, Type, MyNode, LongOrShortNames, SetupTime], + [link, {priority, max}]). + +do_setup(Kernel, Node, Type, MyNode, LongOrShortNames,SetupTime) -> + ?trace("~p~n",[{inet_tcp_dist,self(),setup,Node}]), + [Name, Address] = splitnode(Node, LongOrShortNames), + case inet:getaddr(Address, inet) of + {ok, Ip} -> + Timer = dist_util:start_timer(SetupTime), + case erl_epmd:port_please(Name, Ip) of + {port, TcpPort, Version} -> + ?trace("port_please(~p) -> version ~p~n", + [Node,Version]), + dist_util:reset_timer(Timer), + %% Modification START + ProxyPort = case TcpPort >= 25672 andalso TcpPort < 25700 + andalso inet_tcp_proxy:is_enabled() of + true -> TcpPort + 10000; + false -> TcpPort + end, + case inet_tcp:connect(Ip, ProxyPort, + [{active, false}, + {packet,2}]) of + {ok, Socket} -> + {ok, {_, SrcPort}} = inet:sockname(Socket), + ok = inet_tcp_proxy_manager:register( + node(), Node, SrcPort, TcpPort, ProxyPort), + %% Modification END + HSData = #hs_data{ + kernel_pid = Kernel, + other_node = Node, + this_node = MyNode, + socket = Socket, + timer = Timer, + this_flags = 0, + other_version = Version, + f_send = fun inet_tcp:send/2, + f_recv = fun inet_tcp:recv/3, + f_setopts_pre_nodeup = + fun(S) -> + inet:setopts + (S, + [{active, false}, + {packet, 4}, + nodelay()]) + end, + f_setopts_post_nodeup = + fun(S) -> + inet:setopts + (S, + [{active, true}, + {deliver, port}, + {packet, 4}, + nodelay()]) + end, + f_getll = fun inet:getll/1, + f_address = + fun(_,_) -> + #net_address{ + address = {Ip,TcpPort}, + host = Address, + protocol = tcp, + family = inet} + end, + mf_tick = fun inet_tcp_dist:tick/1, + mf_getstat = fun inet_tcp_dist:getstat/1, + request_type = Type + }, + dist_util:handshake_we_started(HSData); + R -> + io:format("~p failed! ~p~n", [node(), R]), + %% Other Node may have closed since + %% port_please ! + ?trace("other node (~p) " + "closed since port_please.~n", + [Node]), + ?shutdown(Node) + end; + _ -> + ?trace("port_please (~p) " + "failed.~n", [Node]), + ?shutdown(Node) + end; + _Other -> + ?trace("inet_getaddr(~p) " + "failed (~p).~n", [Node,_Other]), + ?shutdown(Node) + end. + +%% If Node is illegal terminate the connection setup!! +splitnode(Node, LongOrShortNames) -> + case split_node(atom_to_list(Node), $@, []) of + [Name|Tail] when Tail =/= [] -> + Host = lists:append(Tail), + case split_node(Host, $., []) of + [_] when LongOrShortNames =:= longnames -> + error_msg("** System running to use " + "fully qualified " + "hostnames **~n" + "** Hostname ~s is illegal **~n", + [Host]), + ?shutdown(Node); + L when length(L) > 1, LongOrShortNames =:= shortnames -> + error_msg("** System NOT running to use fully qualified " + "hostnames **~n" + "** Hostname ~s is illegal **~n", + [Host]), + ?shutdown(Node); + _ -> + [Name, Host] + end; + [_] -> + error_msg("** Nodename ~p illegal, no '@' character **~n", + [Node]), + ?shutdown(Node); + _ -> + error_msg("** Nodename ~p illegal **~n", [Node]), + ?shutdown(Node) + end. + +split_node([Chr|T], Chr, Ack) -> [lists:reverse(Ack)|split_node(T, Chr, [])]; +split_node([H|T], Chr, Ack) -> split_node(T, Chr, [H|Ack]); +split_node([], _, Ack) -> [lists:reverse(Ack)]. + +%% we may not always want the nodelay behaviour +%% for performance reasons + +nodelay() -> + case application:get_env(kernel, dist_nodelay) of + undefined -> + {nodelay, true}; + {ok, true} -> + {nodelay, true}; + {ok, false} -> + {nodelay, false}; + _ -> + {nodelay, true} + end. + +-else. + +setup(_Node, _Type, _MyNode, _LongOrShortNames, _SetupTime) -> + exit(erlang_r15b_required). + +do_setup(_Kernel, _Node, _Type, _MyNode, _LongOrShortNames, _SetupTime) -> + exit(erlang_r15b_required). + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/inet_tcp_proxy.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/inet_tcp_proxy.erl new file mode 100644 index 0000000..18ffcc2 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/inet_tcp_proxy.erl @@ -0,0 +1,106 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% +-module(inet_tcp_proxy). + +%% A TCP proxy for insertion into the Erlang distribution mechanism, +%% which allows us to simulate network partitions. + +-export([start/0, reconnect/1, is_enabled/0, allow/1, block/1]). + +-define(TABLE, ?MODULE). + +%% This can't start_link because there's no supervision hierarchy we +%% can easily fit it into (we need to survive all application +%% restarts). So we have to do some horrible error handling. + +start() -> + spawn(error_handler(fun go/0)), + ok. + +reconnect(Nodes) -> + [erlang:disconnect_node(N) || N <- Nodes, N =/= node()], + ok. + +is_enabled() -> + lists:member(?TABLE, ets:all()). + +allow(Node) -> ets:delete(?TABLE, Node). +block(Node) -> ets:insert(?TABLE, {Node, block}). + +%%---------------------------------------------------------------------------- + +error_handler(Thunk) -> + fun () -> + try + Thunk() + catch _:{{nodedown, _}, _} -> + %% The only other node we ever talk to is the test + %% runner; if that's down then the test is nearly + %% over; die quietly. + ok; + _:X -> + io:format(user, "TCP proxy died with ~p~n At ~p~n", + [X, erlang:get_stacktrace()]), + erlang:halt(1) + end + end. + +go() -> + ets:new(?TABLE, [public, named_table]), + {ok, Port} = application:get_env(kernel, inet_dist_listen_min), + ProxyPort = Port + 10000, + {ok, Sock} = gen_tcp:listen(ProxyPort, [inet, + {reuseaddr, true}]), + accept_loop(Sock, Port). + +accept_loop(ListenSock, Port) -> + {ok, Sock} = gen_tcp:accept(ListenSock), + Proxy = spawn(error_handler(fun() -> run_it(Sock, Port) end)), + ok = gen_tcp:controlling_process(Sock, Proxy), + accept_loop(ListenSock, Port). + +run_it(SockIn, Port) -> + case {inet:peername(SockIn), inet:sockname(SockIn)} of + {{ok, {_Addr, SrcPort}}, {ok, {Addr, _OtherPort}}} -> + {ok, Remote, This} = inet_tcp_proxy_manager:lookup(SrcPort), + case node() of + This -> ok; + _ -> exit({not_me, node(), This}) + end, + {ok, SockOut} = gen_tcp:connect(Addr, Port, [inet]), + run_loop({SockIn, SockOut}, Remote, []); + _ -> + ok + end. + +run_loop(Sockets, RemoteNode, Buf0) -> + Block = [{RemoteNode, block}] =:= ets:lookup(?TABLE, RemoteNode), + receive + {tcp, Sock, Data} -> + Buf = [Data | Buf0], + case Block of + false -> gen_tcp:send(other(Sock, Sockets), lists:reverse(Buf)), + run_loop(Sockets, RemoteNode, []); + true -> run_loop(Sockets, RemoteNode, Buf) + end; + {tcp_closed, Sock} -> + gen_tcp:close(other(Sock, Sockets)); + X -> + exit({weirdness, X}) + end. + +other(A, {A, B}) -> B; +other(B, {A, B}) -> A. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/inet_tcp_proxy_manager.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/inet_tcp_proxy_manager.erl new file mode 100644 index 0000000..1bab158 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/inet_tcp_proxy_manager.erl @@ -0,0 +1,99 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% +-module(inet_tcp_proxy_manager). + +%% The TCP proxies need to decide whether to block based on the node +%% they're running on, and the node connecting to them. The trouble +%% is, they don't have an easy way to determine the latter. Therefore +%% when A connects to B we register the source port used by A here, so +%% that B can later look it up and find out who A is without having to +%% sniff the distribution protocol. +%% +%% That does unfortunately mean that we need a central control +%% thing. We assume here it's running on the node called +%% 'standalone_test' since that's where tests are orchestrated from. +%% +%% Yes, this leaks. For its intended lifecycle, that's fine. + +-behaviour(gen_server). + +-export([start_link/0, register/5, lookup/1]). + +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3]). + +-define(NODE, standalone_test). + +-record(state, {ports, pending}). + +start_link() -> + Node = node(), + Node = controller_node(), + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +register(_From, _To, _SrcPort, Port, Port) -> + %% No proxy, don't register + ok; +register(From, To, SrcPort, _Port, _ProxyPort) -> + gen_server:call(name(), {register, From, To, SrcPort}, infinity). + +lookup(SrcPort) -> + gen_server:call(name(), {lookup, SrcPort}, infinity). + +controller_node() -> + rabbit_nodes:make(atom_to_list(?NODE)). + +name() -> + {?MODULE, controller_node()}. + +%%---------------------------------------------------------------------------- + +init([]) -> + {ok, #state{ports = dict:new(), + pending = []}}. + +handle_call({register, FromNode, ToNode, SrcPort}, _From, + State = #state{ports = Ports, + pending = Pending}) -> + {Notify, Pending2} = + lists:partition(fun ({P, _}) -> P =:= SrcPort end, Pending), + [gen_server:reply(From, {ok, FromNode, ToNode}) || {_, From} <- Notify], + {reply, ok, + State#state{ports = dict:store(SrcPort, {FromNode, ToNode}, Ports), + pending = Pending2}}; + +handle_call({lookup, SrcPort}, From, + State = #state{ports = Ports, pending = Pending}) -> + case dict:find(SrcPort, Ports) of + {ok, {FromNode, ToNode}} -> + {reply, {ok, FromNode, ToNode}, State}; + error -> + {noreply, State#state{pending = [{SrcPort, From} | Pending]}} + end; + +handle_call(_Req, _From, State) -> + {reply, unknown_request, State}. + +handle_cast(_C, State) -> + {noreply, State}. + +handle_info(_I, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_, State, _) -> {ok, State}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbit_ha_test_consumer.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbit_ha_test_consumer.erl new file mode 100644 index 0000000..f11d8d4 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbit_ha_test_consumer.erl @@ -0,0 +1,114 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% +-module(rabbit_ha_test_consumer). + +-include_lib("amqp_client/include/amqp_client.hrl"). + +-export([await_response/1, create/5, start/6]). + +await_response(ConsumerPid) -> + case receive {ConsumerPid, Response} -> Response end of + {error, Reason} -> erlang:error(Reason); + ok -> ok + end. + +create(Channel, Queue, TestPid, CancelOnFailover, ExpectingMsgs) -> + ConsumerPid = spawn_link(?MODULE, start, + [TestPid, Channel, Queue, CancelOnFailover, + ExpectingMsgs + 1, ExpectingMsgs]), + amqp_channel:subscribe( + Channel, consume_method(Queue, CancelOnFailover), ConsumerPid), + ConsumerPid. + +start(TestPid, Channel, Queue, CancelOnFailover, LowestSeen, MsgsToConsume) -> + error_logger:info_msg("consumer ~p on ~p awaiting ~w messages " + "(lowest seen = ~w, cancel-on-failover = ~w)~n", + [self(), Channel, MsgsToConsume, LowestSeen, + CancelOnFailover]), + run(TestPid, Channel, Queue, CancelOnFailover, LowestSeen, MsgsToConsume). + +run(TestPid, _Channel, _Queue, _CancelOnFailover, _LowestSeen, 0) -> + consumer_reply(TestPid, ok); +run(TestPid, Channel, Queue, CancelOnFailover, LowestSeen, MsgsToConsume) -> + receive + #'basic.consume_ok'{} -> + run(TestPid, Channel, Queue, + CancelOnFailover, LowestSeen, MsgsToConsume); + {Delivery = #'basic.deliver'{ redelivered = Redelivered }, + #amqp_msg{payload = Payload}} -> + MsgNum = list_to_integer(binary_to_list(Payload)), + + ack(Delivery, Channel), + + %% we can receive any message we've already seen and, + %% because of the possibility of multiple requeuings, we + %% might see these messages in any order. If we are seeing + %% a message again, we don't decrement the MsgsToConsume + %% counter. + if + MsgNum + 1 == LowestSeen -> + run(TestPid, Channel, Queue, + CancelOnFailover, MsgNum, MsgsToConsume - 1); + MsgNum >= LowestSeen -> + error_logger:info_msg( + "consumer ~p on ~p ignoring redeliverd msg ~p~n", + [self(), Channel, MsgNum]), + true = Redelivered, %% ASSERTION + run(TestPid, Channel, Queue, + CancelOnFailover, LowestSeen, MsgsToConsume); + true -> + %% We received a message we haven't seen before, + %% but it is not the next message in the expected + %% sequence. + consumer_reply(TestPid, + {error, {unexpected_message, MsgNum}}) + end; + #'basic.cancel'{} when CancelOnFailover -> + error_logger:info_msg("consumer ~p on ~p received basic.cancel: " + "resubscribing to ~p on ~p~n", + [self(), Channel, Queue, Channel]), + resubscribe(TestPid, Channel, Queue, CancelOnFailover, + LowestSeen, MsgsToConsume); + #'basic.cancel'{} -> + exit(cancel_received_without_cancel_on_failover) + end. + +%% +%% Private API +%% + +resubscribe(TestPid, Channel, Queue, CancelOnFailover, LowestSeen, + MsgsToConsume) -> + amqp_channel:subscribe( + Channel, consume_method(Queue, CancelOnFailover), self()), + ok = receive #'basic.consume_ok'{} -> ok + end, + error_logger:info_msg("re-subscripting consumer ~p on ~p complete " + "(received basic.consume_ok)", + [self(), Channel]), + start(TestPid, Channel, Queue, CancelOnFailover, LowestSeen, MsgsToConsume). + +consume_method(Queue, CancelOnFailover) -> + Args = [{<<"x-cancel-on-ha-failover">>, bool, CancelOnFailover}], + #'basic.consume'{queue = Queue, + arguments = Args}. + +ack(#'basic.deliver'{delivery_tag = DeliveryTag}, Channel) -> + amqp_channel:call(Channel, #'basic.ack'{delivery_tag = DeliveryTag}), + ok. + +consumer_reply(TestPid, Reply) -> + TestPid ! {self(), Reply}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbit_ha_test_producer.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbit_ha_test_producer.erl new file mode 100644 index 0000000..f3070fe --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbit_ha_test_producer.erl @@ -0,0 +1,119 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% +-module(rabbit_ha_test_producer). + +-export([await_response/1, start/5, create/5]). + +-include_lib("amqp_client/include/amqp_client.hrl"). + +await_response(ProducerPid) -> + error_logger:info_msg("waiting for producer pid ~p~n", [ProducerPid]), + case receive {ProducerPid, Response} -> Response end of + ok -> ok; + {error, _} = Else -> exit(Else); + Else -> exit({weird_response, Else}) + end. + +create(Channel, Queue, TestPid, Confirm, MsgsToSend) -> + ProducerPid = spawn_link(?MODULE, start, [Channel, Queue, TestPid, + Confirm, MsgsToSend]), + receive + {ProducerPid, started} -> ProducerPid + end. + +start(Channel, Queue, TestPid, Confirm, MsgsToSend) -> + ConfirmState = + case Confirm of + true -> amqp_channel:register_confirm_handler(Channel, self()), + #'confirm.select_ok'{} = + amqp_channel:call(Channel, #'confirm.select'{}), + gb_trees:empty(); + false -> none + end, + TestPid ! {self(), started}, + error_logger:info_msg("publishing ~w msgs on ~p~n", [MsgsToSend, Channel]), + producer(Channel, Queue, TestPid, ConfirmState, MsgsToSend). + +%% +%% Private API +%% + +producer(_Channel, _Queue, TestPid, none, 0) -> + TestPid ! {self(), ok}; +producer(Channel, _Queue, TestPid, ConfirmState, 0) -> + error_logger:info_msg("awaiting confirms on channel ~p~n", [Channel]), + Msg = case drain_confirms(no_nacks, ConfirmState) of + no_nacks -> ok; + nacks -> {error, received_nacks}; + {Nacks, CS} -> {error, {missing_confirms, Nacks, + lists:sort(gb_trees:keys(CS))}} + end, + TestPid ! {self(), Msg}; + +producer(Channel, Queue, TestPid, ConfirmState, MsgsToSend) -> + Method = #'basic.publish'{exchange = <<"">>, + routing_key = Queue, + mandatory = false, + immediate = false}, + + ConfirmState1 = maybe_record_confirm(ConfirmState, Channel, MsgsToSend), + + amqp_channel:call(Channel, Method, + #amqp_msg{props = #'P_basic'{delivery_mode = 2}, + payload = list_to_binary( + integer_to_list(MsgsToSend))}), + + producer(Channel, Queue, TestPid, ConfirmState1, MsgsToSend - 1). + +maybe_record_confirm(none, _, _) -> + none; +maybe_record_confirm(ConfirmState, Channel, MsgsToSend) -> + SeqNo = amqp_channel:next_publish_seqno(Channel), + gb_trees:insert(SeqNo, MsgsToSend, ConfirmState). + +drain_confirms(Nacks, ConfirmState) -> + case gb_trees:is_empty(ConfirmState) of + true -> Nacks; + false -> receive + #'basic.ack'{delivery_tag = DeliveryTag, + multiple = IsMulti} -> + drain_confirms(Nacks, + delete_confirms(DeliveryTag, IsMulti, + ConfirmState)); + #'basic.nack'{delivery_tag = DeliveryTag, + multiple = IsMulti} -> + drain_confirms(nacks, + delete_confirms(DeliveryTag, IsMulti, + ConfirmState)) + after + 60000 -> {Nacks, ConfirmState} + end + end. + +delete_confirms(DeliveryTag, false, ConfirmState) -> + gb_trees:delete(DeliveryTag, ConfirmState); +delete_confirms(DeliveryTag, true, ConfirmState) -> + multi_confirm(DeliveryTag, ConfirmState). + +multi_confirm(DeliveryTag, ConfirmState) -> + case gb_trees:is_empty(ConfirmState) of + true -> ConfirmState; + false -> {Key, _, ConfirmState1} = gb_trees:take_smallest(ConfirmState), + case Key =< DeliveryTag of + true -> multi_confirm(DeliveryTag, ConfirmState1); + false -> ConfirmState + end + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbit_test_configs.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbit_test_configs.erl new file mode 100644 index 0000000..ce82e93 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbit_test_configs.erl @@ -0,0 +1,234 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% +-module(rabbit_test_configs). + +-include_lib("amqp_client/include/amqp_client.hrl"). + +-export([enable_plugins/1]). +-export([cluster/2, cluster_ab/1, cluster_abc/1, start_ab/1, start_abc/1]). +-export([start_connections/1, build_cluster/1]). +-export([ha_policy_all/1, ha_policy_two_pos/1]). +-export([start_nodes/2, start_nodes/3, add_to_cluster/2]). +-export([stop_nodes/1, start_node/1, stop_node/1, kill_node/1, restart_node/1, + execute/1]). +-export([cover_work_factor/2]). + +-import(rabbit_test_util, [set_ha_policy/3, set_ha_policy/4, a2b/1]). +-import(rabbit_misc, [pget/2]). + +-define(INITIAL_KEYS, [cover, base, server, plugins]). +-define(NON_RUNNING_KEYS, ?INITIAL_KEYS ++ [nodename, port]). + +cluster_ab(InitialCfg) -> cluster(InitialCfg, [a, b]). +cluster_abc(InitialCfg) -> cluster(InitialCfg, [a, b, c]). +start_ab(InitialCfg) -> start_nodes(InitialCfg, [a, b]). +start_abc(InitialCfg) -> start_nodes(InitialCfg, [a, b, c]). + +cluster(InitialCfg, NodeNames) -> + start_connections(build_cluster(start_nodes(InitialCfg, NodeNames))). + +start_nodes(InitialCfg, NodeNames) -> + start_nodes(InitialCfg, NodeNames, 5672). + +start_nodes(InitialCfg0, NodeNames, FirstPort) -> + {ok, Already0} = net_adm:names(), + Already = [list_to_atom(N) || {N, _P} <- Already0], + [check_node_not_running(Node, Already) || Node <- NodeNames], + Ports = lists:seq(FirstPort, length(NodeNames) + FirstPort - 1), + InitialCfgs = case InitialCfg0 of + [{_, _}|_] -> [InitialCfg0 || _ <- NodeNames]; + _ -> InitialCfg0 + end, + Nodes = [[{nodename, N}, {port, P} | strip_non_initial(Cfg)] + || {N, P, Cfg} <- lists:zip3(NodeNames, Ports, InitialCfgs)], + [start_node(Node) || Node <- Nodes]. + +check_node_not_running(Node, Already) -> + case lists:member(Node, Already) of + true -> exit({node_already_running, Node}); + false -> ok + end. + +strip_non_initial(Cfg) -> + [{K, V} || {K, V} <- Cfg, lists:member(K, ?INITIAL_KEYS)]. + +strip_running(Cfg) -> + [{K, V} || {K, V} <- Cfg, lists:member(K, ?NON_RUNNING_KEYS)]. + +enable_plugins(Cfg) -> enable_plugins(pget(plugins, Cfg), pget(server, Cfg)). + +enable_plugins(none, _Server) -> ok; +enable_plugins(Dir, Server) -> + Env = plugins_env(Dir), + R = execute(Env, Server ++ "/scripts/rabbitmq-plugins list -m"), + Plugins = string:tokens(R, "\n"), + [execute(Env, {Server ++ "/scripts/rabbitmq-plugins enable ~s", [Plugin]}) + || Plugin <- Plugins], + ok. + +plugins_env(none) -> + [{"RABBITMQ_ENABLED_PLUGINS_FILE", "/does-not-exist"}]; +plugins_env(Dir) -> + [{"RABBITMQ_PLUGINS_DIR", {"~s/plugins", [Dir]}}, + {"RABBITMQ_PLUGINS_EXPAND_DIR", {"~s/expand", [Dir]}}, + {"RABBITMQ_ENABLED_PLUGINS_FILE", {"~s/enabled_plugins", [Dir]}}]. + +start_node(Cfg) -> + Nodename = pget(nodename, Cfg), + Port = pget(port, Cfg), + Base = pget(base, Cfg), + Server = pget(server, Cfg), + PidFile = rabbit_misc:format("~s/~s.pid", [Base, Nodename]), + Linked = + execute_bg( + [{"RABBITMQ_MNESIA_BASE", {"~s/rabbitmq-~s-mnesia", [Base,Nodename]}}, + {"RABBITMQ_LOG_BASE", {"~s", [Base]}}, + {"RABBITMQ_NODENAME", {"~s", [Nodename]}}, + {"RABBITMQ_NODE_PORT", {"~B", [Port]}}, + {"RABBITMQ_PID_FILE", PidFile}, + {"RABBITMQ_CONFIG_FILE", "/some/path/which/does/not/exist"}, + {"RABBITMQ_ALLOW_INPUT", "1"}, %% Needed to make it close on our exit + %% Bit of a hack - only needed for mgmt tests. + {"RABBITMQ_SERVER_START_ARGS", + {"-rabbitmq_management listener [{port,1~B}]", [Port]}}, + {"RABBITMQ_SERVER_ERL_ARGS", + %% Next two lines are defaults + {"+K true +A30 +P 1048576 " + "-kernel inet_default_connect_options [{nodelay,true}] " + %% Some tests need to be able to make distribution unhappy + "-pa ~s/../rabbitmq-test/ebin " + "-proto_dist inet_proxy", [Server]}} + | plugins_env(pget(plugins, Cfg))], + Server ++ "/scripts/rabbitmq-server"), + execute({Server ++ "/scripts/rabbitmqctl -n ~s wait ~s", + [Nodename, PidFile]}), + Node = rabbit_nodes:make(Nodename), + OSPid = rpc:call(Node, os, getpid, []), + %% The cover system thinks all nodes with the same name are the + %% same node and will automaticaly re-establish cover as soon as + %% we see them, so we only want to start cover once per node name + %% for the entire test run. + case {pget(cover, Cfg), lists:member(Node, cover:which_nodes())} of + {true, false} -> cover:start([Node]); + _ -> ok + end, + [{node, Node}, + {pid_file, PidFile}, + {os_pid, OSPid}, + {linked_pid, Linked} | Cfg]. + +build_cluster([First | Rest]) -> + add_to_cluster([First], Rest). + +add_to_cluster([First | _] = Existing, New) -> + [cluster_with(First, Node) || Node <- New], + Existing ++ New. + +cluster_with(Cfg, NewCfg) -> + Node = pget(node, Cfg), + NewNodename = pget(nodename, NewCfg), + Server = pget(server, Cfg), + execute({Server ++ "/scripts/rabbitmqctl -n ~s stop_app", + [NewNodename]}), + execute({Server ++ "/scripts/rabbitmqctl -n ~s join_cluster ~s", + [NewNodename, Node]}), + execute({Server ++ "/scripts/rabbitmqctl -n ~s start_app", + [NewNodename]}). + +ha_policy_all([Cfg | _] = Cfgs) -> + set_ha_policy(Cfg, <<".*">>, <<"all">>), + Cfgs. + +ha_policy_two_pos([Cfg | _] = Cfgs) -> + Members = [a2b(pget(node, C)) || C <- Cfgs], + TwoNodes = [M || M <- lists:sublist(Members, 2)], + set_ha_policy(Cfg, <<"^ha.two.">>, {<<"nodes">>, TwoNodes}, []), + set_ha_policy(Cfg, <<"^ha.auto.">>, {<<"nodes">>, TwoNodes}, + [{<<"ha-sync-mode">>, <<"automatic">>}]), + Cfgs. + +start_connections(Nodes) -> [start_connection(Node) || Node <- Nodes]. + +start_connection(Cfg) -> + Port = pget(port, Cfg), + {ok, Conn} = amqp_connection:start(#amqp_params_network{port = Port}), + {ok, Ch} = amqp_connection:open_channel(Conn), + [{connection, Conn}, {channel, Ch} | Cfg]. + +stop_nodes(Nodes) -> [stop_node(Node) || Node <- Nodes]. + +stop_node(Cfg) -> + Server = pget(server, Cfg), + maybe_flush_cover(Cfg), + catch execute({Server ++ "/scripts/rabbitmqctl -n ~s stop ~s", + [pget(nodename, Cfg), pget(pid_file, Cfg)]}), + strip_running(Cfg). + +kill_node(Cfg) -> + maybe_flush_cover(Cfg), + catch execute({"kill -9 ~s", [pget(os_pid, Cfg)]}), + strip_running(Cfg). + +restart_node(Cfg) -> + start_node(stop_node(Cfg)). + +maybe_flush_cover(Cfg) -> + case pget(cover, Cfg) of + true -> cover:flush(pget(node, Cfg)); + false -> ok + end. + +%% Cover slows things down enough that if we are sending messages in +%% bulk, we want to send fewer or we'll be here all day... +cover_work_factor(Without, Cfg) -> + case pget(cover, Cfg) of + true -> trunc(Without * 0.1); + false -> Without + end. + +%%---------------------------------------------------------------------------- + +execute(Cmd) -> execute([], Cmd). + +execute(Env0, Cmd0) -> + Env = [{K, fmt(V)} || {K, V} <- Env0], + Cmd = fmt(Cmd0), + Port = erlang:open_port( + {spawn, "/usr/bin/env sh -c \"" ++ Cmd ++ "\""}, + [{env, Env}, exit_status, + stderr_to_stdout, use_stdio]), + port_receive_loop(Port, ""). + +port_receive_loop(Port, Stdout) -> + receive + {Port, {exit_status, 0}} -> Stdout; + {Port, {exit_status, 137}} -> Stdout; %% [0] + {Port, {exit_status, X}} -> exit({exit_status, X, Stdout}); + {Port, {data, Out}} -> %%io:format(user, "~s", [Out]), + port_receive_loop(Port, Stdout ++ Out) + end. + +%% [0] code 137 -> killed with SIGKILL which we do in some tests + +execute_bg(Env, Cmd) -> + spawn_link(fun () -> + execute(Env, Cmd), + {links, Links} = process_info(self(), links), + [unlink(L) || L <- Links] + end). + +fmt({Fmt, Args}) -> rabbit_misc:format(Fmt, Args); +fmt(Str) -> Str. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbit_test_runner.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbit_test_runner.erl new file mode 100644 index 0000000..7193704 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbit_test_runner.erl @@ -0,0 +1,214 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_test_runner). + +-include_lib("kernel/include/file.hrl"). + +-define(TIMEOUT, 600). + +-import(rabbit_misc, [pget/2]). + +-export([run_in_broker/2, run_multi/5]). + +run_in_broker(Dir, Filter) -> + io:format("~nIn-broker tests~n================~n~n", []), + eunit:test(make_tests_single(Dir, Filter, ?TIMEOUT), []). + +run_multi(ServerDir, Dir, Filter, Cover, PluginsDir) -> + io:format("~nMulti-node tests~n================~n~n", []), + %% Umbrella does not give us -sname + net_kernel:start([?MODULE, shortnames]), + inets:start(), %% Used by HTTP tests + error_logger:tty(false), + case Cover of + true -> io:format("Cover compiling..."), + cover:start(), + ok = rabbit_misc:enable_cover(["../rabbitmq-server/"]), + io:format(" done.~n~n"); + false -> ok + end, + R = eunit:test(make_tests_multi( + ServerDir, Dir, Filter, Cover, PluginsDir, ?TIMEOUT), []), + case Cover of + true -> io:format("~nCover reporting..."), + ok = rabbit_misc:report_cover(), + io:format(" done.~n~n"); + false -> ok + end, + R. + +make_tests_single(Dir, Filter, Timeout) -> + {Filtered, AllCount, Width} = find_tests(Dir, Filter, "_test"), + io:format("Running ~B of ~B tests; FILTER=~s~n~n", + [length(Filtered), AllCount, Filter]), + [make_test_single(M, FWith, F, ShowHeading, Timeout, Width) + || {M, FWith, F, ShowHeading} <- annotate_show_heading(Filtered)]. + +make_tests_multi(ServerDir, Dir, Filter, Cover, PluginsDir, Timeout) -> + {Filtered, AllCount, Width} = find_tests(Dir, Filter, "_with"), + io:format("Running ~B of ~B tests; FILTER=~s; COVER=~s~n~n", + [length(Filtered), AllCount, Filter, Cover]), + Cfg = [{cover, Cover}, + {base, basedir() ++ "/nodes"}, + {server, ServerDir}, + {plugins, PluginsDir}], + rabbit_test_configs:enable_plugins(Cfg), + [make_test_multi(M, FWith, F, ShowHeading, Timeout, Width, Cfg) + || {M, FWith, F, ShowHeading} <- annotate_show_heading(Filtered)]. + +find_tests(Dir, Filter, Suffix) -> + All = [{M, FWith, F} || + M <- modules(Dir), + {FWith, _Arity} <- proplists:get_value(exports, M:module_info()), + string:right(atom_to_list(FWith), length(Suffix)) =:= Suffix, + F <- [truncate_function_name(FWith, length(Suffix))]], + Filtered = [Test || {M, _FWith, F} = Test <- All, + should_run(M, F, Filter)], + Width = case Filtered of + [] -> 0; + _ -> lists:max([atom_length(F) || {_, _, F} <- Filtered]) + end, + {Filtered, length(All), Width}. + +make_test_single(M, FWith, F, ShowHeading, Timeout, Width) -> + {timeout, + Timeout, + fun () -> + maybe_print_heading(M, ShowHeading), + io:format(user, "~s [running]", [name(F, Width)]), + M:FWith(), + io:format(user, " [PASSED].~n", []) + end}. + +make_test_multi(M, FWith, F, ShowHeading, Timeout, Width, InitialCfg) -> + {setup, + fun () -> + maybe_print_heading(M, ShowHeading), + io:format(user, "~s [setup]", [name(F, Width)]), + setup_error_logger(M, F, basedir()), + recursive_delete(pget(base, InitialCfg)), + try + apply_config(M:FWith(), InitialCfg) + catch + error:{Type, Error, Cfg, Stack} -> + case Cfg of + InitialCfg -> ok; %% [0] + _ -> rabbit_test_configs:stop_nodes(Cfg) + end, + exit({Type, Error, Stack}) + end + end, + fun (Nodes) -> + rabbit_test_configs:stop_nodes(Nodes), + %% Partition tests change this, let's revert + net_kernel:set_net_ticktime(60, 1), + io:format(user, ".~n", []) + end, + fun (Nodes) -> + [{timeout, + Timeout, + fun () -> + [link(pget(linked_pid, N)) || N <- Nodes], + io:format(user, " [running]", []), + M:F(Nodes), + io:format(user, " [PASSED]", []) + end}] + end}. +%% [0] If we didn't get as far as starting any nodes then we only have +%% one proplist for initial config, not several per node. So avoid +%% trying to "stop" it - it won't work (and there's nothing to do +%% anyway). + +maybe_print_heading(M, true) -> + io:format(user, "~n~s~n~s~n", [M, string:chars($-, atom_length(M))]); +maybe_print_heading(_M, false) -> + ok. + +apply_config(Things, Cfg) when is_list(Things) -> + lists:foldl(fun apply_config/2, Cfg, Things); +apply_config(F, Cfg) when is_atom(F) -> + apply_config(fun (C) -> rabbit_test_configs:F(C) end, Cfg); +apply_config(F, Cfg) when is_function(F) -> + try + F(Cfg) + catch + Type:Error -> erlang:error({Type, Error, Cfg, erlang:get_stacktrace()}) + end. + +annotate_show_heading(List) -> + annotate_show_heading(List, undefined). + +annotate_show_heading([], _) -> + []; +annotate_show_heading([{M, FWith, F} | Rest], Current) -> + [{M, FWith, F, M =/= Current} | annotate_show_heading(Rest, M)]. + +setup_error_logger(M, F, Base) -> + case error_logger_logfile_filename() of + {error, no_log_file} -> ok; + _ -> ok = error_logger:logfile(close) + end, + FN = rabbit_misc:format("~s/~s:~s.log", [basedir(), M, F]), + ensure_dir(Base), + ok = error_logger:logfile({open, FN}). + +truncate_function_name(FWith, Length) -> + FName = atom_to_list(FWith), + list_to_atom(string:substr(FName, 1, length(FName) - Length)). + +should_run(_M, _F, "all") -> true; +should_run(M, F, Filter) -> MF = rabbit_misc:format("~s:~s", [M, F]), + case re:run(MF, Filter) of + {match, _} -> true; + nomatch -> false + end. + +ensure_dir(Path) -> + case file:read_file_info(Path) of + {ok, #file_info{type=regular}} -> exit({exists_as_file, Path}); + {ok, #file_info{type=directory}} -> ok; + _ -> file:make_dir(Path) + end. + +modules(RelDir) -> + {ok, Files} = file:list_dir(RelDir), + [M || F <- Files, + M <- case string:tokens(F, ".") of + [MStr, "beam"] -> [list_to_atom(MStr)]; + _ -> [] + end]. + +recursive_delete(Dir) -> + rabbit_test_configs:execute({"rm -rf ~s", [Dir]}). + +name(F, Width) -> + R = atom_to_list(F), + R ++ ":" ++ string:chars($ , Width - length(R)). + +atom_length(A) -> length(atom_to_list(A)). + +basedir() -> "/tmp/rabbitmq-multi-node". + +%% reimplement error_logger:logfile(filename) only using +%% gen_event:call/4 instead of gen_event:call/3 with our old friend +%% the 5 second timeout. Grr. +error_logger_logfile_filename() -> + case gen_event:call( + error_logger, error_logger_file_h, filename, infinity) of + {error,_} -> {error, no_log_file}; + Val -> Val + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbit_test_util.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbit_test_util.erl new file mode 100644 index 0000000..c8b0f9a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbit_test_util.erl @@ -0,0 +1,134 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% +-module(rabbit_test_util). + +-include_lib("amqp_client/include/amqp_client.hrl"). +-import(rabbit_misc, [pget/2]). + +-compile(export_all). + +set_ha_policy(Cfg, Pattern, Policy) -> + set_ha_policy(Cfg, Pattern, Policy, []). + +set_ha_policy(Cfg, Pattern, Policy, Extra) -> + set_policy(Cfg, Pattern, Pattern, <<"queues">>, ha_policy(Policy) ++ Extra). + +ha_policy(<<"all">>) -> [{<<"ha-mode">>, <<"all">>}]; +ha_policy({Mode, Params}) -> [{<<"ha-mode">>, Mode}, + {<<"ha-params">>, Params}]. + +set_policy(Cfg, Name, Pattern, ApplyTo, Definition) -> + ok = rpc:call(pget(node, Cfg), rabbit_policy, set, + [<<"/">>, Name, Pattern, Definition, 0, ApplyTo]). + +clear_policy(Cfg, Name) -> + ok = rpc:call(pget(node, Cfg), rabbit_policy, delete, [<<"/">>, Name]). + +set_param(Cfg, Component, Name, Value) -> + ok = rpc:call(pget(node, Cfg), rabbit_runtime_parameters, set, + [<<"/">>, Component, Name, Value, none]). + +clear_param(Cfg, Component, Name) -> + ok = rpc:call(pget(node, Cfg), rabbit_runtime_parameters, clear, + [<<"/">>, Component, Name]). + +control_action(Command, Cfg) -> + control_action(Command, Cfg, [], []). + +control_action(Command, Cfg, Args) -> + control_action(Command, Cfg, Args, []). + +control_action(Command, Cfg, Args, Opts) -> + Node = pget(node, Cfg), + rpc:call(Node, rabbit_control_main, action, + [Command, Node, Args, Opts, + fun (F, A) -> + error_logger:info_msg(F ++ "~n", A) + end]). + +restart_app(Cfg) -> + stop_app(Cfg), + start_app(Cfg). + +stop_app(Cfg) -> + control_action(stop_app, Cfg). + +start_app(Cfg) -> + control_action(start_app, Cfg). + +connect(Cfg) -> + Port = pget(port, Cfg), + {ok, Conn} = amqp_connection:start(#amqp_params_network{port = Port}), + {ok, Ch} = amqp_connection:open_channel(Conn), + {Conn, Ch}. + +%%---------------------------------------------------------------------------- + +kill_after(Time, Cfg, Method) -> + timer:sleep(Time), + kill(Cfg, Method). + +kill(Cfg, Method) -> + kill0(Cfg, Method), + wait_down(pget(node, Cfg)). + +kill0(Cfg, stop) -> rabbit_test_configs:stop_node(Cfg); +kill0(Cfg, sigkill) -> rabbit_test_configs:kill_node(Cfg). + +wait_down(Node) -> + case net_adm:ping(Node) of + pong -> timer:sleep(25), + wait_down(Node); + pang -> ok + end. + +a2b(A) -> list_to_binary(atom_to_list(A)). + +%%---------------------------------------------------------------------------- + +publish(Ch, QName, Count) -> + amqp_channel:call(Ch, #'confirm.select'{}), + [amqp_channel:call(Ch, + #'basic.publish'{routing_key = QName}, + #amqp_msg{props = #'P_basic'{delivery_mode = 2}, + payload = list_to_binary(integer_to_list(I))}) + || I <- lists:seq(1, Count)], + amqp_channel:wait_for_confirms(Ch). + +consume(Ch, QName, Count) -> + amqp_channel:subscribe(Ch, #'basic.consume'{queue = QName, no_ack = true}, + self()), + CTag = receive #'basic.consume_ok'{consumer_tag = C} -> C end, + [begin + Exp = list_to_binary(integer_to_list(I)), + receive {#'basic.deliver'{consumer_tag = CTag}, + #amqp_msg{payload = Exp}} -> + ok + after 500 -> + exit(timeout) + end + end|| I <- lists:seq(1, Count)], + #'queue.declare_ok'{message_count = 0} + = amqp_channel:call(Ch, #'queue.declare'{queue = QName, + durable = true}), + amqp_channel:call(Ch, #'basic.cancel'{consumer_tag = CTag}), + ok. + +fetch(Ch, QName, Count) -> + [{#'basic.get_ok'{}, _} = + amqp_channel:call(Ch, #'basic.get'{queue = QName}) || + _ <- lists:seq(1, Count)], + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbitmq_test.app.src b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbitmq_test.app.src new file mode 100644 index 0000000..108f874 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/src/rabbitmq_test.app.src @@ -0,0 +1,11 @@ +{application, rabbitmq_test, + [ + {description, ""}, + {vsn, "1"}, + {registered, []}, + {applications, [ + kernel, + stdlib + ]}, + {env, []} + ]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/clustering_management.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/clustering_management.erl new file mode 100644 index 0000000..43eb06b --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/clustering_management.erl @@ -0,0 +1,447 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% +-module(clustering_management). + +-compile(export_all). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +-import(rabbit_misc, [pget/2]). + +-define(LOOP_RECURSION_DELAY, 100). + +join_and_part_cluster_with() -> start_abc. +join_and_part_cluster(Config) -> + [Rabbit, Hare, Bunny] = cluster_members(Config), + assert_not_clustered(Rabbit), + assert_not_clustered(Hare), + assert_not_clustered(Bunny), + + stop_join_start(Rabbit, Bunny), + assert_clustered([Rabbit, Bunny]), + + stop_join_start(Hare, Bunny, true), + assert_cluster_status( + {[Bunny, Hare, Rabbit], [Bunny, Rabbit], [Bunny, Hare, Rabbit]}, + [Rabbit, Hare, Bunny]), + + %% Allow clustering with already clustered node + ok = stop_app(Rabbit), + {ok, already_member} = join_cluster(Rabbit, Hare), + ok = start_app(Rabbit), + + stop_reset_start(Rabbit), + assert_not_clustered(Rabbit), + assert_cluster_status({[Bunny, Hare], [Bunny], [Bunny, Hare]}, + [Hare, Bunny]), + + stop_reset_start(Hare), + assert_not_clustered(Hare), + assert_not_clustered(Bunny). + +join_cluster_bad_operations_with() -> start_abc. +join_cluster_bad_operations(Config) -> + [Rabbit, Hare, Bunny] = cluster_members(Config), + + %% Non-existant node + ok = stop_app(Rabbit), + assert_failure(fun () -> join_cluster(Rabbit, non@existant) end), + ok = start_app(Rabbit), + assert_not_clustered(Rabbit), + + %% Trying to cluster with mnesia running + assert_failure(fun () -> join_cluster(Rabbit, Bunny) end), + assert_not_clustered(Rabbit), + + %% Trying to cluster the node with itself + ok = stop_app(Rabbit), + assert_failure(fun () -> join_cluster(Rabbit, Rabbit) end), + ok = start_app(Rabbit), + assert_not_clustered(Rabbit), + + %% Do not let the node leave the cluster or reset if it's the only + %% ram node + stop_join_start(Hare, Rabbit, true), + assert_cluster_status({[Rabbit, Hare], [Rabbit], [Rabbit, Hare]}, + [Rabbit, Hare]), + ok = stop_app(Hare), + assert_failure(fun () -> join_cluster(Rabbit, Bunny) end), + assert_failure(fun () -> reset(Rabbit) end), + ok = start_app(Hare), + assert_cluster_status({[Rabbit, Hare], [Rabbit], [Rabbit, Hare]}, + [Rabbit, Hare]), + + %% Cannot start RAM-only node first + ok = stop_app(Rabbit), + ok = stop_app(Hare), + assert_failure(fun () -> start_app(Hare) end), + ok = start_app(Rabbit), + ok = start_app(Hare), + ok. + +%% This tests that the nodes in the cluster are notified immediately of a node +%% join, and not just after the app is started. +join_to_start_interval_with() -> start_abc. +join_to_start_interval(Config) -> + [Rabbit, Hare, _Bunny] = cluster_members(Config), + + ok = stop_app(Rabbit), + ok = join_cluster(Rabbit, Hare), + assert_cluster_status({[Rabbit, Hare], [Rabbit, Hare], [Hare]}, + [Rabbit, Hare]), + ok = start_app(Rabbit), + assert_clustered([Rabbit, Hare]). + +forget_cluster_node_with() -> start_abc. +forget_cluster_node(Config) -> + [Rabbit, Hare, Bunny] = cluster_members(Config), + + %% Trying to remove a node not in the cluster should fail + assert_failure(fun () -> forget_cluster_node(Hare, Rabbit) end), + + stop_join_start(Rabbit, Hare), + assert_clustered([Rabbit, Hare]), + + %% Trying to remove an online node should fail + assert_failure(fun () -> forget_cluster_node(Hare, Rabbit) end), + + ok = stop_app(Rabbit), + %% We're passing the --offline flag, but Hare is online + assert_failure(fun () -> forget_cluster_node(Hare, Rabbit, true) end), + %% Removing some non-existant node will fail + assert_failure(fun () -> forget_cluster_node(Hare, non@existant) end), + ok = forget_cluster_node(Hare, Rabbit), + assert_not_clustered(Hare), + assert_cluster_status({[Rabbit, Hare], [Rabbit, Hare], [Hare]}, + [Rabbit]), + + %% Now we can't start Rabbit since it thinks that it's still in the cluster + %% with Hare, while Hare disagrees. + assert_failure(fun () -> start_app(Rabbit) end), + + ok = reset(Rabbit), + ok = start_app(Rabbit), + assert_not_clustered(Rabbit), + + %% Now we remove Rabbit from an offline node. + stop_join_start(Bunny, Hare), + stop_join_start(Rabbit, Hare), + assert_clustered([Rabbit, Hare, Bunny]), + ok = stop_app(Hare), + ok = stop_app(Rabbit), + ok = stop_app(Bunny), + %% This is fine but we need the flag + assert_failure(fun () -> forget_cluster_node(Hare, Bunny) end), + %% Hare was not the second-to-last to go down + ok = forget_cluster_node(Hare, Bunny, true), + ok = start_app(Hare), + ok = start_app(Rabbit), + %% Bunny still thinks its clustered with Rabbit and Hare + assert_failure(fun () -> start_app(Bunny) end), + ok = reset(Bunny), + ok = start_app(Bunny), + assert_not_clustered(Bunny), + assert_clustered([Rabbit, Hare]). + +forget_cluster_node_removes_things_with() -> start_abc. +forget_cluster_node_removes_things([RabbitCfg, HareCfg, _BunnyCfg] = Config) -> + [Rabbit, Hare, _Bunny] = cluster_members(Config), + stop_join_start(Rabbit, Hare), + {_RConn, RCh} = rabbit_test_util:connect(RabbitCfg), + #'queue.declare_ok'{} = + amqp_channel:call(RCh, #'queue.declare'{queue = <<"test">>, + durable = true}), + + ok = stop_app(Rabbit), + + {_HConn, HCh} = rabbit_test_util:connect(HareCfg), + {'EXIT',{{shutdown,{server_initiated_close,404,_}}, _}} = + (catch amqp_channel:call(HCh, #'queue.declare'{queue = <<"test">>, + durable = true})), + + ok = forget_cluster_node(Hare, Rabbit), + + {_HConn2, HCh2} = rabbit_test_util:connect(HareCfg), + #'queue.declare_ok'{} = + amqp_channel:call(HCh2, #'queue.declare'{queue = <<"test">>, + durable = true}), + ok. + +change_cluster_node_type_with() -> start_abc. +change_cluster_node_type(Config) -> + [Rabbit, Hare, _Bunny] = cluster_members(Config), + + %% Trying to change the ram node when not clustered should always fail + ok = stop_app(Rabbit), + assert_failure(fun () -> change_cluster_node_type(Rabbit, ram) end), + assert_failure(fun () -> change_cluster_node_type(Rabbit, disc) end), + ok = start_app(Rabbit), + + ok = stop_app(Rabbit), + join_cluster(Rabbit, Hare), + assert_cluster_status({[Rabbit, Hare], [Rabbit, Hare], [Hare]}, + [Rabbit, Hare]), + change_cluster_node_type(Rabbit, ram), + assert_cluster_status({[Rabbit, Hare], [Hare], [Hare]}, + [Rabbit, Hare]), + change_cluster_node_type(Rabbit, disc), + assert_cluster_status({[Rabbit, Hare], [Rabbit, Hare], [Hare]}, + [Rabbit, Hare]), + change_cluster_node_type(Rabbit, ram), + ok = start_app(Rabbit), + assert_cluster_status({[Rabbit, Hare], [Hare], [Hare, Rabbit]}, + [Rabbit, Hare]), + + %% Changing to ram when you're the only ram node should fail + ok = stop_app(Hare), + assert_failure(fun () -> change_cluster_node_type(Hare, ram) end), + ok = start_app(Hare). + +change_cluster_when_node_offline_with() -> start_abc. +change_cluster_when_node_offline(Config) -> + [Rabbit, Hare, Bunny] = cluster_members(Config), + + %% Cluster the three notes + stop_join_start(Rabbit, Hare), + assert_clustered([Rabbit, Hare]), + + stop_join_start(Bunny, Hare), + assert_clustered([Rabbit, Hare, Bunny]), + + %% Bring down Rabbit, and remove Bunny from the cluster while + %% Rabbit is offline + ok = stop_app(Rabbit), + ok = stop_app(Bunny), + ok = reset(Bunny), + assert_cluster_status({[Bunny], [Bunny], []}, [Bunny]), + assert_cluster_status({[Rabbit, Hare], [Rabbit, Hare], [Hare]}, [Hare]), + assert_cluster_status( + {[Rabbit, Hare, Bunny], [Rabbit, Hare, Bunny], [Hare, Bunny]}, [Rabbit]), + + %% Bring Rabbit back up + ok = start_app(Rabbit), + assert_clustered([Rabbit, Hare]), + ok = start_app(Bunny), + assert_not_clustered(Bunny), + + %% Now the same, but Rabbit is a RAM node, and we bring up Bunny + %% before + ok = stop_app(Rabbit), + ok = change_cluster_node_type(Rabbit, ram), + ok = start_app(Rabbit), + stop_join_start(Bunny, Hare), + assert_cluster_status( + {[Rabbit, Hare, Bunny], [Hare, Bunny], [Rabbit, Hare, Bunny]}, + [Rabbit, Hare, Bunny]), + ok = stop_app(Rabbit), + ok = stop_app(Bunny), + ok = reset(Bunny), + ok = start_app(Bunny), + assert_not_clustered(Bunny), + assert_cluster_status({[Rabbit, Hare], [Hare], [Hare]}, [Hare]), + assert_cluster_status( + {[Rabbit, Hare, Bunny], [Hare, Bunny], [Hare, Bunny]}, + [Rabbit]), + ok = start_app(Rabbit), + assert_cluster_status({[Rabbit, Hare], [Hare], [Rabbit, Hare]}, + [Rabbit, Hare]), + assert_not_clustered(Bunny). + +update_cluster_nodes_test_with() -> start_abc. +update_cluster_nodes_test(Config) -> + [Rabbit, Hare, Bunny] = cluster_members(Config), + + %% Mnesia is running... + assert_failure(fun () -> update_cluster_nodes(Rabbit, Hare) end), + + ok = stop_app(Rabbit), + ok = join_cluster(Rabbit, Hare), + ok = stop_app(Bunny), + ok = join_cluster(Bunny, Hare), + ok = start_app(Bunny), + stop_reset_start(Hare), + assert_failure(fun () -> start_app(Rabbit) end), + %% Bogus node + assert_failure(fun () -> update_cluster_nodes(Rabbit, non@existant) end), + %% Inconsisent node + assert_failure(fun () -> update_cluster_nodes(Rabbit, Hare) end), + ok = update_cluster_nodes(Rabbit, Bunny), + ok = start_app(Rabbit), + assert_not_clustered(Hare), + assert_clustered([Rabbit, Bunny]). + +erlang_config_with() -> start_abc. +erlang_config(Config) -> + [Rabbit, Hare, _Bunny] = cluster_members(Config), + + ok = stop_app(Hare), + ok = reset(Hare), + ok = rpc:call(Hare, application, set_env, + [rabbit, cluster_nodes, {[Rabbit], disc}]), + ok = start_app(Hare), + assert_clustered([Rabbit, Hare]), + + ok = stop_app(Hare), + ok = reset(Hare), + ok = rpc:call(Hare, application, set_env, + [rabbit, cluster_nodes, {[Rabbit], ram}]), + ok = start_app(Hare), + assert_cluster_status({[Rabbit, Hare], [Rabbit], [Rabbit, Hare]}, + [Rabbit, Hare]), + + %% We get a warning but we start anyway + ok = stop_app(Hare), + ok = reset(Hare), + ok = rpc:call(Hare, application, set_env, + [rabbit, cluster_nodes, {[non@existent], disc}]), + ok = start_app(Hare), + assert_not_clustered(Hare), + assert_not_clustered(Rabbit), + + %% If we use a legacy config file, it still works (and a warning is emitted) + ok = stop_app(Hare), + ok = reset(Hare), + ok = rpc:call(Hare, application, set_env, + [rabbit, cluster_nodes, [Rabbit]]), + ok = start_app(Hare), + assert_cluster_status({[Rabbit, Hare], [Rabbit], [Rabbit, Hare]}, + [Rabbit, Hare]). + +force_reset_test_with() -> start_abc. +force_reset_test(Config) -> + [Rabbit, Hare, _Bunny] = cluster_members(Config), + + stop_join_start(Rabbit, Hare), + stop_app(Rabbit), + force_reset(Rabbit), + %% Hare thinks that Rabbit is still clustered + assert_cluster_status({[Rabbit, Hare], [Rabbit, Hare], [Hare]}, + [Hare]), + %% %% ...but it isn't + assert_cluster_status({[Rabbit], [Rabbit], []}, [Rabbit]), + %% We can rejoin Rabbit and Hare + update_cluster_nodes(Rabbit, Hare), + start_app(Rabbit), + assert_clustered([Rabbit, Hare]). + +%% ---------------------------------------------------------------------------- +%% Internal utils + +cluster_members(Nodes) -> [pget(node,Cfg) || Cfg <- Nodes]. + +assert_cluster_status(Status0, Nodes) -> + Status = {AllNodes, _, _} = sort_cluster_status(Status0), + wait_for_cluster_status(Status, AllNodes, Nodes). + +wait_for_cluster_status(Status, AllNodes, Nodes) -> + Max = 10000 / ?LOOP_RECURSION_DELAY, + wait_for_cluster_status(0, Max, Status, AllNodes, Nodes). + +wait_for_cluster_status(N, Max, Status, _AllNodes, Nodes) when N >= Max -> + error({cluster_status_max_tries_failed, + [{nodes, Nodes}, + {expected_status, Status}, + {max_tried, Max}]}); +wait_for_cluster_status(N, Max, Status, AllNodes, Nodes) -> + case lists:all(fun (Node) -> + verify_status_equal(Node, Status, AllNodes) + end, Nodes) of + true -> ok; + false -> timer:sleep(?LOOP_RECURSION_DELAY), + wait_for_cluster_status(N + 1, Max, Status, AllNodes, Nodes) + end. + +verify_status_equal(Node, Status, AllNodes) -> + NodeStatus = sort_cluster_status(cluster_status(Node)), + (AllNodes =/= [Node]) =:= rpc:call(Node, rabbit_mnesia, is_clustered, []) + andalso NodeStatus =:= Status. + +cluster_status(Node) -> + {rpc:call(Node, rabbit_mnesia, cluster_nodes, [all]), + rpc:call(Node, rabbit_mnesia, cluster_nodes, [disc]), + rpc:call(Node, rabbit_mnesia, cluster_nodes, [running])}. + +sort_cluster_status({All, Disc, Running}) -> + {lists:sort(All), lists:sort(Disc), lists:sort(Running)}. + +assert_clustered(Nodes) -> + assert_cluster_status({Nodes, Nodes, Nodes}, Nodes). + +assert_not_clustered(Node) -> + assert_cluster_status({[Node], [Node], [Node]}, [Node]). + +assert_failure(Fun) -> + case catch Fun() of + {error, Reason} -> Reason; + {badrpc, {'EXIT', Reason}} -> Reason; + {badrpc_multi, Reason, _Nodes} -> Reason; + Other -> exit({expected_failure, Other}) + end. + +stop_app(Node) -> + control_action(stop_app, Node). + +start_app(Node) -> + control_action(start_app, Node). + +join_cluster(Node, To) -> + join_cluster(Node, To, false). + +join_cluster(Node, To, Ram) -> + control_action(join_cluster, Node, [atom_to_list(To)], [{"--ram", Ram}]). + +reset(Node) -> + control_action(reset, Node). + +force_reset(Node) -> + control_action(force_reset, Node). + +forget_cluster_node(Node, Removee, RemoveWhenOffline) -> + control_action(forget_cluster_node, Node, [atom_to_list(Removee)], + [{"--offline", RemoveWhenOffline}]). + +forget_cluster_node(Node, Removee) -> + forget_cluster_node(Node, Removee, false). + +change_cluster_node_type(Node, Type) -> + control_action(change_cluster_node_type, Node, [atom_to_list(Type)]). + +update_cluster_nodes(Node, DiscoveryNode) -> + control_action(update_cluster_nodes, Node, [atom_to_list(DiscoveryNode)]). + +stop_join_start(Node, ClusterTo, Ram) -> + ok = stop_app(Node), + ok = join_cluster(Node, ClusterTo, Ram), + ok = start_app(Node). + +stop_join_start(Node, ClusterTo) -> + stop_join_start(Node, ClusterTo, false). + +stop_reset_start(Node) -> + ok = stop_app(Node), + ok = reset(Node), + ok = start_app(Node). + +control_action(Command, Node) -> + control_action(Command, Node, [], []). + +control_action(Command, Node, Args) -> + control_action(Command, Node, Args, []). + +control_action(Command, Node, Args, Opts) -> + rpc:call(Node, rabbit_control_main, action, + [Command, Node, Args, Opts, + fun io:format/2]). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/dynamic_ha.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/dynamic_ha.erl new file mode 100644 index 0000000..8be3eeb --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/dynamic_ha.erl @@ -0,0 +1,220 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% +-module(dynamic_ha). + +%% rabbit_tests:test_dynamic_mirroring() is a unit test which should +%% test the logic of what all the policies decide to do, so we don't +%% need to exhaustively test that here. What we need to test is that: +%% +%% * Going from non-mirrored to mirrored works and vice versa +%% * Changing policy can add / remove mirrors and change the master +%% * Adding a node will create a new mirror when there are not enough nodes +%% for the policy +%% * Removing a node will not create a new mirror even if the policy +%% logic wants it (since this gives us a good way to lose messages +%% on cluster shutdown, by repeated failover to new nodes) +%% +%% The first two are change_policy, the last two are change_cluster + +-compile(export_all). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +-define(QNAME, <<"ha.test">>). +-define(POLICY, <<"^ha.test$">>). %% " emacs +-define(VHOST, <<"/">>). + +-import(rabbit_test_util, [set_ha_policy/3, set_ha_policy/4, + clear_policy/2, a2b/1, publish/3, consume/3]). +-import(rabbit_misc, [pget/2]). + +change_policy_with() -> cluster_abc. +change_policy([CfgA, _CfgB, _CfgC] = Cfgs) -> + ACh = pget(channel, CfgA), + [A, B, C] = [pget(node, Cfg) || Cfg <- Cfgs], + + %% When we first declare a queue with no policy, it's not HA. + amqp_channel:call(ACh, #'queue.declare'{queue = ?QNAME}), + assert_slaves(A, ?QNAME, {A, ''}), + + %% Give it policy "all", it becomes HA and gets all mirrors + set_ha_policy(CfgA, ?POLICY, <<"all">>), + assert_slaves(A, ?QNAME, {A, [B, C]}), + + %% Give it policy "nodes", it gets specific mirrors + set_ha_policy(CfgA, ?POLICY, {<<"nodes">>, [a2b(A), a2b(B)]}), + assert_slaves(A, ?QNAME, {A, [B]}), + + %% Now explicitly change the mirrors + set_ha_policy(CfgA, ?POLICY, {<<"nodes">>, [a2b(A), a2b(C)]}), + assert_slaves(A, ?QNAME, {A, [C]}, [{A, [B, C]}]), + + %% Clear the policy, and we go back to non-mirrored + clear_policy(CfgA, ?POLICY), + assert_slaves(A, ?QNAME, {A, ''}), + + %% Test switching "away" from an unmirrored node + set_ha_policy(CfgA, ?POLICY, {<<"nodes">>, [a2b(B), a2b(C)]}), + assert_slaves(A, ?QNAME, {A, [B, C]}, [{A, [B]}, {A, [C]}]), + + ok. + +change_cluster_with() -> cluster_abc. +change_cluster([CfgA, _CfgB, _CfgC] = CfgsABC) -> + ACh = pget(channel, CfgA), + [A, B, C] = [pget(node, Cfg) || Cfg <- CfgsABC], + + amqp_channel:call(ACh, #'queue.declare'{queue = ?QNAME}), + assert_slaves(A, ?QNAME, {A, ''}), + + %% Give it policy exactly 4, it should mirror to all 3 nodes + set_ha_policy(CfgA, ?POLICY, {<<"exactly">>, 4}), + assert_slaves(A, ?QNAME, {A, [B, C]}), + + %% Add D and E, D joins in + [CfgD, CfgE] = CfgsDE = rabbit_test_configs:start_nodes(CfgA, [d, e], 5675), + D = pget(node, CfgD), + rabbit_test_configs:add_to_cluster(CfgsABC, CfgsDE), + assert_slaves(A, ?QNAME, {A, [B, C, D]}), + + %% Remove D, E does not join in + rabbit_test_configs:stop_node(CfgD), + assert_slaves(A, ?QNAME, {A, [B, C]}), + + %% Clean up since we started this by hand + rabbit_test_configs:stop_node(CfgE), + ok. + +rapid_change_with() -> cluster_abc. +rapid_change([CfgA, _CfgB, _CfgC]) -> + ACh = pget(channel, CfgA), + Self = self(), + spawn_link( + fun() -> + [rapid_amqp_ops(ACh, I) || I <- lists:seq(1, 100)], + Self ! done + end), + rapid_loop(CfgA), + ok. + +rapid_amqp_ops(Ch, I) -> + Payload = list_to_binary(integer_to_list(I)), + amqp_channel:call(Ch, #'queue.declare'{queue = ?QNAME}), + amqp_channel:cast(Ch, #'basic.publish'{exchange = <<"">>, + routing_key = ?QNAME}, + #amqp_msg{payload = Payload}), + amqp_channel:subscribe(Ch, #'basic.consume'{queue = ?QNAME, + no_ack = true}, self()), + receive #'basic.consume_ok'{} -> ok + end, + receive {#'basic.deliver'{}, #amqp_msg{payload = Payload}} -> + ok + end, + amqp_channel:call(Ch, #'queue.delete'{queue = ?QNAME}). + +rapid_loop(Cfg) -> + receive done -> + ok + after 0 -> + set_ha_policy(Cfg, ?POLICY, <<"all">>), + clear_policy(Cfg, ?POLICY), + rapid_loop(Cfg) + end. + +%% Vhost deletion needs to successfully tear down policies and queues +%% with policies. At least smoke-test that it doesn't blow up. +vhost_deletion_with() -> [cluster_ab, ha_policy_all]. +vhost_deletion([CfgA, _CfgB]) -> + ACh = pget(channel, CfgA), + Node = pget(node, CfgA), + amqp_channel:call(ACh, #'queue.declare'{queue = <<"test">>}), + ok = rpc:call(Node, rabbit_vhost, delete, [<<"/">>]), + ok. + +%%---------------------------------------------------------------------------- + +assert_slaves(RPCNode, QName, Exp) -> + assert_slaves(RPCNode, QName, Exp, []). + +assert_slaves(RPCNode, QName, Exp, PermittedIntermediate) -> + assert_slaves0(RPCNode, QName, Exp, + [{get(previous_exp_m_node), get(previous_exp_s_nodes)} | + PermittedIntermediate]). + +assert_slaves0(RPCNode, QName, {ExpMNode, ExpSNodes}, PermittedIntermediate) -> + Q = find_queue(QName, RPCNode), + Pid = proplists:get_value(pid, Q), + SPids = proplists:get_value(slave_pids, Q), + ActMNode = node(Pid), + ActSNodes = case SPids of + '' -> ''; + _ -> [node(SPid) || SPid <- SPids] + end, + case ExpMNode =:= ActMNode andalso equal_list(ExpSNodes, ActSNodes) of + false -> + %% It's an async change, so if nothing has changed let's + %% just wait - of course this means if something does not + %% change when expected then we time out the test which is + %% a bit tedious + case [found || {PermMNode, PermSNodes} <- PermittedIntermediate, + PermMNode =:= ActMNode, + equal_list(PermSNodes, ActSNodes)] of + [] -> ct:fail("Expected ~p / ~p, got ~p / ~p~nat ~p~n", + [ExpMNode, ExpSNodes, ActMNode, ActSNodes, + get_stacktrace()]); + _ -> timer:sleep(100), + assert_slaves0(RPCNode, QName, {ExpMNode, ExpSNodes}, + PermittedIntermediate) + end; + true -> + put(previous_exp_m_node, ExpMNode), + put(previous_exp_s_nodes, ExpSNodes), + ok + end. + +equal_list('', '') -> true; +equal_list('', _Act) -> false; +equal_list(_Exp, '') -> false; +equal_list([], []) -> true; +equal_list(_Exp, []) -> false; +equal_list([], _Act) -> false; +equal_list([H|T], Act) -> case lists:member(H, Act) of + true -> equal_list(T, Act -- [H]); + false -> false + end. + +find_queue(QName, RPCNode) -> + Qs = rpc:call(RPCNode, rabbit_amqqueue, info_all, [?VHOST], infinity), + case find_queue0(QName, Qs) of + did_not_find_queue -> timer:sleep(100), + find_queue(QName, RPCNode); + Q -> Q + end. + +find_queue0(QName, Qs) -> + case [Q || Q <- Qs, proplists:get_value(name, Q) =:= + rabbit_misc:r(?VHOST, queue, QName)] of + [R] -> R; + [] -> did_not_find_queue + end. + +get_stacktrace() -> + try + throw(e) + catch + _:e -> + erlang:get_stacktrace() + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/eager_sync.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/eager_sync.erl new file mode 100644 index 0000000..9c2d935 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/eager_sync.erl @@ -0,0 +1,205 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% +-module(eager_sync). + +-compile(export_all). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +-define(QNAME, <<"ha.two.test">>). +-define(QNAME_AUTO, <<"ha.auto.test">>). +-define(MESSAGE_COUNT, 2000). + +-import(rabbit_test_util, [a2b/1, publish/3, consume/3, fetch/3]). +-import(rabbit_misc, [pget/2]). + +-define(CONFIG, [cluster_abc, ha_policy_two_pos]). + +eager_sync_with() -> ?CONFIG. +eager_sync([A, B, C]) -> + %% Queue is on AB but not C. + ACh = pget(channel, A), + Ch = pget(channel, C), + amqp_channel:call(ACh, #'queue.declare'{queue = ?QNAME, + durable = true}), + + %% Don't sync, lose messages + publish(Ch, ?QNAME, ?MESSAGE_COUNT), + restart(A), + restart(B), + consume(Ch, ?QNAME, 0), + + %% Sync, keep messages + publish(Ch, ?QNAME, ?MESSAGE_COUNT), + restart(A), + ok = sync(C, ?QNAME), + restart(B), + consume(Ch, ?QNAME, ?MESSAGE_COUNT), + + %% Check the no-need-to-sync path + publish(Ch, ?QNAME, ?MESSAGE_COUNT), + ok = sync(C, ?QNAME), + consume(Ch, ?QNAME, ?MESSAGE_COUNT), + + %% keep unacknowledged messages + publish(Ch, ?QNAME, ?MESSAGE_COUNT), + fetch(Ch, ?QNAME, 2), + restart(A), + fetch(Ch, ?QNAME, 3), + sync(C, ?QNAME), + restart(B), + consume(Ch, ?QNAME, ?MESSAGE_COUNT), + + ok. + +eager_sync_cancel_with() -> ?CONFIG. +eager_sync_cancel([A, B, C]) -> + %% Queue is on AB but not C. + ACh = pget(channel, A), + Ch = pget(channel, C), + + amqp_channel:call(ACh, #'queue.declare'{queue = ?QNAME, + durable = true}), + {ok, not_syncing} = sync_cancel(C, ?QNAME), %% Idempotence + eager_sync_cancel_test2(A, B, C, Ch). + +eager_sync_cancel_test2(A, B, C, Ch) -> + %% Sync then cancel + publish(Ch, ?QNAME, ?MESSAGE_COUNT), + restart(A), + spawn_link(fun() -> ok = sync_nowait(C, ?QNAME) end), + case wait_for_syncing(C, ?QNAME, 1) of + ok -> + case sync_cancel(C, ?QNAME) of + ok -> + wait_for_running(C, ?QNAME), + restart(B), + consume(Ch, ?QNAME, 0), + + {ok, not_syncing} = sync_cancel(C, ?QNAME), %% Idempotence + ok; + {ok, not_syncing} -> + %% Damn. Syncing finished between wait_for_syncing/3 and + %% sync_cancel/2 above. Start again. + amqp_channel:call(Ch, #'queue.purge'{queue = ?QNAME}), + eager_sync_cancel_test2(A, B, C, Ch) + end; + synced_already -> + %% Damn. Syncing finished before wait_for_syncing/3. Start again. + amqp_channel:call(Ch, #'queue.purge'{queue = ?QNAME}), + eager_sync_cancel_test2(A, B, C, Ch) + end. + +eager_sync_auto_with() -> ?CONFIG. +eager_sync_auto([A, B, C]) -> + ACh = pget(channel, A), + Ch = pget(channel, C), + amqp_channel:call(ACh, #'queue.declare'{queue = ?QNAME_AUTO, + durable = true}), + + %% Sync automatically, don't lose messages + publish(Ch, ?QNAME_AUTO, ?MESSAGE_COUNT), + restart(A), + wait_for_sync(C, ?QNAME_AUTO), + restart(B), + wait_for_sync(C, ?QNAME_AUTO), + consume(Ch, ?QNAME_AUTO, ?MESSAGE_COUNT), + + ok. + +eager_sync_auto_on_policy_change_with() -> ?CONFIG. +eager_sync_auto_on_policy_change([A, B, C]) -> + ACh = pget(channel, A), + Ch = pget(channel, C), + amqp_channel:call(ACh, #'queue.declare'{queue = ?QNAME, + durable = true}), + + %% Sync automatically once the policy is changed to tell us to. + publish(Ch, ?QNAME, ?MESSAGE_COUNT), + restart(A), + Params = [a2b(pget(node, Cfg)) || Cfg <- [A, B]], + rabbit_test_util:set_ha_policy( + A, <<"^ha.two.">>, {<<"nodes">>, Params}, + [{<<"ha-sync-mode">>, <<"automatic">>}]), + wait_for_sync(C, ?QNAME), + + ok. + +eager_sync_requeue_with() -> ?CONFIG. +eager_sync_requeue([A, B, C]) -> + %% Queue is on AB but not C. + ACh = pget(channel, A), + Ch = pget(channel, C), + amqp_channel:call(ACh, #'queue.declare'{queue = ?QNAME, + durable = true}), + + publish(Ch, ?QNAME, 2), + {#'basic.get_ok'{delivery_tag = TagA}, _} = + amqp_channel:call(Ch, #'basic.get'{queue = ?QNAME}), + {#'basic.get_ok'{delivery_tag = TagB}, _} = + amqp_channel:call(Ch, #'basic.get'{queue = ?QNAME}), + amqp_channel:cast(Ch, #'basic.reject'{delivery_tag = TagA, requeue = true}), + restart(B), + ok = sync(C, ?QNAME), + amqp_channel:cast(Ch, #'basic.reject'{delivery_tag = TagB, requeue = true}), + consume(Ch, ?QNAME, 2), + + ok. + +restart(Cfg) -> rabbit_test_util:restart_app(Cfg). + +sync(Cfg, QName) -> + case sync_nowait(Cfg, QName) of + ok -> wait_for_sync(Cfg, QName), + ok; + R -> R + end. + +sync_nowait(Cfg, QName) -> action(Cfg, sync_queue, QName). +sync_cancel(Cfg, QName) -> action(Cfg, cancel_sync_queue, QName). + +wait_for_sync(Cfg, QName) -> + sync_detection:wait_for_sync_status(true, Cfg, QName). + +action(Cfg, Action, QName) -> + rabbit_test_util:control_action( + Action, Cfg, [binary_to_list(QName)], [{"-p", "/"}]). + +queue(Cfg, QName) -> + QNameRes = rabbit_misc:r(<<"/">>, queue, QName), + {ok, Q} = rpc:call(pget(node, Cfg), rabbit_amqqueue, lookup, [QNameRes]), + Q. + +wait_for_syncing(Cfg, QName, Target) -> + case state(Cfg, QName) of + {{syncing, _}, _} -> ok; + {running, Target} -> synced_already; + _ -> timer:sleep(100), + wait_for_syncing(Cfg, QName, Target) + end. + +wait_for_running(Cfg, QName) -> + case state(Cfg, QName) of + {running, _} -> ok; + _ -> timer:sleep(100), + wait_for_running(Cfg, QName) + end. + +state(Cfg, QName) -> + [{state, State}, {synchronised_slave_pids, Pids}] = + rpc:call(pget(node, Cfg), rabbit_amqqueue, info, + [queue(Cfg, QName), [state, synchronised_slave_pids]]), + {State, length(Pids)}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/many_node_ha.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/many_node_ha.erl new file mode 100644 index 0000000..9104d4c --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/many_node_ha.erl @@ -0,0 +1,64 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% +-module(many_node_ha). + +-compile(export_all). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +-import(rabbit_test_util, [a2b/1]). +-import(rabbit_misc, [pget/2]). + +kill_intermediate_with() -> + fun (Cfg) -> rabbit_test_configs:ha_policy_all( + rabbit_test_configs:cluster(Cfg, [a,b,c,d,e,f])) + end. +kill_intermediate([CfgA, CfgB, CfgC, CfgD, CfgE, CfgF]) -> + Msgs = rabbit_test_configs:cover_work_factor(20000, CfgA), + MasterChannel = pget(channel, CfgA), + ConsumerChannel = pget(channel, CfgE), + ProducerChannel = pget(channel, CfgF), + Queue = <<"test">>, + amqp_channel:call(MasterChannel, #'queue.declare'{queue = Queue, + auto_delete = false}), + + %% TODO: this seems *highly* timing dependant - the assumption being + %% that the kill will work quickly enough that there will still be + %% some messages in-flight that we *must* receive despite the intervening + %% node deaths. It would be nice if we could find a means to do this + %% in a way that is not actually timing dependent. + + %% Worse still, it assumes that killing the master will cause a + %% failover to Slave1, and so on. Nope. + + ConsumerPid = rabbit_ha_test_consumer:create(ConsumerChannel, + Queue, self(), false, Msgs), + + ProducerPid = rabbit_ha_test_producer:create(ProducerChannel, + Queue, self(), false, Msgs), + + %% create a killer for the master and the first 3 slaves + [rabbit_test_util:kill_after(Time, Cfg, sigkill) || + {Cfg, Time} <- [{CfgA, 50}, + {CfgB, 50}, + {CfgC, 100}, + {CfgD, 100}]], + + %% verify that the consumer got all msgs, or die, or time out + rabbit_ha_test_producer:await_response(ProducerPid), + rabbit_ha_test_consumer:await_response(ConsumerPid), + ok. + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/partitions.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/partitions.erl new file mode 100644 index 0000000..7ad6a07 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/partitions.erl @@ -0,0 +1,245 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% +-module(partitions). + +-compile(export_all). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +-import(rabbit_misc, [pget/2]). + +-define(CONFIG, [start_abc, fun enable_dist_proxy/1, + build_cluster, short_ticktime(1), start_connections]). +%% We set ticktime to 1s and setuptime is 7s so to make sure it +%% passes... +-define(DELAY, 8000). + +ignore_with() -> ?CONFIG. +ignore(Cfgs) -> + [A, B, C] = [pget(node, Cfg) || Cfg <- Cfgs], + block_unblock([{B, C}]), + timer:sleep(?DELAY), + [] = partitions(A), + [C] = partitions(B), + [B] = partitions(C), + ok. + +pause_on_down_with() -> ?CONFIG. +pause_on_down([CfgA, CfgB, CfgC] = Cfgs) -> + A = pget(node, CfgA), + set_mode(Cfgs, pause_minority), + true = is_running(A), + + rabbit_test_util:kill(CfgB, sigkill), + timer:sleep(?DELAY), + true = is_running(A), + + rabbit_test_util:kill(CfgC, sigkill), + await_running(A, false), + ok. + +pause_on_blocked_with() -> ?CONFIG. +pause_on_blocked(Cfgs) -> + [A, B, C] = [pget(node, Cfg) || Cfg <- Cfgs], + set_mode(Cfgs, pause_minority), + [(true = is_running(N)) || N <- [A, B, C]], + block([{A, B}, {A, C}]), + await_running(A, false), + [await_running(N, true) || N <- [B, C]], + unblock([{A, B}, {A, C}]), + [await_running(N, true) || N <- [A, B, C]], + Status = rpc:call(B, rabbit_mnesia, status, []), + [] = pget(partitions, Status), + ok. + +%% Make sure we do not confirm any messages after a partition has +%% happened but before we pause, since any such confirmations would be +%% lies. +%% +%% This test has to use an AB cluster (not ABC) since GM ends up +%% taking longer to detect down slaves when there are more nodes and +%% we close the window by mistake. +%% +%% In general there are quite a few ways to accidentally cause this +%% test to pass since there are a lot of things in the broker that can +%% suddenly take several seconds to time out when TCP connections +%% won't establish. +pause_false_promises_mirrored_with() -> + [start_ab, fun enable_dist_proxy/1, + build_cluster, short_ticktime(10), start_connections, ha_policy_all]. + +pause_false_promises_mirrored(Cfgs) -> + pause_false_promises(Cfgs). + +pause_false_promises_unmirrored_with() -> + [start_ab, fun enable_dist_proxy/1, + build_cluster, short_ticktime(10), start_connections]. + +pause_false_promises_unmirrored(Cfgs) -> + pause_false_promises(Cfgs). + +pause_false_promises([CfgA, CfgB | _] = Cfgs) -> + [A, B] = [pget(node, Cfg) || Cfg <- Cfgs], + set_mode([CfgA], pause_minority), + ChA = pget(channel, CfgA), + ChB = pget(channel, CfgB), + amqp_channel:call(ChB, #'queue.declare'{queue = <<"test">>, + durable = true}), + amqp_channel:call(ChA, #'confirm.select'{}), + amqp_channel:register_confirm_handler(ChA, self()), + + %% Cause a partition after 1s + Self = self(), + spawn_link(fun () -> + timer:sleep(1000), + %%io:format(user, "~p BLOCK~n", [calendar:local_time()]), + block([{A, B}]), + unlink(Self) + end), + + %% Publish large no of messages, see how many we get confirmed + [amqp_channel:cast(ChA, #'basic.publish'{routing_key = <<"test">>}, + #amqp_msg{props = #'P_basic'{delivery_mode = 1}}) || + _ <- lists:seq(1, 100000)], + %%io:format(user, "~p finish publish~n", [calendar:local_time()]), + + %% Time for the partition to be detected. We don't put this sleep + %% in receive_acks since otherwise we'd have another similar sleep + %% at the end. + timer:sleep(30000), + Confirmed = receive_acks(0), + %%io:format(user, "~p got acks~n", [calendar:local_time()]), + await_running(A, false), + %%io:format(user, "~p A stopped~n", [calendar:local_time()]), + + unblock([{A, B}]), + await_running(A, true), + + %% But how many made it onto the rest of the cluster? + #'queue.declare_ok'{message_count = Survived} = + amqp_channel:call(ChB, #'queue.declare'{queue = <<"test">>, + durable = true}), + %%io:format(user, "~p queue declared~n", [calendar:local_time()]), + case Confirmed > Survived of + true -> ?debugVal({Confirmed, Survived}); + false -> ok + end, + ?assert(Confirmed =< Survived), + ok. + +receive_acks(Max) -> + receive + #'basic.ack'{delivery_tag = DTag} -> + receive_acks(DTag) + after ?DELAY -> + Max + end. + +prompt_disconnect_detection_with() -> + [start_ab, fun enable_dist_proxy/1, + build_cluster, short_ticktime(1), start_connections]. + +prompt_disconnect_detection([CfgA, CfgB]) -> + A = pget(node, CfgA), + B = pget(node, CfgB), + ChB = pget(channel, CfgB), + [amqp_channel:call(ChB, #'queue.declare'{}) || _ <- lists:seq(1, 100)], + block([{A, B}]), + timer:sleep(?DELAY), + %% We want to make sure we do not end up waiting for setuptime * + %% no of queues. Unfortunately that means we need a timeout... + [] = rpc(CfgA, rabbit_amqqueue, info_all, [<<"/">>], ?DELAY), + ok. + +autoheal_with() -> ?CONFIG. +autoheal(Cfgs) -> + [A, B, C] = [pget(node, Cfg) || Cfg <- Cfgs], + set_mode(Cfgs, autoheal), + Test = fun (Pairs) -> + block_unblock(Pairs), + [await_running(N, true) || N <- [A, B, C]], + [] = partitions(A), + [] = partitions(B), + [] = partitions(C) + end, + Test([{B, C}]), + Test([{A, C}, {B, C}]), + Test([{A, B}, {A, C}, {B, C}]), + ok. + +set_mode(Cfgs, Mode) -> + [set_env(Cfg, rabbit, cluster_partition_handling, Mode) || Cfg <- Cfgs]. + +set_env(Cfg, App, K, V) -> + rpc(Cfg, application, set_env, [App, K, V]). + +block_unblock(Pairs) -> + block(Pairs), + timer:sleep(?DELAY), + unblock(Pairs). + +block(Pairs) -> [block(X, Y) || {X, Y} <- Pairs]. +unblock(Pairs) -> [allow(X, Y) || {X, Y} <- Pairs]. + +partitions(Node) -> + rpc:call(Node, rabbit_node_monitor, partitions, []). + +block(X, Y) -> + rpc:call(X, inet_tcp_proxy, block, [Y]), + rpc:call(Y, inet_tcp_proxy, block, [X]). + +allow(X, Y) -> + rpc:call(X, inet_tcp_proxy, allow, [Y]), + rpc:call(Y, inet_tcp_proxy, allow, [X]). + +await_running (Node, Bool) -> await(Node, Bool, fun is_running/1). +await_listening(Node, Bool) -> await(Node, Bool, fun is_listening/1). + +await(Node, Bool, Fun) -> + case Fun(Node) of + Bool -> ok; + _ -> timer:sleep(100), + await(Node, Bool, Fun) + end. + +is_running(Node) -> rpc:call(Node, rabbit, is_running, []). + +is_listening(Node) -> + case rpc:call(Node, rabbit_networking, node_listeners, [Node]) of + [] -> false; + [_|_] -> true; + _ -> false + end. + +enable_dist_proxy(Cfgs) -> + inet_tcp_proxy_manager:start_link(), + Nodes = [pget(node, Cfg) || Cfg <- Cfgs], + [ok = rpc:call(Node, inet_tcp_proxy, start, []) || Node <- Nodes], + [ok = rpc:call(Node, inet_tcp_proxy, reconnect, [Nodes]) || Node <- Nodes], + Cfgs. + +short_ticktime(Time) -> + fun (Cfgs) -> + [rpc(Cfg, net_kernel, set_net_ticktime, [Time, 0]) || Cfg <- Cfgs], + net_kernel:set_net_ticktime(Time, 0), + Cfgs + end. + +rpc(Cfg, M, F, A) -> + rpc:call(pget(node, Cfg), M, F, A). + +rpc(Cfg, M, F, A, T) -> + rpc:call(pget(node, Cfg), M, F, A, T). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/simple_ha.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/simple_ha.erl new file mode 100644 index 0000000..7b13f0f --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/simple_ha.erl @@ -0,0 +1,123 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% +-module(simple_ha). + +-compile(export_all). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +-import(rabbit_test_util, [set_ha_policy/3, a2b/1]). +-import(rabbit_misc, [pget/2]). + +-define(CONFIG, [cluster_abc, ha_policy_all]). + +rapid_redeclare_with() -> [cluster_ab, ha_policy_all]. +rapid_redeclare([CfgA | _]) -> + Ch = pget(channel, CfgA), + Queue = <<"test">>, + [begin + amqp_channel:call(Ch, #'queue.declare'{queue = Queue, + durable = true}), + amqp_channel:call(Ch, #'queue.delete'{queue = Queue}) + end || _I <- lists:seq(1, 20)], + ok. + +consume_survives_stop_with() -> ?CONFIG. +consume_survives_sigkill_with() -> ?CONFIG. +consume_survives_policy_with() -> ?CONFIG. +auto_resume_with() -> ?CONFIG. +auto_resume_no_ccn_client_with() -> ?CONFIG. + +consume_survives_stop(Cf) -> consume_survives(Cf, fun stop/2, true). +consume_survives_sigkill(Cf) -> consume_survives(Cf, fun sigkill/2, true). +consume_survives_policy(Cf) -> consume_survives(Cf, fun policy/2, true). +auto_resume(Cf) -> consume_survives(Cf, fun sigkill/2, false). +auto_resume_no_ccn_client(Cf) -> consume_survives(Cf, fun sigkill/2, false, + false). + +confirms_survive_stop_with() -> ?CONFIG. +confirms_survive_sigkill_with() -> ?CONFIG. +confirms_survive_policy_with() -> ?CONFIG. + +confirms_survive_stop(Cf) -> confirms_survive(Cf, fun stop/2). +confirms_survive_sigkill(Cf) -> confirms_survive(Cf, fun sigkill/2). +confirms_survive_policy(Cf) -> confirms_survive(Cf, fun policy/2). + +%%---------------------------------------------------------------------------- + +consume_survives(Nodes, DeathFun, CancelOnFailover) -> + consume_survives(Nodes, DeathFun, CancelOnFailover, true). + +consume_survives([CfgA, CfgB, CfgC] = Nodes, + DeathFun, CancelOnFailover, CCNSupported) -> + Msgs = rabbit_test_configs:cover_work_factor(20000, CfgA), + Channel1 = pget(channel, CfgA), + Channel2 = pget(channel, CfgB), + Channel3 = pget(channel, CfgC), + + %% declare the queue on the master, mirrored to the two slaves + Queue = <<"test">>, + amqp_channel:call(Channel1, #'queue.declare'{queue = Queue, + auto_delete = false}), + + %% start up a consumer + ConsCh = case CCNSupported of + true -> Channel2; + false -> open_incapable_channel(pget(port, CfgB)) + end, + ConsumerPid = rabbit_ha_test_consumer:create( + ConsCh, Queue, self(), CancelOnFailover, Msgs), + + %% send a bunch of messages from the producer + ProducerPid = rabbit_ha_test_producer:create(Channel3, Queue, + self(), false, Msgs), + DeathFun(CfgA, Nodes), + %% verify that the consumer got all msgs, or die - the await_response + %% calls throw an exception if anything goes wrong.... + rabbit_ha_test_consumer:await_response(ConsumerPid), + rabbit_ha_test_producer:await_response(ProducerPid), + ok. + +confirms_survive([CfgA, CfgB, _CfgC] = Nodes, DeathFun) -> + Msgs = rabbit_test_configs:cover_work_factor(20000, CfgA), + Node1Channel = pget(channel, CfgA), + Node2Channel = pget(channel, CfgB), + + %% declare the queue on the master, mirrored to the two slaves + Queue = <<"test">>, + amqp_channel:call(Node1Channel,#'queue.declare'{queue = Queue, + auto_delete = false, + durable = true}), + + %% send a bunch of messages from the producer + ProducerPid = rabbit_ha_test_producer:create(Node2Channel, Queue, + self(), true, Msgs), + DeathFun(CfgA, Nodes), + rabbit_ha_test_producer:await_response(ProducerPid), + ok. + +stop(Cfg, _Cfgs) -> rabbit_test_util:kill_after(50, Cfg, stop). +sigkill(Cfg, _Cfgs) -> rabbit_test_util:kill_after(50, Cfg, sigkill). +policy(Cfg, [_|T]) -> Nodes = [a2b(pget(node, C)) || C <- T], + set_ha_policy(Cfg, <<".*">>, {<<"nodes">>, Nodes}). + +open_incapable_channel(NodePort) -> + Props = [{<<"capabilities">>, table, []}], + {ok, ConsConn} = + amqp_connection:start(#amqp_params_network{port = NodePort, + client_properties = Props}), + {ok, Ch} = amqp_connection:open_channel(ConsConn), + Ch. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/sync_detection.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/sync_detection.erl new file mode 100644 index 0000000..c284d14 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-test/test/src/sync_detection.erl @@ -0,0 +1,189 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% +-module(sync_detection). + +-compile(export_all). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +-import(rabbit_test_util, [stop_app/1, start_app/1]). +-import(rabbit_misc, [pget/2]). + +-define(LOOP_RECURSION_DELAY, 100). + +slave_synchronization_with() -> [cluster_ab, ha_policy_two_pos]. +slave_synchronization([Master, Slave]) -> + Channel = pget(channel, Master), + Queue = <<"ha.two.test">>, + #'queue.declare_ok'{} = + amqp_channel:call(Channel, #'queue.declare'{queue = Queue, + auto_delete = false}), + + %% The comments on the right are the queue length and the pending acks on + %% the master. + stop_app(Slave), + + %% We get and ack one message when the slave is down, and check that when we + %% start the slave it's not marked as synced until ack the message. We also + %% publish another message when the slave is up. + send_dummy_message(Channel, Queue), % 1 - 0 + {#'basic.get_ok'{delivery_tag = Tag1}, _} = + amqp_channel:call(Channel, #'basic.get'{queue = Queue}), % 0 - 1 + + start_app(Slave), + + slave_unsynced(Master, Queue), + send_dummy_message(Channel, Queue), % 1 - 1 + slave_unsynced(Master, Queue), + + amqp_channel:cast(Channel, #'basic.ack'{delivery_tag = Tag1}), % 1 - 0 + + slave_synced(Master, Queue), + + %% We restart the slave and we send a message, so that the slave will only + %% have one of the messages. + stop_app(Slave), + start_app(Slave), + + send_dummy_message(Channel, Queue), % 2 - 0 + + slave_unsynced(Master, Queue), + + %% We reject the message that the slave doesn't have, and verify that it's + %% still unsynced + {#'basic.get_ok'{delivery_tag = Tag2}, _} = + amqp_channel:call(Channel, #'basic.get'{queue = Queue}), % 1 - 1 + slave_unsynced(Master, Queue), + amqp_channel:cast(Channel, #'basic.reject'{ delivery_tag = Tag2, + requeue = true }), % 2 - 0 + slave_unsynced(Master, Queue), + {#'basic.get_ok'{delivery_tag = Tag3}, _} = + amqp_channel:call(Channel, #'basic.get'{queue = Queue}), % 1 - 1 + amqp_channel:cast(Channel, #'basic.ack'{delivery_tag = Tag3}), % 1 - 0 + slave_synced(Master, Queue), + {#'basic.get_ok'{delivery_tag = Tag4}, _} = + amqp_channel:call(Channel, #'basic.get'{queue = Queue}), % 0 - 1 + amqp_channel:cast(Channel, #'basic.ack'{delivery_tag = Tag4}), % 0 - 0 + slave_synced(Master, Queue). + +slave_synchronization_ttl_with() -> [cluster_abc, ha_policy_two_pos]. +slave_synchronization_ttl([Master, Slave, DLX]) -> + Channel = pget(channel, Master), + DLXChannel = pget(channel, DLX), + + %% We declare a DLX queue to wait for messages to be TTL'ed + DLXQueue = <<"dlx-queue">>, + #'queue.declare_ok'{} = + amqp_channel:call(Channel, #'queue.declare'{queue = DLXQueue, + auto_delete = false}), + + TestMsgTTL = 5000, + Queue = <<"ha.two.test">>, + %% Sadly we need fairly high numbers for the TTL because starting/stopping + %% nodes takes a fair amount of time. + Args = [{<<"x-message-ttl">>, long, TestMsgTTL}, + {<<"x-dead-letter-exchange">>, longstr, <<>>}, + {<<"x-dead-letter-routing-key">>, longstr, DLXQueue}], + #'queue.declare_ok'{} = + amqp_channel:call(Channel, #'queue.declare'{queue = Queue, + auto_delete = false, + arguments = Args}), + + slave_synced(Master, Queue), + + %% All unknown + stop_app(Slave), + send_dummy_message(Channel, Queue), + send_dummy_message(Channel, Queue), + start_app(Slave), + slave_unsynced(Master, Queue), + wait_for_messages(DLXQueue, DLXChannel, 2), + slave_synced(Master, Queue), + + %% 1 unknown, 1 known + stop_app(Slave), + send_dummy_message(Channel, Queue), + start_app(Slave), + slave_unsynced(Master, Queue), + send_dummy_message(Channel, Queue), + slave_unsynced(Master, Queue), + wait_for_messages(DLXQueue, DLXChannel, 2), + slave_synced(Master, Queue), + + %% %% both known + send_dummy_message(Channel, Queue), + send_dummy_message(Channel, Queue), + slave_synced(Master, Queue), + wait_for_messages(DLXQueue, DLXChannel, 2), + slave_synced(Master, Queue), + + ok. + +send_dummy_message(Channel, Queue) -> + Payload = <<"foo">>, + Publish = #'basic.publish'{exchange = <<>>, routing_key = Queue}, + amqp_channel:cast(Channel, Publish, #amqp_msg{payload = Payload}). + +slave_pids(Node, Queue) -> + {ok, Q} = rpc:call(Node, rabbit_amqqueue, lookup, + [rabbit_misc:r(<<"/">>, queue, Queue)]), + SSP = synchronised_slave_pids, + [{SSP, Pids}] = rpc:call(Node, rabbit_amqqueue, info, [Q, [SSP]]), + case Pids of + '' -> []; + _ -> Pids + end. + +%% The mnesia syncronization takes a while, but we don't want to wait for the +%% test to fail, since the timetrap is quite high. +wait_for_sync_status(Status, Cfg, Queue) -> + Max = 10000 / ?LOOP_RECURSION_DELAY, + wait_for_sync_status(0, Max, Status, pget(node, Cfg), Queue). + +wait_for_sync_status(N, Max, Status, Node, Queue) when N >= Max -> + error({sync_status_max_tries_failed, + [{queue, Queue}, + {node, Node}, + {expected_status, Status}, + {max_tried, Max}]}); +wait_for_sync_status(N, Max, Status, Node, Queue) -> + Synced = length(slave_pids(Node, Queue)) =:= 1, + case Synced =:= Status of + true -> ok; + false -> timer:sleep(?LOOP_RECURSION_DELAY), + wait_for_sync_status(N + 1, Max, Status, Node, Queue) + end. + +slave_synced(Cfg, Queue) -> + wait_for_sync_status(true, Cfg, Queue). + +slave_unsynced(Cfg, Queue) -> + wait_for_sync_status(false, Cfg, Queue). + +wait_for_messages(Queue, Channel, N) -> + Sub = #'basic.consume'{queue = Queue}, + #'basic.consume_ok'{consumer_tag = CTag} = amqp_channel:call(Channel, Sub), + receive + #'basic.consume_ok'{} -> ok + end, + lists:foreach( + fun (_) -> receive + {#'basic.deliver'{delivery_tag = Tag}, _Content} -> + amqp_channel:cast(Channel, + #'basic.ack'{delivery_tag = Tag}) + end + end, lists:seq(1, N)), + amqp_channel:call(Channel, #'basic.cancel'{consumer_tag = CTag}). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/.srcdist_done b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/.srcdist_done new file mode 100644 index 0000000..e69de29 diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/Makefile b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/Makefile new file mode 100644 index 0000000..482105a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/Makefile @@ -0,0 +1 @@ +include ../umbrella.mk diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/README b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/README new file mode 100644 index 0000000..c91d414 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/README @@ -0,0 +1,40 @@ +An opinionated tracing plugin for RabbitMQ management. Build it like +any other plugin. After installation you should see a "Tracing" tab in +the management UI. Hopefully use is obvious. + +Configuration +============= + +There is one configuration option: + +directory: This controls where the log files go. It defaults to +"/var/tmp/rabbitmq-tracing". + +Performance +=========== + +On my workstation, rabbitmq-tracing can write about 2000 msg/s to a +log file. You should be careful using rabbitmq-tracing if you think +you're going to capture more messages than this. Of course, any +messages that can't be logged are queued. + +The code to serve up the log files over HTTP is pretty dumb, it loads +the whole log into memory. If you have large log files you may wish +to transfer them off the server in some other way. + +HTTP API +======== + +GET /api/traces +GET /api/traces/ +GET PUT DELETE /api/traces// +GET /api/trace-files +GET DELETE /api/trace-files/ (GET returns the file as text/plain, + not JSON describing it.) + +Example for how to create a trace: + +$ curl -i -u guest:guest -H "content-type:application/json" -XPUT \ + http://localhost:55672/api/traces/%2f/my-trace \ + -d'{"format":"text","pattern":"#"}' + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/package.mk b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/package.mk new file mode 100644 index 0000000..58341bb --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/package.mk @@ -0,0 +1,8 @@ +RELEASABLE:=true +DEPS:=rabbitmq-management +WITH_BROKER_TEST_COMMANDS:=eunit:test(rabbit_tracing_test,[verbose]) + +CONSTRUCT_APP_PREREQS:=$(shell find $(PACKAGE_DIR)/priv -type f) +define construct_app_commands + cp -r $(PACKAGE_DIR)/priv $(APP_DIR) +endef diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/priv/www/js/tmpl/traces.ejs b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/priv/www/js/tmpl/traces.ejs new file mode 100644 index 0000000..b8c6354 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/priv/www/js/tmpl/traces.ejs @@ -0,0 +1,145 @@ +

Traces

+
+

All traces

+
+ + + + + +
+

Currently running traces

+ <% if (traces.length > 0) { %> + + + + <% if (vhosts_interesting) { %> + + <% } %> + + + + + + + + + + <% + for (var i = 0; i < traces.length; i++) { + var trace = traces[i]; + %> + > + <% if (vhosts_interesting) { %> + + <% } %> + + + + <% if (trace.queue) { %> + + + <% } else { %> + + <% } %> + + + <% } %> + +
Virtual hostNamePatternFormatRateQueued
<%= fmt_string(trace.vhost) %><%= fmt_string(trace.name) %><%= fmt_string(trace.pattern) %><%= fmt_string(trace.format) %> + <%= fmt_rate(trace.queue.message_stats, 'ack', false) %> + + <%= trace.queue.messages %> + <%= link_trace_queue(trace) %> + +
FAILED
+
+
+ + + +
+
+ <% } else { %> +

... no traces running ...

+ <% } %> +
+

Trace log files

+ <% if (files.length > 0) { %> + + + + + + + + + + <% + for (var i = 0; i < files.length; i++) { + var file = files[i]; + %> + > + + + + + <% } %> + +
NameSize
<%= link_trace(file.name) %><%= fmt_bytes(file.size) %> +
+ + +
+
+ <% } else { %> +

... no files ...

+ <% } %> +
+
+
+ +
+

Add a new trace

+
+
+ +<% if (vhosts_interesting) { %> + + + + +<% } else { %> + +<% } %> + + + + + + + + + + + + +
+ +
*
+ +
+ + Examples: #, publish.#, deliver.# #.amq.direct, #.myqueue +
+ +
+
+
diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/priv/www/js/tracing.js b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/priv/www/js/tracing.js new file mode 100644 index 0000000..89852ba --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/priv/www/js/tracing.js @@ -0,0 +1,38 @@ +dispatcher_add(function(sammy) { + sammy.get('#/traces', function() { + render({'traces': '/traces', + 'vhosts': '/vhosts', + 'files': '/trace-files'}, + 'traces', '#/traces'); + }); + sammy.get('#/traces/:vhost/:name', function() { + var path = '/traces/' + esc(this.params['vhost']) + '/' + esc(this.params['name']); + render({'trace': path}, + 'trace', '#/traces'); + }); + sammy.put('#/traces', function() { + if (sync_put(this, '/traces/:vhost/:name')) + update(); + return false; + }); + sammy.del('#/traces', function() { + if (sync_delete(this, '/traces/:vhost/:name')) + partial_update(); + return false; + }); + sammy.del('#/trace-files', function() { + if (sync_delete(this, '/trace-files/:name')) + partial_update(); + return false; + }); +}); + +NAVIGATION['Admin'][0]['Tracing'] = ['#/traces', 'administrator']; + +function link_trace(name) { + return _link_to(fmt_escape_html(name), 'api/trace-files/' + esc(name)); +} + +function link_trace_queue(trace) { + return _link_to('(queue)', '#/queues/' + esc(trace.vhost) + '/' + esc(trace.queue.name)); +} diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_app.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_app.erl new file mode 100644 index 0000000..bfa249f --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_app.erl @@ -0,0 +1,26 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_tracing_app). + +-behaviour(application). +-export([start/2, stop/1]). + +start(_Type, _StartArgs) -> + rabbit_tracing_sup:start_link(). + +stop(_State) -> + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_consumer.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_consumer.erl new file mode 100644 index 0000000..2194226 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_consumer.erl @@ -0,0 +1,168 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ Federation. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_tracing_consumer). + +-behaviour(gen_server). + +-include_lib("amqp_client/include/amqp_client.hrl"). + +-import(rabbit_misc, [pget/2, pget/3, table_lookup/2]). + +-record(state, {conn, ch, vhost, queue, file, filename, format}). +-record(log_record, {timestamp, type, exchange, queue, node, routing_keys, + properties, payload}). + +-define(X, <<"amq.rabbitmq.trace">>). + +-export([start_link/1, info_all/1]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3]). + +start_link(Args) -> + gen_server:start_link(?MODULE, Args, []). + +info_all(Pid) -> + gen_server:call(Pid, info_all, infinity). + +%%---------------------------------------------------------------------------- + +init(Args) -> + process_flag(trap_exit, true), + Name = pget(name, Args), + VHost = pget(vhost, Args), + {ok, Conn} = amqp_connection:start( + #amqp_params_direct{virtual_host = VHost}), + link(Conn), + {ok, Ch} = amqp_connection:open_channel(Conn), + link(Ch), + #'queue.declare_ok'{queue = Q} = + amqp_channel:call(Ch, #'queue.declare'{durable = false, + exclusive = true}), + #'queue.bind_ok'{} = + amqp_channel:call( + Ch, #'queue.bind'{exchange = ?X, queue = Q, + routing_key = pget(pattern, Args)}), + #'basic.qos_ok'{} = + amqp_channel:call(Ch, #'basic.qos'{prefetch_count = 10}), + #'basic.consume_ok'{} = + amqp_channel:subscribe(Ch, #'basic.consume'{queue = Q, + no_ack = false}, self()), + {ok, Dir} = application:get_env(directory), + Filename = Dir ++ "/" ++ binary_to_list(Name) ++ ".log", + case filelib:ensure_dir(Filename) of + ok -> + case file:open(Filename, [append]) of + {ok, F} -> + rabbit_tracing_traces:announce(VHost, Name, self()), + Format = list_to_atom(binary_to_list(pget(format, Args))), + rabbit_log:info("Tracer opened log file ~p with " + "format ~p~n", [Filename, Format]), + {ok, #state{conn = Conn, ch = Ch, vhost = VHost, queue = Q, + file = F, filename = Filename, + format = Format}}; + {error, E} -> + {stop, {could_not_open, Filename, E}} + end; + {error, E} -> + {stop, {could_not_create_dir, Dir, E}} + end. + +handle_call(info_all, _From, State = #state{vhost = V, queue = Q}) -> + [QInfo] = rabbit_mgmt_db:augment_queues( + [rabbit_mgmt_wm_queue:queue(V, Q)], + rabbit_mgmt_util:no_range(), basic), + {reply, [{queue, rabbit_mgmt_format:strip_pids(QInfo)}], State}; + +handle_call(_Req, _From, State) -> + {reply, unknown_request, State}. + +handle_cast(_C, State) -> + {noreply, State}. + +handle_info(Delivery = {#'basic.deliver'{delivery_tag = Seq}, #amqp_msg{}}, + State = #state{ch = Ch, file = F, format = Format}) -> + Print = fun(Fmt, Args) -> io:format(F, Fmt, Args) end, + log(Format, Print, delivery_to_log_record(Delivery)), + amqp_channel:cast(Ch, #'basic.ack'{delivery_tag = Seq}), + {noreply, State}; + +handle_info(_I, State) -> + {noreply, State}. + +terminate(shutdown, #state{conn = Conn, ch = Ch, + file = F, filename = Filename}) -> + catch amqp_channel:close(Ch), + catch amqp_connection:close(Conn), + catch file:close(F), + rabbit_log:info("Tracer closed log file ~p~n", [Filename]), + ok; + +terminate(_Reason, _State) -> + ok. + +code_change(_, State, _) -> {ok, State}. + +%%---------------------------------------------------------------------------- + +delivery_to_log_record({#'basic.deliver'{routing_key = Key}, + #amqp_msg{props = #'P_basic'{headers = H}, + payload = Payload}}) -> + {Type, Q} = case Key of + <<"publish.", _Rest/binary>> -> {published, none}; + <<"deliver.", Rest/binary>> -> {received, Rest} + end, + {longstr, Node} = table_lookup(H, <<"node">>), + {longstr, X} = table_lookup(H, <<"exchange_name">>), + {array, Keys} = table_lookup(H, <<"routing_keys">>), + {table, Props} = table_lookup(H, <<"properties">>), + #log_record{timestamp = rabbit_mgmt_format:timestamp(os:timestamp()), + type = Type, + exchange = X, + queue = Q, + node = Node, + routing_keys = [K || {_, K} <- Keys], + properties = Props, + payload = Payload}. + +log(text, P, Record) -> + P("~n~s~n", [string:copies("=", 80)]), + P("~s: ", [Record#log_record.timestamp]), + case Record#log_record.type of + published -> P("Message published~n~n", []); + received -> P("Message received~n~n", []) + end, + P("Node: ~s~n", [Record#log_record.node]), + P("Exchange: ~s~n", [Record#log_record.exchange]), + case Record#log_record.queue of + none -> ok; + Q -> P("Queue: ~s~n", [Q]) + end, + P("Routing keys: ~p~n", [Record#log_record.routing_keys]), + P("Properties: ~p~n", [Record#log_record.properties]), + P("Payload: ~n~s~n", [Record#log_record.payload]); + +log(json, P, Record) -> + P("~s~n", [mochijson2:encode( + [{timestamp, Record#log_record.timestamp}, + {type, Record#log_record.type}, + {node, Record#log_record.node}, + {exchange, Record#log_record.exchange}, + {queue, Record#log_record.queue}, + {routing_keys, Record#log_record.routing_keys}, + {properties, rabbit_mgmt_format:amqp_table( + Record#log_record.properties)}, + {payload, base64:encode(Record#log_record.payload)}])]). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_consumer_sup.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_consumer_sup.erl new file mode 100644 index 0000000..10289a6 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_consumer_sup.erl @@ -0,0 +1,34 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ Federation. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_tracing_consumer_sup). + +-behaviour(supervisor). + +-include_lib("rabbit_common/include/rabbit.hrl"). + +-export([start_link/1]). +-export([init/1]). + +start_link(Args) -> supervisor2:start_link(?MODULE, Args). + +%%---------------------------------------------------------------------------- + +init(Args) -> + {ok, {{one_for_one, 3, 10}, + [{consumer, {rabbit_tracing_consumer, start_link, [Args]}, + transient, ?MAX_WAIT, worker, + [rabbit_tracing_consumer]}]}}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_files.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_files.erl new file mode 100644 index 0000000..2005fdf --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_files.erl @@ -0,0 +1,51 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_tracing_files). + +-include_lib("kernel/include/file.hrl"). + +-export([list/0, exists/1, delete/1, full_path/1]). + +%%-------------------------------------------------------------------- + +list() -> + {ok, Dir} = application:get_env(rabbitmq_tracing, directory), + ok = filelib:ensure_dir(Dir ++ "/a"), + {ok, Names} = file:list_dir(Dir), + [file_info(Name) || Name <- Names]. + +exists(Name) -> + filelib:is_regular(full_path(Name)). + +delete(Name) -> + ok = file:delete(full_path(Name)). + +full_path(Name0) when is_binary(Name0) -> + full_path(binary_to_list(Name0)); +full_path(Name0) -> + {ok, Dir} = application:get_env(rabbitmq_tracing, directory), + case mochiweb_util:safe_relative_path(Name0) of + undefined -> exit(how_rude); + Name -> Dir ++ "/" ++ Name + end. + +%%-------------------------------------------------------------------- + +file_info(Name) -> + {ok, Info} = file:read_file_info(full_path(Name)), + [{name, list_to_binary(Name)}, + {size, Info#file_info.size}]. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_mgmt.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_mgmt.erl new file mode 100644 index 0000000..25a4bd8 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_mgmt.erl @@ -0,0 +1,29 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_tracing_mgmt). + +-behaviour(rabbit_mgmt_extension). + +-export([dispatcher/0, web_ui/0]). + +dispatcher() -> [{["traces"], rabbit_tracing_wm_traces, []}, + {["traces", vhost], rabbit_tracing_wm_traces, []}, + {["traces", vhost, name], rabbit_tracing_wm_trace, []}, + {["trace-files"], rabbit_tracing_wm_files, []}, + {["trace-files", name], rabbit_tracing_wm_file, []}]. + +web_ui() -> [{javascript, <<"tracing.js">>}]. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_sup.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_sup.erl new file mode 100644 index 0000000..e0f352e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_sup.erl @@ -0,0 +1,50 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_tracing_sup). + +-behaviour(supervisor). + +-include_lib("rabbit_common/include/rabbit.hrl"). + +-define(SUPERVISOR, ?MODULE). + +-export([start_link/0, start_child/2, stop_child/1]). +-export([init/1]). + +%%---------------------------------------------------------------------------- + +start_link() -> + supervisor:start_link({local, ?SUPERVISOR}, ?MODULE, []). + +start_child(Id, Args) -> + supervisor:start_child( + ?SUPERVISOR, + {Id, {rabbit_tracing_consumer_sup, start_link, [Args]}, + temporary, ?MAX_WAIT, supervisor, + [rabbit_tracing_consumer_sup]}). + +stop_child(Id) -> + supervisor:terminate_child(?SUPERVISOR, Id), + supervisor:delete_child(?SUPERVISOR, Id), + ok. + +%%---------------------------------------------------------------------------- + +init([]) -> {ok, {{one_for_one, 3, 10}, + [{traces, {rabbit_tracing_traces, start_link, []}, + transient, ?MAX_WAIT, worker, + [rabbit_tracing_traces]}]}}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_traces.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_traces.erl new file mode 100644 index 0000000..7fc7c14 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_traces.erl @@ -0,0 +1,123 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_tracing_traces). + +-behaviour(gen_server). + +-import(rabbit_misc, [pget/2]). + +-export([list/0, lookup/2, create/3, stop/2, announce/3]). + +-export([start_link/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3]). + +-define(SERVER, ?MODULE). + +-record(state, { table }). + +%%-------------------------------------------------------------------- + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +list() -> + gen_server:call(?MODULE, list, infinity). + +lookup(VHost, Name) -> + gen_server:call(?MODULE, {lookup, VHost, Name}, infinity). + +create(VHost, Name, Trace) -> + gen_server:call(?MODULE, {create, VHost, Name, Trace}, infinity). + +stop(VHost, Name) -> + gen_server:call(?MODULE, {stop, VHost, Name}, infinity). + +announce(VHost, Name, Pid) -> + gen_server:cast(?MODULE, {announce, {VHost, Name}, Pid}). + +%%-------------------------------------------------------------------- + +init([]) -> + {ok, #state{table = ets:new(anon, [private])}}. + +handle_call(list, _From, State = #state{table = Table}) -> + {reply, [augment(Trace) || {_K, Trace} <- ets:tab2list(Table)], State}; + +handle_call({lookup, VHost, Name}, _From, State = #state{table = Table}) -> + {reply, case ets:lookup(Table, {VHost, Name}) of + [] -> not_found; + [{_K, Trace}] -> augment(Trace) + end, State}; + +handle_call({create, VHost, Name, Trace0}, _From, + State = #state{table = Table}) -> + Already = vhost_tracing(VHost, Table), + Trace = pset(vhost, VHost, pset(name, Name, Trace0)), + true = ets:insert(Table, {{VHost, Name}, Trace}), + case Already of + true -> ok; + false -> rabbit_trace:start(VHost) + end, + {reply, rabbit_tracing_sup:start_child({VHost, Name}, Trace), State}; + +handle_call({stop, VHost, Name}, _From, State = #state{table = Table}) -> + true = ets:delete(Table, {VHost, Name}), + case vhost_tracing(VHost, Table) of + true -> ok; + false -> rabbit_trace:stop(VHost) + end, + rabbit_tracing_sup:stop_child({VHost, Name}), + {reply, ok, State}; + +handle_call(_Req, _From, State) -> + {reply, unknown_request, State}. + +handle_cast({announce, Key, Pid}, State = #state{table = Table}) -> + case ets:lookup(Table, Key) of + [] -> ok; + [{_, Trace}] -> ets:insert(Table, {Key, pset(pid, Pid, Trace)}) + end, + {noreply, State}; + +handle_cast(_C, State) -> + {noreply, State}. + +handle_info(_I, State) -> + {noreply, State}. + +terminate(_, _) -> ok. + +code_change(_, State, _) -> {ok, State}. + +%%-------------------------------------------------------------------- + +pset(Key, Value, List) -> [{Key, Value} | proplists:delete(Key, List)]. + +vhost_tracing(VHost, Table) -> + case [true || {{V, _}, _} <- ets:tab2list(Table), V =:= VHost] of + [] -> false; + _ -> true + end. + +augment(Trace) -> + Pid = pget(pid, Trace), + Trace1 = lists:keydelete(pid, 1, Trace), + case Pid of + undefined -> Trace1; + _ -> rabbit_tracing_consumer:info_all(Pid) ++ Trace1 + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_wm_file.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_wm_file.erl new file mode 100644 index 0000000..4d67f73 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_wm_file.erl @@ -0,0 +1,49 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. + +-module(rabbit_tracing_wm_file). + +-export([init/1, resource_exists/2, serve/2, content_types_provided/2, + is_authorized/2, allowed_methods/2, delete_resource/2]). + +-include_lib("rabbitmq_management/include/rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). + +%%-------------------------------------------------------------------- +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"text/plain", serve}], ReqData, Context}. + +allowed_methods(ReqData, Context) -> + {['HEAD', 'GET', 'DELETE'], ReqData, Context}. + +resource_exists(ReqData, Context) -> + Name = rabbit_mgmt_util:id(name, ReqData), + {rabbit_tracing_files:exists(Name), ReqData, Context}. + +serve(ReqData, Context) -> + Name = rabbit_mgmt_util:id(name, ReqData), + {ok, Content} = file:read_file(rabbit_tracing_files:full_path(Name)), + {Content, ReqData, Context}. + +delete_resource(ReqData, Context) -> + Name = rabbit_mgmt_util:id(name, ReqData), + ok = rabbit_tracing_files:delete(Name), + {true, ReqData, Context}. + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_admin(ReqData, Context). + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_wm_files.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_wm_files.erl new file mode 100644 index 0000000..b19cfba --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_wm_files.erl @@ -0,0 +1,37 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_tracing_wm_files). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2]). + +-include_lib("rabbitmq_management/include/rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply(rabbit_tracing_files:list(), ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_admin(ReqData, Context). + +%%-------------------------------------------------------------------- diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_wm_trace.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_wm_trace.erl new file mode 100644 index 0000000..d7f8acb --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_wm_trace.erl @@ -0,0 +1,82 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. + +-module(rabbit_tracing_wm_trace). + +-export([init/1, resource_exists/2, to_json/2, + content_types_provided/2, content_types_accepted/2, + is_authorized/2, allowed_methods/2, accept_content/2, + delete_resource/2]). + +-define(ERR, <<"Something went wrong trying to start the trace - check the " + "logs.">>). + +-include_lib("rabbitmq_management/include/rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). + +%%-------------------------------------------------------------------- +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +content_types_accepted(ReqData, Context) -> + {[{"application/json", accept_content}], ReqData, Context}. + +allowed_methods(ReqData, Context) -> + {['HEAD', 'GET', 'PUT', 'DELETE'], ReqData, Context}. + +resource_exists(ReqData, Context) -> + {case trace(ReqData) of + not_found -> false; + _ -> true + end, ReqData, Context}. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply(trace(ReqData), ReqData, Context). + +accept_content(ReqData, Context) -> + case rabbit_mgmt_util:vhost(ReqData) of + not_found -> not_found; + VHost -> Name = rabbit_mgmt_util:id(name, ReqData), + rabbit_mgmt_util:with_decode( + [format], ReqData, Context, + fun([_], Trace) -> + case rabbit_tracing_traces:create( + VHost, Name, Trace) of + {ok, _} -> {true, ReqData, Context}; + _ -> rabbit_mgmt_util:bad_request( + ?ERR, ReqData, Context) + end + end) + end. + +delete_resource(ReqData, Context) -> + VHost = rabbit_mgmt_util:vhost(ReqData), + Name = rabbit_mgmt_util:id(name, ReqData), + ok = rabbit_tracing_traces:stop(VHost, Name), + {true, ReqData, Context}. + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_admin(ReqData, Context). + +%%-------------------------------------------------------------------- + +trace(ReqData) -> + case rabbit_mgmt_util:vhost(ReqData) of + not_found -> not_found; + VHost -> rabbit_tracing_traces:lookup( + VHost, rabbit_mgmt_util:id(name, ReqData)) + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_wm_traces.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_wm_traces.erl new file mode 100644 index 0000000..9f42355 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbit_tracing_wm_traces.erl @@ -0,0 +1,37 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_tracing_wm_traces). + +-export([init/1, to_json/2, content_types_provided/2, is_authorized/2]). + +-include_lib("rabbitmq_management/include/rabbit_mgmt.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). + +%%-------------------------------------------------------------------- + +init(_Config) -> {ok, #context{}}. + +content_types_provided(ReqData, Context) -> + {[{"application/json", to_json}], ReqData, Context}. + +to_json(ReqData, Context) -> + rabbit_mgmt_util:reply(rabbit_tracing_traces:list(), ReqData, Context). + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_admin(ReqData, Context). + +%%-------------------------------------------------------------------- diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbitmq_tracing.app.src b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbitmq_tracing.app.src new file mode 100644 index 0000000..df66878 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/src/rabbitmq_tracing.app.src @@ -0,0 +1,8 @@ +{application, rabbitmq_tracing, + [{description, "RabbitMQ message logging / tracing"}, + {vsn, "%%VSN%%"}, + {modules, []}, + {registered, []}, + {mod, {rabbit_tracing_app, []}}, + {env, [{directory, "/var/tmp/rabbitmq-tracing"}]}, + {applications, [kernel, stdlib, rabbit, rabbitmq_management]}]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/test/src/rabbit_tracing_test.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/test/src/rabbit_tracing_test.erl new file mode 100644 index 0000000..1c0ed0a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-tracing/test/src/rabbit_tracing_test.erl @@ -0,0 +1,186 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_tracing_test). + +-define(LOG_DIR, "/var/tmp/rabbitmq-tracing/"). + +-include_lib("eunit/include/eunit.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). +-include_lib("rabbitmq_management/include/rabbit_mgmt_test.hrl"). + +-import(rabbit_misc, [pget/2]). + +tracing_test() -> + case filelib:is_dir(?LOG_DIR) of + true -> {ok, Files} = file:list_dir(?LOG_DIR), + [ok = file:delete(?LOG_DIR ++ F) || F <- Files]; + _ -> ok + end, + + [] = http_get("/traces/%2f/"), + [] = http_get("/trace-files/"), + + Args = [{format, <<"json">>}, + {pattern, <<"#">>}], + http_put("/traces/%2f/test", Args, ?NO_CONTENT), + assert_list([[{name, <<"test">>}, + {format, <<"json">>}, + {pattern, <<"#">>}]], http_get("/traces/%2f/")), + assert_item([{name, <<"test">>}, + {format, <<"json">>}, + {pattern, <<"#">>}], http_get("/traces/%2f/test")), + + {ok, Conn} = amqp_connection:start(#amqp_params_network{}), + {ok, Ch} = amqp_connection:open_channel(Conn), + amqp_channel:cast(Ch, #'basic.publish'{ exchange = <<"amq.topic">>, + routing_key = <<"key">> }, + #amqp_msg{props = #'P_basic'{}, + payload = <<"Hello world">>}), + + amqp_channel:close(Ch), + amqp_connection:close(Conn), + + timer:sleep(100), + + http_delete("/traces/%2f/test", ?NO_CONTENT), + [] = http_get("/traces/%2f/"), + assert_list([[{name, <<"test.log">>}]], http_get("/trace-files/")), + %% This is a bit cheeky as the log is actually one JSON doc per + %% line and we assume here it's only one line + assert_item([{type, <<"published">>}, + {exchange, <<"amq.topic">>}, + {routing_keys, [<<"key">>]}, + {payload, base64:encode(<<"Hello world">>)}], + http_get("/trace-files/test.log")), + http_delete("/trace-files/test.log", ?NO_CONTENT), + ok. + +%%--------------------------------------------------------------------------- +%% Below is copypasta from rabbit_mgmt_test_http, it's not obvious how +%% to share that given the build system. + +http_get(Path) -> + http_get(Path, ?OK). + +http_get(Path, CodeExp) -> + http_get(Path, "guest", "guest", CodeExp). + +http_get(Path, User, Pass, CodeExp) -> + {ok, {{_HTTP, CodeAct, _}, Headers, ResBody}} = + req(get, Path, [auth_header(User, Pass)]), + assert_code(CodeExp, CodeAct, "GET", Path, ResBody), + decode(CodeExp, Headers, ResBody). + +http_put(Path, List, CodeExp) -> + http_put_raw(Path, format_for_upload(List), CodeExp). + +http_put(Path, List, User, Pass, CodeExp) -> + http_put_raw(Path, format_for_upload(List), User, Pass, CodeExp). + +http_post(Path, List, CodeExp) -> + http_post_raw(Path, format_for_upload(List), CodeExp). + +http_post(Path, List, User, Pass, CodeExp) -> + http_post_raw(Path, format_for_upload(List), User, Pass, CodeExp). + +format_for_upload(List) -> + iolist_to_binary(mochijson2:encode({struct, List})). + +http_put_raw(Path, Body, CodeExp) -> + http_upload_raw(put, Path, Body, "guest", "guest", CodeExp). + +http_put_raw(Path, Body, User, Pass, CodeExp) -> + http_upload_raw(put, Path, Body, User, Pass, CodeExp). + +http_post_raw(Path, Body, CodeExp) -> + http_upload_raw(post, Path, Body, "guest", "guest", CodeExp). + +http_post_raw(Path, Body, User, Pass, CodeExp) -> + http_upload_raw(post, Path, Body, User, Pass, CodeExp). + +http_upload_raw(Type, Path, Body, User, Pass, CodeExp) -> + {ok, {{_HTTP, CodeAct, _}, Headers, ResBody}} = + req(Type, Path, [auth_header(User, Pass)], Body), + assert_code(CodeExp, CodeAct, Type, Path, ResBody), + decode(CodeExp, Headers, ResBody). + +http_delete(Path, CodeExp) -> + http_delete(Path, "guest", "guest", CodeExp). + +http_delete(Path, User, Pass, CodeExp) -> + {ok, {{_HTTP, CodeAct, _}, Headers, ResBody}} = + req(delete, Path, [auth_header(User, Pass)]), + assert_code(CodeExp, CodeAct, "DELETE", Path, ResBody), + decode(CodeExp, Headers, ResBody). + +assert_code(CodeExp, CodeAct, Type, Path, Body) -> + case CodeExp of + CodeAct -> ok; + _ -> throw({expected, CodeExp, got, CodeAct, type, Type, + path, Path, body, Body}) + end. + +req(Type, Path, Headers) -> + httpc:request(Type, {?PREFIX ++ Path, Headers}, ?HTTPC_OPTS, []). + +req(Type, Path, Headers, Body) -> + httpc:request(Type, {?PREFIX ++ Path, Headers, "application/json", Body}, + ?HTTPC_OPTS, []). + +decode(?OK, _Headers, ResBody) -> cleanup(mochijson2:decode(ResBody)); +decode(_, Headers, _ResBody) -> Headers. + +cleanup(L) when is_list(L) -> + [cleanup(I) || I <- L]; +cleanup({struct, I}) -> + cleanup(I); +cleanup({K, V}) when is_binary(K) -> + {list_to_atom(binary_to_list(K)), cleanup(V)}; +cleanup(I) -> + I. + +auth_header(Username, Password) -> + {"Authorization", + "Basic " ++ binary_to_list(base64:encode(Username ++ ":" ++ Password))}. + +%%--------------------------------------------------------------------------- + +assert_list(Exp, Act) -> + case length(Exp) == length(Act) of + true -> ok; + false -> throw({expected, Exp, actual, Act}) + end, + [case length(lists:filter(fun(ActI) -> test_item(ExpI, ActI) end, Act)) of + 1 -> ok; + N -> throw({found, N, ExpI, in, Act}) + end || ExpI <- Exp]. + +assert_item(Exp, Act) -> + case test_item0(Exp, Act) of + [] -> ok; + Or -> throw(Or) + end. + +test_item(Exp, Act) -> + case test_item0(Exp, Act) of + [] -> true; + _ -> false + end. + +test_item0(Exp, Act) -> + [{did_not_find, ExpI, in, Act} || ExpI <- Exp, + not lists:member(ExpI, Act)]. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/.srcdist_done b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/.srcdist_done new file mode 100644 index 0000000..e69de29 diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/LICENSE b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/LICENSE new file mode 100644 index 0000000..7714141 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/LICENSE @@ -0,0 +1,470 @@ + MOZILLA PUBLIC LICENSE + Version 1.1 + + --------------- + +1. Definitions. + + 1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. + + 1.1. "Contributor" means each entity that creates or contributes to + the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications + made by that particular Contributor. + + 1.3. "Covered Code" means the Original Code or Modifications or the + combination of the Original Code and Modifications, in each case + including portions thereof. + + 1.4. "Electronic Distribution Mechanism" means a mechanism generally + accepted in the software development community for the electronic + transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source + Code. + + 1.6. "Initial Developer" means the individual or entity identified + as the Initial Developer in the Source Code notice required by Exhibit + A. + + 1.7. "Larger Work" means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + + 1.8. "License" means this document. + + 1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + A. Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. + + B. Any new file that contains any part of the Original Code or + previous Modifications. + + 1.10. "Original Code" means Source Code of computer software code + which is described in the Source Code notice required by Exhibit A as + Original Code, and which, at the time of its release under this + License is not already Covered Code governed by this License. + + 1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, + and apparatus claims, in any patent Licensable by grantor. + + 1.11. "Source Code" means the preferred form of the Covered Code for + making modifications to it, including all modules it contains, plus + any associated interface definition files, scripts used to control + compilation and installation of an Executable, or source code + differential comparisons against either the Original Code or another + well known, available Covered Code of the Contributor's choice. The + Source Code can be in a compressed or archival form, provided the + appropriate decompression or de-archiving software is widely available + for no charge. + + 1.12. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License or a future version of this License issued under Section 6.1. + For legal entities, "You" includes any entity which controls, is + controlled by, or is under common control with You. For purposes of + this definition, "control" means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty percent + (50%) of the outstanding shares or beneficial ownership of such + entity. + +2. Source Code License. + + 2.1. The Initial Developer Grant. + The Initial Developer hereby grants You a world-wide, royalty-free, + non-exclusive license, subject to third party intellectual property + claims: + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original + Code (or portions thereof) with or without Modifications, and/or + as part of a Larger Work; and + + (b) under Patents Claims infringed by the making, using or + selling of Original Code, to make, have made, use, practice, + sell, and offer for sale, and/or otherwise dispose of the + Original Code (or portions thereof). + + (c) the licenses granted in this Section 2.1(a) and (b) are + effective on the date Initial Developer first distributes + Original Code under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) + separate from the Original Code; or 3) for infringements caused + by: i) the modification of the Original Code or ii) the + combination of the Original Code with other software or devices. + + 2.2. Contributor Grant. + Subject to third party intellectual property claims, each Contributor + hereby grants You a world-wide, royalty-free, non-exclusive license + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor, to use, reproduce, modify, + display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an + unmodified basis, with other Modifications, as Covered Code + and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either alone + and/or in combination with its Contributor Version (or portions + of such combination), to make, use, sell, offer for sale, have + made, and/or otherwise dispose of: 1) Modifications made by that + Contributor (or portions thereof); and 2) the combination of + Modifications made by that Contributor with its Contributor + Version (or portions of such combination). + + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of + the Covered Code. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: 1) for any code that Contributor has deleted from the + Contributor Version; 2) separate from the Contributor Version; + 3) for infringements caused by: i) third party modifications of + Contributor Version or ii) the combination of Modifications made + by that Contributor with other software (except as part of the + Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by + that Contributor. + +3. Distribution Obligations. + + 3.1. Application of License. + The Modifications which You create or to which You contribute are + governed by the terms of this License, including without limitation + Section 2.2. The Source Code version of Covered Code may be + distributed only under the terms of this License or a future version + of this License released under Section 6.1, and You must include a + copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code + version that alters or restricts the applicable version of this + License or the recipients' rights hereunder. However, You may include + an additional document offering the additional rights described in + Section 3.5. + + 3.2. Availability of Source Code. + Any Modification which You create or to which You contribute must be + made available in Source Code form under the terms of this License + either on the same media as an Executable version or via an accepted + Electronic Distribution Mechanism to anyone to whom you made an + Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) + months after the date it initially became available, or at least six + (6) months after a subsequent version of that particular Modification + has been made available to such recipients. You are responsible for + ensuring that the Source Code version remains available even if the + Electronic Distribution Mechanism is maintained by a third party. + + 3.3. Description of Modifications. + You must cause all Covered Code to which You contribute to contain a + file documenting the changes You made to create that Covered Code and + the date of any change. You must include a prominent statement that + the Modification is derived, directly or indirectly, from Original + Code provided by the Initial Developer and including the name of the + Initial Developer in (a) the Source Code, and (b) in any notice in an + Executable version or related documentation in which You describe the + origin or ownership of the Covered Code. + + 3.4. Intellectual Property Matters + (a) Third Party Claims. + If Contributor has knowledge that a license under a third party's + intellectual property rights is required to exercise the rights + granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code + distribution titled "LEGAL" which describes the claim and the + party making the claim in sufficient detail that a recipient will + know whom to contact. If Contributor obtains such knowledge after + the Modification is made available as described in Section 3.2, + Contributor shall promptly modify the LEGAL file in all copies + Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) + reasonably calculated to inform those who received the Covered + Code that new knowledge has been obtained. + + (b) Contributor APIs. + If Contributor's Modifications include an application programming + interface and Contributor has knowledge of patent licenses which + are reasonably necessary to implement that API, Contributor must + also include this information in the LEGAL file. + + (c) Representations. + Contributor represents that, except as disclosed pursuant to + Section 3.4(a) above, Contributor believes that Contributor's + Modifications are Contributor's original creation(s) and/or + Contributor has sufficient rights to grant the rights conveyed by + this License. + + 3.5. Required Notices. + You must duplicate the notice in Exhibit A in each file of the Source + Code. If it is not possible to put such notice in a particular Source + Code file due to its structure, then You must include such notice in a + location (such as a relevant directory) where a user would be likely + to look for such a notice. If You created one or more Modification(s) + You may add your name as a Contributor to the notice described in + Exhibit A. You must also duplicate this License in any documentation + for the Source Code where You describe recipients' rights or ownership + rights relating to Covered Code. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear than + any such warranty, support, indemnity or liability obligation is + offered by You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of warranty, + support, indemnity or liability terms You offer. + + 3.6. Distribution of Executable Versions. + You may distribute Covered Code in Executable form only if the + requirements of Section 3.1-3.5 have been met for that Covered Code, + and if You include a notice stating that the Source Code version of + the Covered Code is available under the terms of this License, + including a description of how and where You have fulfilled the + obligations of Section 3.2. The notice must be conspicuously included + in any notice in an Executable version, related documentation or + collateral in which You describe recipients' rights relating to the + Covered Code. You may distribute the Executable version of Covered + Code or ownership rights under a license of Your choice, which may + contain terms different from this License, provided that You are in + compliance with the terms of this License and that the license for the + Executable version does not attempt to limit or alter the recipient's + rights in the Source Code version from the rights set forth in this + License. If You distribute the Executable version under a different + license You must make it absolutely clear that any terms which differ + from this License are offered by You alone, not by the Initial + Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such + terms You offer. + + 3.7. Larger Works. + You may create a Larger Work by combining Covered Code with other code + not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Code. + +4. Inability to Comply Due to Statute or Regulation. + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description + must be included in the LEGAL file described in Section 3.4 and must + be included with all distributions of the Source Code. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Application of this License. + + This License applies to code to which the Initial Developer has + attached the notice in Exhibit A and to related Covered Code. + +6. Versions of the License. + + 6.1. New Versions. + Netscape Communications Corporation ("Netscape") may publish revised + and/or new versions of the License from time to time. Each version + will be given a distinguishing version number. + + 6.2. Effect of New Versions. + Once Covered Code has been published under a particular version of the + License, You may always continue to use it under the terms of that + version. You may also choose to use such Covered Code under the terms + of any subsequent version of the License published by Netscape. No one + other than Netscape has the right to modify the terms applicable to + Covered Code created under this License. + + 6.3. Derivative Works. + If You create or use a modified version of this License (which you may + only do in order to apply it to code which is not already Covered Code + governed by this License), You must (a) rename Your license so that + the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", + "MPL", "NPL" or any confusingly similar phrase do not appear in your + license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license + contains terms which differ from the Mozilla Public License and + Netscape Public License. (Filling in the name of the Initial + Developer, Original Code or Contributor in the notice described in + Exhibit A shall not of themselves be deemed to be modifications of + this License.) + +7. DISCLAIMER OF WARRANTY. + + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE + IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, + YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE + COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER + OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +8. TERMINATION. + + 8.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall + survive any termination of this License. Provisions which, by their + nature, must remain in effect beyond the termination of this License + shall survive. + + 8.2. If You initiate litigation by asserting a patent infringement + claim (excluding declatory judgment actions) against Initial Developer + or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + + (a) such Participant's Contributor Version directly or indirectly + infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License + shall, upon 60 days notice from Participant terminate prospectively, + unless if within 60 days after receipt of notice You either: (i) + agree in writing to pay Participant a mutually agreeable reasonable + royalty for Your past and future use of Modifications made by such + Participant, or (ii) withdraw Your litigation claim with respect to + the Contributor Version against such Participant. If within 60 days + of notice, a reasonable royalty and payment arrangement are not + mutually agreed upon in writing by the parties or the litigation claim + is not withdrawn, the rights granted by Participant to You under + Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + + (b) any software, hardware, or device, other than such Participant's + Contributor Version, directly or indirectly infringes any patent, then + any rights granted to You by such Participant under Sections 2.1(b) + and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that + Participant. + + 8.3. If You assert a patent infringement claim against Participant + alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as + by license or settlement) prior to the initiation of patent + infringement litigation, then the reasonable value of the licenses + granted by such Participant under Sections 2.1 or 2.2 shall be taken + into account in determining the amount or value of any payment or + license. + + 8.4. In the event of termination under Sections 8.1 or 8.2 above, + all end user license agreements (excluding distributors and resellers) + which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination. + +9. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, + OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR + ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY + CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, + WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE + EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO + THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +10. U.S. GOVERNMENT END USERS. + + The Covered Code is a "commercial item," as that term is defined in + 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer + software" and "commercial computer software documentation," as such + terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 + C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), + all U.S. Government End Users acquire Covered Code with only those + rights set forth herein. + +11. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed by + California law provisions (except to the extent applicable law, if + any, provides otherwise), excluding its conflict-of-law provisions. + With respect to disputes in which at least one party is a citizen of, + or an entity chartered or registered to do business in the United + States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern + District of California, with venue lying in Santa Clara County, + California, with the losing party responsible for costs, including + without limitation, court costs and reasonable attorneys' fees and + expenses. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. + Any law or regulation which provides that the language of a contract + shall be construed against the drafter shall not apply to this + License. + +12. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, + out of its utilization of rights under this License and You agree to + work with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + +13. MULTIPLE-LICENSED CODE. + + Initial Developer may designate portions of the Covered Code as + "Multiple-Licensed". "Multiple-Licensed" means that the Initial + Developer permits you to utilize portions of the Covered Code under + Your choice of the NPL or the alternative licenses, if any, specified + by the Initial Developer in the file described in Exhibit A. + +EXHIBIT A -Mozilla Public License. + + ``The contents of this file are subject to the Mozilla Public License + Version 1.1 (the "License"); you may not use this file except in + compliance with the License. You may obtain a copy of the License at + http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + License for the specific language governing rights and limitations + under the License. + + The Original Code is ______________________________________. + + The Initial Developer of the Original Code is ________________________. + Portions created by ______________________ are Copyright (C) ______ + _______________________. All Rights Reserved. + + Contributor(s): ______________________________________. + + Alternatively, the contents of this file may be used under the terms + of the _____ license (the "[___] License"), in which case the + provisions of [______] License are applicable instead of those + above. If you wish to allow use of your version of this file only + under the terms of the [____] License and not to allow others to use + your version of this file under the MPL, indicate your decision by + deleting the provisions above and replace them with the notice and + other provisions required by the [___] License. If you do not delete + the provisions above, a recipient may use your version of this file + under either the MPL or the [___] License." + + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.] + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/Makefile b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/Makefile new file mode 100644 index 0000000..482105a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/Makefile @@ -0,0 +1 @@ +include ../umbrella.mk diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/README.md b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/README.md new file mode 100644 index 0000000..425ef6d --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/README.md @@ -0,0 +1,25 @@ +rabbitmq-web-dispatch +--------------------- + +rabbitmq-web-dispatch is a thin veneer around mochiweb that provides the +ability for multiple applications to co-exist on mochiweb +listeners. Applications can register static docroots or dynamic +handlers to be executed, dispatched by URL path prefix. + +See http://www.rabbitmq.com/mochiweb.html for information on +configuring web plugins. + +The most general registration procedure is +`rabbit_web_dispatch:register_context_handler/5`. This takes a callback +procedure of the form + + loop(Request) -> + ... + +The module `rabbit_webmachine` provides a means of running more than +one webmachine in a VM, and understands rabbitmq-web-dispatch contexts. To +use it, supply a dispatch table term of the kind usually given to +webmachine in the file `priv/dispatch.conf`. + +`setup/{1,2}` in the same module allows some global configuration of +webmachine logging and error handling. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/package.mk b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/package.mk new file mode 100644 index 0000000..d5913ca --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/package.mk @@ -0,0 +1,3 @@ +DEPS:=mochiweb-wrapper webmachine-wrapper +WITH_BROKER_TEST_COMMANDS:=rabbit_web_dispatch_test:test() +STANDALONE_TEST_COMMANDS:=rabbit_web_dispatch_test_unit:test() diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_web_dispatch.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_web_dispatch.erl new file mode 100644 index 0000000..c328c1f --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_web_dispatch.erl @@ -0,0 +1,114 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_web_dispatch). + +-export([register_context_handler/5, register_static_context/6]). +-export([register_port_redirect/4]). +-export([unregister_context/1]). + +%% Handler Registration + +%% Registers a dynamic selector and handler combination, with a link +%% to display in lists. +register_handler(Name, Listener, Selector, Handler, Link) -> + rabbit_web_dispatch_registry:add(Name, Listener, Selector, Handler, Link). + +%% Methods for standard use cases + +%% Registers a dynamic handler under a fixed context path, with link +%% to display in the global context. +register_context_handler(Name, Listener, Prefix, Handler, LinkText) -> + register_handler( + Name, Listener, context_selector(Prefix), Handler, {Prefix, LinkText}), + {ok, Prefix}. + +%% Convenience function registering a fully static context to serve +%% content from a module-relative directory, with link to display in +%% the global context. +register_static_context(Name, Listener, Prefix, Module, FSPath, LinkText) -> + register_handler(Name, Listener, + context_selector(Prefix), + static_context_handler(Prefix, Module, FSPath), + {Prefix, LinkText}), + {ok, Prefix}. + +%% A context which just redirects the request to a different port. +register_port_redirect(Name, Listener, Prefix, RedirectPort) -> + register_context_handler( + Name, Listener, Prefix, + fun (Req) -> + Host = case Req:get_header_value("host") of + undefined -> {ok, {IP, _Port}} = rabbit_net:sockname( + Req:get(socket)), + rabbit_misc:ntoa(IP); + Header -> hd(string:tokens(Header, ":")) + end, + URL = rabbit_misc:format( + "~s://~s:~B~s", + [Req:get(scheme), Host, RedirectPort, Req:get(raw_path)]), + Req:respond({301, [{"Location", URL}], ""}) + end, + rabbit_misc:format("Redirect to port ~B", [RedirectPort])). + +context_selector("") -> + fun(_Req) -> true end; +context_selector(Prefix) -> + Prefix1 = "/" ++ Prefix, + fun(Req) -> + Path = Req:get(raw_path), + (Path == Prefix1) orelse (string:str(Path, Prefix1 ++ "/") == 1) + end. + +%% Produces a handler for use with register_handler that serves up +%% static content from a directory specified relative to the directory +%% containing the ebin directory containing the named module's beam +%% file. +static_context_handler(Prefix, Module, FSPath) -> + {file, Here} = code:is_loaded(Module), + ModuleRoot = filename:dirname(filename:dirname(Here)), + LocalPath = filename:join(ModuleRoot, FSPath), + static_context_handler(Prefix, LocalPath). + +%% Produces a handler for use with register_handler that serves up +%% static content from a specified directory. +static_context_handler("", LocalPath) -> + fun(Req) -> + "/" ++ Path = Req:get(raw_path), + serve_file(Req, Path, LocalPath) + end; +static_context_handler(Prefix, LocalPath) -> + fun(Req) -> + "/" ++ Path = Req:get(raw_path), + case string:substr(Path, length(Prefix) + 1) of + "" -> Req:respond({301, [{"Location", "/" ++ Prefix ++ "/"}], ""}); + "/" ++ P -> serve_file(Req, P, LocalPath) + end + end. + +serve_file(Req, Path, LocalPath) -> + case Req:get(method) of + Method when Method =:= 'GET'; Method =:= 'HEAD' -> + Req:serve_file(Path, LocalPath); + _ -> + Req:respond({405, [{"Allow", "GET, HEAD"}], + "Only GET or HEAD supported for static content"}) + end. + +%% The opposite of all those register_* functions. +unregister_context(Name) -> + rabbit_web_dispatch_registry:remove(Name). + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_web_dispatch_app.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_web_dispatch_app.erl new file mode 100644 index 0000000..478b3fd --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_web_dispatch_app.erl @@ -0,0 +1,30 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_web_dispatch_app). + +-behaviour(application). +-export([start/2,stop/1]). + +%% @spec start(_Type, _StartArgs) -> ServerRet +%% @doc application start callback for rabbit_web_dispatch. +start(_Type, _StartArgs) -> + rabbit_web_dispatch_sup:start_link(). + +%% @spec stop(_State) -> ServerRet +%% @doc application stop callback for rabbit_web_dispatch. +stop(_State) -> + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_web_dispatch_registry.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_web_dispatch_registry.erl new file mode 100644 index 0000000..93eb4d4 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_web_dispatch_registry.erl @@ -0,0 +1,199 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_web_dispatch_registry). + +-behaviour(gen_server). + +-export([start_link/0]). +-export([add/5, remove/1, set_fallback/2, lookup/2, list_all/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3]). + +-define(ETS, rabbitmq_web_dispatch). + +%% This gen_server is merely to serialise modifications to the dispatch +%% table for listeners. + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +add(Name, Listener, Selector, Handler, Link) -> + gen_server:call(?MODULE, {add, Name, Listener, Selector, Handler, Link}, + infinity). + +remove(Name) -> + gen_server:call(?MODULE, {remove, Name}, infinity). + +set_fallback(Listener, FallbackHandler) -> + gen_server:call(?MODULE, {set_fallback, Listener, FallbackHandler}, + infinity). + +lookup(Listener, Req) -> + case lookup_dispatch(Listener) of + {ok, {Selectors, Fallback}} -> + case catch match_request(Selectors, Req) of + {'EXIT', Reason} -> {lookup_failure, Reason}; + no_handler -> {handler, Fallback}; + Handler -> {handler, Handler} + end; + Err -> + Err + end. + +%% This is called in a somewhat obfuscated manner in +%% rabbit_mgmt_external_stats:rabbit_web_dispatch_registry_list_all() +list_all() -> + gen_server:call(?MODULE, list_all, infinity). + +%% Callback Methods + +init([]) -> + ?ETS = ets:new(?ETS, [named_table, public]), + {ok, undefined}. + +handle_call({add, Name, Listener, Selector, Handler, Link = {_, Desc}}, _From, + undefined) -> + Continue = case rabbit_web_dispatch_sup:ensure_listener(Listener) of + new -> set_dispatch( + Listener, [], + listing_fallback_handler(Listener)), + true; + existing -> true; + ignore -> false + end, + case Continue of + true -> case lookup_dispatch(Listener) of + {ok, {Selectors, Fallback}} -> + Selector2 = lists:keystore( + Name, 1, Selectors, + {Name, Selector, Handler, Link}), + set_dispatch(Listener, Selector2, Fallback); + {error, {different, Desc2, Listener2}} -> + exit({incompatible_listeners, + {Desc, Listener}, {Desc2, Listener2}}) + end; + false -> ok + end, + {reply, ok, undefined}; + +handle_call({remove, Name}, _From, + undefined) -> + Listener = listener_by_name(Name), + {ok, {Selectors, Fallback}} = lookup_dispatch(Listener), + Selectors1 = lists:keydelete(Name, 1, Selectors), + set_dispatch(Listener, Selectors1, Fallback), + case Selectors1 of + [] -> rabbit_web_dispatch_sup:stop_listener(Listener); + _ -> ok + end, + {reply, ok, undefined}; + +handle_call({set_fallback, Listener, FallbackHandler}, _From, + undefined) -> + {ok, {Selectors, _OldFallback}} = lookup_dispatch(Listener), + set_dispatch(Listener, Selectors, FallbackHandler), + {reply, ok, undefined}; + +handle_call(list_all, _From, undefined) -> + {reply, list(), undefined}; + +handle_call(Req, _From, State) -> + error_logger:format("Unexpected call to ~p: ~p~n", [?MODULE, Req]), + {stop, unknown_request, State}. + +handle_cast(_, State) -> + {noreply, State}. + +handle_info(_, State) -> + {noreply, State}. + +terminate(_, _) -> + true = ets:delete(?ETS), + ok. + +code_change(_, State, _) -> + {ok, State}. + +%%--------------------------------------------------------------------------- + +%% Internal Methods + +port(Listener) -> proplists:get_value(port, Listener). + +lookup_dispatch(Lsnr) -> + case ets:lookup(?ETS, port(Lsnr)) of + [{_, Lsnr, S, F}] -> {ok, {S, F}}; + [{_, Lsnr2, S, _F}] -> {error, {different, first_desc(S), Lsnr2}}; + [] -> {error, {no_record_for_listener, Lsnr}} + end. + +first_desc([{_N, _S, _H, {_, Desc}} | _]) -> Desc. + +set_dispatch(Listener, Selectors, Fallback) -> + ets:insert(?ETS, {port(Listener), Listener, Selectors, Fallback}). + +match_request([], _) -> + no_handler; +match_request([{_Name, Selector, Handler, _Link}|Rest], Req) -> + case Selector(Req) of + true -> Handler; + false -> match_request(Rest, Req) + end. + +list() -> + [{Path, Desc, Listener} || + {_P, Listener, Selectors, _F} <- ets:tab2list(?ETS), + {_N, _S, _H, {Path, Desc}} <- Selectors]. + +listener_by_name(Name) -> + case [L || {_P, L, S, _F} <- ets:tab2list(?ETS), contains_name(Name, S)] of + [Listener] -> Listener; + [] -> exit({not_found, Name}) + end. + +contains_name(Name, Selectors) -> + lists:member(Name, [N || {N, _S, _H, _L} <- Selectors]). + +list(Listener) -> + {ok, {Selectors, _Fallback}} = lookup_dispatch(Listener), + [{Path, Desc} || {_N, _S, _H, {Path, Desc}} <- Selectors]. + +%%--------------------------------------------------------------------------- + +listing_fallback_handler(Listener) -> + fun(Req) -> + HTMLPrefix = + "" + "RabbitMQ Web Server" + "

RabbitMQ Web Server

Contexts available:

    ", + HTMLSuffix = "
", + {ReqPath, _, _} = mochiweb_util:urlsplit_path(Req:get(raw_path)), + List = + case list(Listener) of + [] -> + "
  • No contexts installed
  • "; + Contexts -> + [handler_listing(Path, ReqPath, Desc) + || {Path, Desc} <- Contexts] + end, + Req:respond({200, [], HTMLPrefix ++ List ++ HTMLSuffix}) + end. + +handler_listing(Path, ReqPath, Desc) -> + io_lib:format( + "
  • ~s
  • ", + [rabbit_web_dispatch_util:relativise(ReqPath, "/" ++ Path), Desc]). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_web_dispatch_sup.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_web_dispatch_sup.erl new file mode 100644 index 0000000..83be7b1 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_web_dispatch_sup.erl @@ -0,0 +1,104 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_web_dispatch_sup). + +-behaviour(supervisor). + +-define(SUP, ?MODULE). + +%% External exports +-export([start_link/0, ensure_listener/1, stop_listener/1]). + +%% supervisor callbacks +-export([init/1]). + +%% @spec start_link() -> ServerRet +%% @doc API for starting the supervisor. +start_link() -> + supervisor:start_link({local, ?SUP}, ?MODULE, []). + +ensure_listener(Listener) -> + case proplists:get_value(port, Listener) of + undefined -> + {error, {no_port_given, Listener}}; + _ -> + Child = {{rabbit_web_dispatch_web, name(Listener)}, + {mochiweb_http, start, [mochi_options(Listener)]}, + transient, 5000, worker, dynamic}, + case supervisor:start_child(?SUP, Child) of + {ok, _} -> new; + {error, {already_started, _}} -> existing; + {error, {E, _}} -> check_error(Listener, E) + end + end. + +stop_listener(Listener) -> + Name = name(Listener), + ok = supervisor:terminate_child(?SUP, {rabbit_web_dispatch_web, Name}), + ok = supervisor:delete_child(?SUP, {rabbit_web_dispatch_web, Name}). + +%% @spec init([[instance()]]) -> SupervisorTree +%% @doc supervisor callback. +init([]) -> + Registry = {rabbit_web_dispatch_registry, + {rabbit_web_dispatch_registry, start_link, []}, + transient, 5000, worker, dynamic}, + {ok, {{one_for_one, 10, 10}, [Registry]}}. + +%% ---------------------------------------------------------------------- + +mochi_options(Listener) -> + [{name, name(Listener)}, + {loop, loopfun(Listener)} | + easy_ssl(proplists:delete( + name, proplists:delete(ignore_in_use, Listener)))]. + +loopfun(Listener) -> + fun (Req) -> + case rabbit_web_dispatch_registry:lookup(Listener, Req) of + no_handler -> + Req:not_found(); + {error, Reason} -> + Req:respond({500, [], "Registry Error: " ++ Reason}); + {handler, Handler} -> + Handler(Req) + end + end. + +name(Listener) -> + Port = proplists:get_value(port, Listener), + list_to_atom(atom_to_list(?MODULE) ++ "_" ++ integer_to_list(Port)). + +easy_ssl(Options) -> + case {proplists:get_value(ssl, Options), + proplists:get_value(ssl_opts, Options)} of + {true, undefined} -> + {ok, ServerOpts} = application:get_env(rabbit, ssl_options), + SSLOpts = [{K, V} || + {K, V} <- ServerOpts, + not lists:member(K, [verify, fail_if_no_peer_cert])], + [{ssl_opts, SSLOpts}|Options]; + _ -> + Options + end. + +check_error(Listener, Error) -> + Ignore = proplists:get_value(ignore_in_use, Listener, false), + case {Error, Ignore} of + {eaddrinuse, true} -> ignore; + _ -> exit({could_not_start_listener, Listener, Error}) + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_web_dispatch_util.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_web_dispatch_util.erl new file mode 100644 index 0000000..c031138 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_web_dispatch_util.erl @@ -0,0 +1,63 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_web_dispatch_util). + +-export([parse_auth_header/1]). +-export([relativise/2, unrelativise/2]). + +parse_auth_header(Header) -> + case Header of + "Basic " ++ Base64 -> + Str = base64:mime_decode_to_string(Base64), + case string:chr(Str, $:) of + 0 -> invalid; + N -> [list_to_binary(string:sub_string(Str, 1, N - 1)), + list_to_binary(string:sub_string(Str, N + 1))] + end; + _ -> + invalid + end. + +relativise("/" ++ F, "/" ++ T) -> + From = string:tokens(F, "/"), + To = string:tokens(T, "/"), + string:join(relativise0(From, To), "/"). + +relativise0([H], [H|_] = To) -> + To; +relativise0([H|From], [H|To]) -> + relativise0(From, To); +relativise0(From, []) -> + lists:duplicate(length(From), ".."); +relativise0([_|From], To) -> + lists:duplicate(length(From), "..") ++ To; +relativise0([], To) -> + To. + +unrelativise(F, "/" ++ T) -> "/" ++ T; +unrelativise(F, "./" ++ T) -> unrelativise(F, T); +unrelativise(F, "../" ++ T) -> unrelativise(strip_tail(F), T); +unrelativise(F, T) -> case string:str(F, "/") of + 0 -> T; + _ -> strip_tail(F) ++ "/" ++ T + end. + +strip_tail("") -> exit(not_enough_to_strip); +strip_tail(S) -> case string:rstr(S, "/") of + 0 -> ""; + I -> string:left(S, I - 1) + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_webmachine.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_webmachine.erl new file mode 100644 index 0000000..b62e7ad --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbit_webmachine.erl @@ -0,0 +1,67 @@ +%% This file contains an adapted version of webmachine_mochiweb:loop/1 +%% from webmachine (revision 0c4b60ac68b4). + +%% All modifications are (C) 2011-2013 GoPivotal, Inc. + +-module(rabbit_webmachine). + +%% An alternative to webmachine_mochiweb, which places the dispatch +%% table (among other things) into the application env, and thereby +%% makes it impossible to run more than one instance of +%% webmachine. Since rabbit_web_dispatch is all about multi-tenanting +%% webapps, clearly this won't do for us. + +%% Instead of using webmachine_mochiweb:start/1 or +%% webmachine_mochiweb:loop/1, construct a loop procedure using +%% makeloop/1 and supply it as the argument to +%% rabbit_web_dispatch:register_context_handler or to mochiweb_http:start. + +%% We hardwire the "error handler" and use a "logging module" if +%% supplied. + +-export([makeloop/1, setup/0]). + +setup() -> + application:set_env(webmachine, error_handler, webmachine_error_handler). + +makeloop(Dispatch) -> + fun (MochiReq) -> + Req = webmachine:new_request(mochiweb, MochiReq), + {Path, _} = Req:path(), + {ReqData, _} = Req:get_reqdata(), + %% webmachine_mochiweb:loop/1 uses dispatch/4 here; + %% however, we don't need to dispatch by the host name. + case webmachine_dispatcher:dispatch(Path, Dispatch, ReqData) of + {no_dispatch_match, _Host, _PathElements} -> + {ErrorHTML, ReqState1} = + webmachine_error_handler:render_error( + 404, Req, {none, none, []}), + Req1 = {webmachine_request, ReqState1}, + {ok, ReqState2} = Req1:append_to_response_body(ErrorHTML), + Req2 = {webmachine_request, ReqState2}, + {ok, ReqState3} = Req2:send_response(404), + maybe_log_access(ReqState3); + {Mod, ModOpts, HostTokens, Port, PathTokens, Bindings, + AppRoot, StringPath} -> + BootstrapResource = webmachine_resource:new(x,x,x,x), + {ok, Resource} = BootstrapResource:wrap(Mod, ModOpts), + {ok, RS1} = Req:load_dispatch_data(Bindings, HostTokens, Port, + PathTokens, + AppRoot, StringPath), + XReq1 = {webmachine_request, RS1}, + {ok, RS2} = XReq1:set_metadata('resource_module', Mod), + try + webmachine_decision_core:handle_request(Resource, RS2) + catch + error:_ -> + FailReq = {webmachine_request, RS2}, + {ok, RS3} = FailReq:send_response(500), + maybe_log_access(RS3) + end + end + end. + +maybe_log_access(ReqState) -> + Req = {webmachine_request, ReqState}, + {LogData, _ReqState1} = Req:log_data(), + webmachine_log:log_access(LogData). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbitmq_web_dispatch.app.src b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbitmq_web_dispatch.app.src new file mode 100644 index 0000000..5e7dd4d --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/src/rabbitmq_web_dispatch.app.src @@ -0,0 +1,8 @@ +{application, rabbitmq_web_dispatch, + [{description, "RabbitMQ Web Dispatcher"}, + {vsn, "%%VSN%%"}, + {modules, []}, + {registered, []}, + {mod, {rabbit_web_dispatch_app, []}}, + {env, []}, + {applications, [kernel, stdlib, mochiweb, webmachine]}]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/test/priv/www/index.html b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/test/priv/www/index.html new file mode 100644 index 0000000..b9f7cd4 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/test/priv/www/index.html @@ -0,0 +1,7 @@ + + + RabbitMQ HTTP Server Test Page + + + + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/test/src/rabbit_web_dispatch_test.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/test/src/rabbit_web_dispatch_test.erl new file mode 100644 index 0000000..139dccc --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/test/src/rabbit_web_dispatch_test.erl @@ -0,0 +1,38 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_web_dispatch_test). + +-include_lib("eunit/include/eunit.hrl"). + +query_static_resource_test() -> + %% TODO this is a fairly rubbish test, but not as bad as it was + rabbit_web_dispatch:register_static_context(test, [{port, 12345}], + "rabbit_web_dispatch_test", + ?MODULE, "priv/www", "Test"), + {ok, {_Status, _Headers, Body}} = + httpc:request("http://localhost:12345/rabbit_web_dispatch_test/index.html"), + ?assert(string:str(Body, "RabbitMQ HTTP Server Test Page") /= 0). + +add_idempotence_test() -> + F = fun(_Req) -> ok end, + L = {"/foo", "Foo"}, + rabbit_web_dispatch_registry:add(foo, [{port, 12345}], F, F, L), + rabbit_web_dispatch_registry:add(foo, [{port, 12345}], F, F, L), + ?assertEqual( + 1, length([ok || {"/foo", _, _} <- + rabbit_web_dispatch_registry:list_all()])), + passed. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/test/src/rabbit_web_dispatch_test_unit.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/test/src/rabbit_web_dispatch_test_unit.erl new file mode 100644 index 0000000..b90ed40 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-dispatch/test/src/rabbit_web_dispatch_test_unit.erl @@ -0,0 +1,36 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_web_dispatch_test_unit). + +-include_lib("eunit/include/eunit.hrl"). + +relativise_test() -> + Rel = fun rabbit_web_dispatch_util:relativise/2, + ?assertEqual("baz", Rel("/foo/bar/bash", "/foo/bar/baz")), + ?assertEqual("../bax/baz", Rel("/foo/bar/bash", "/foo/bax/baz")), + ?assertEqual("../bax/baz", Rel("/bar/bash", "/bax/baz")), + ?assertEqual("..", Rel("/foo/bar/bash", "/foo/bar")), + ?assertEqual("../..", Rel("/foo/bar/bash", "/foo")), + ?assertEqual("bar/baz", Rel("/foo/bar", "/foo/bar/baz")), + ?assertEqual("foo", Rel("/", "/foo")). + +unrelativise_test() -> + Un = fun rabbit_web_dispatch_util:unrelativise/2, + ?assertEqual("/foo/bar", Un("/foo/foo", "bar")), + ?assertEqual("/foo/bar", Un("/foo/foo", "./bar")), + ?assertEqual("bar", Un("foo", "bar")), + ?assertEqual("/baz/bar", Un("/foo/foo", "../baz/bar")). diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/.srcdist_done b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/.srcdist_done new file mode 100644 index 0000000..e69de29 diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/LICENSE b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/LICENSE new file mode 100644 index 0000000..012eade --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/LICENSE @@ -0,0 +1,9 @@ +This package, the rabbitmq-web-stomp-examples, is licensed under the +MPL. For the MPL, please see LICENSE-MPL-RabbitMQ. + +priv/stomp.js is a part of stomp-websocket project +(https://github.com/jmesnil/stomp-websocket) and is released under +APL2. For the license see LICENSE-APL2-Stomp-Websocket. + +If you have any questions regarding licensing, please contact us at +info@rabbitmq.com. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/LICENSE-APL2-Stomp-Websocket b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/LICENSE-APL2-Stomp-Websocket new file mode 100644 index 0000000..ef51da2 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/LICENSE-APL2-Stomp-Websocket @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/LICENSE-MPL-RabbitMQ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/LICENSE-MPL-RabbitMQ new file mode 100644 index 0000000..c87c1a3 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/LICENSE-MPL-RabbitMQ @@ -0,0 +1,455 @@ + MOZILLA PUBLIC LICENSE + Version 1.1 + + --------------- + +1. Definitions. + + 1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. + + 1.1. "Contributor" means each entity that creates or contributes to + the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications + made by that particular Contributor. + + 1.3. "Covered Code" means the Original Code or Modifications or the + combination of the Original Code and Modifications, in each case + including portions thereof. + + 1.4. "Electronic Distribution Mechanism" means a mechanism generally + accepted in the software development community for the electronic + transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source + Code. + + 1.6. "Initial Developer" means the individual or entity identified + as the Initial Developer in the Source Code notice required by Exhibit + A. + + 1.7. "Larger Work" means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + + 1.8. "License" means this document. + + 1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + A. Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. + + B. Any new file that contains any part of the Original Code or + previous Modifications. + + 1.10. "Original Code" means Source Code of computer software code + which is described in the Source Code notice required by Exhibit A as + Original Code, and which, at the time of its release under this + License is not already Covered Code governed by this License. + + 1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, + and apparatus claims, in any patent Licensable by grantor. + + 1.11. "Source Code" means the preferred form of the Covered Code for + making modifications to it, including all modules it contains, plus + any associated interface definition files, scripts used to control + compilation and installation of an Executable, or source code + differential comparisons against either the Original Code or another + well known, available Covered Code of the Contributor's choice. The + Source Code can be in a compressed or archival form, provided the + appropriate decompression or de-archiving software is widely available + for no charge. + + 1.12. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License or a future version of this License issued under Section 6.1. + For legal entities, "You" includes any entity which controls, is + controlled by, or is under common control with You. For purposes of + this definition, "control" means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty percent + (50%) of the outstanding shares or beneficial ownership of such + entity. + +2. Source Code License. + + 2.1. The Initial Developer Grant. + The Initial Developer hereby grants You a world-wide, royalty-free, + non-exclusive license, subject to third party intellectual property + claims: + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original + Code (or portions thereof) with or without Modifications, and/or + as part of a Larger Work; and + + (b) under Patents Claims infringed by the making, using or + selling of Original Code, to make, have made, use, practice, + sell, and offer for sale, and/or otherwise dispose of the + Original Code (or portions thereof). + + (c) the licenses granted in this Section 2.1(a) and (b) are + effective on the date Initial Developer first distributes + Original Code under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) + separate from the Original Code; or 3) for infringements caused + by: i) the modification of the Original Code or ii) the + combination of the Original Code with other software or devices. + + 2.2. Contributor Grant. + Subject to third party intellectual property claims, each Contributor + hereby grants You a world-wide, royalty-free, non-exclusive license + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor, to use, reproduce, modify, + display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an + unmodified basis, with other Modifications, as Covered Code + and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either alone + and/or in combination with its Contributor Version (or portions + of such combination), to make, use, sell, offer for sale, have + made, and/or otherwise dispose of: 1) Modifications made by that + Contributor (or portions thereof); and 2) the combination of + Modifications made by that Contributor with its Contributor + Version (or portions of such combination). + + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of + the Covered Code. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: 1) for any code that Contributor has deleted from the + Contributor Version; 2) separate from the Contributor Version; + 3) for infringements caused by: i) third party modifications of + Contributor Version or ii) the combination of Modifications made + by that Contributor with other software (except as part of the + Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by + that Contributor. + +3. Distribution Obligations. + + 3.1. Application of License. + The Modifications which You create or to which You contribute are + governed by the terms of this License, including without limitation + Section 2.2. The Source Code version of Covered Code may be + distributed only under the terms of this License or a future version + of this License released under Section 6.1, and You must include a + copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code + version that alters or restricts the applicable version of this + License or the recipients' rights hereunder. However, You may include + an additional document offering the additional rights described in + Section 3.5. + + 3.2. Availability of Source Code. + Any Modification which You create or to which You contribute must be + made available in Source Code form under the terms of this License + either on the same media as an Executable version or via an accepted + Electronic Distribution Mechanism to anyone to whom you made an + Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) + months after the date it initially became available, or at least six + (6) months after a subsequent version of that particular Modification + has been made available to such recipients. You are responsible for + ensuring that the Source Code version remains available even if the + Electronic Distribution Mechanism is maintained by a third party. + + 3.3. Description of Modifications. + You must cause all Covered Code to which You contribute to contain a + file documenting the changes You made to create that Covered Code and + the date of any change. You must include a prominent statement that + the Modification is derived, directly or indirectly, from Original + Code provided by the Initial Developer and including the name of the + Initial Developer in (a) the Source Code, and (b) in any notice in an + Executable version or related documentation in which You describe the + origin or ownership of the Covered Code. + + 3.4. Intellectual Property Matters + (a) Third Party Claims. + If Contributor has knowledge that a license under a third party's + intellectual property rights is required to exercise the rights + granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code + distribution titled "LEGAL" which describes the claim and the + party making the claim in sufficient detail that a recipient will + know whom to contact. If Contributor obtains such knowledge after + the Modification is made available as described in Section 3.2, + Contributor shall promptly modify the LEGAL file in all copies + Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) + reasonably calculated to inform those who received the Covered + Code that new knowledge has been obtained. + + (b) Contributor APIs. + If Contributor's Modifications include an application programming + interface and Contributor has knowledge of patent licenses which + are reasonably necessary to implement that API, Contributor must + also include this information in the LEGAL file. + + (c) Representations. + Contributor represents that, except as disclosed pursuant to + Section 3.4(a) above, Contributor believes that Contributor's + Modifications are Contributor's original creation(s) and/or + Contributor has sufficient rights to grant the rights conveyed by + this License. + + 3.5. Required Notices. + You must duplicate the notice in Exhibit A in each file of the Source + Code. If it is not possible to put such notice in a particular Source + Code file due to its structure, then You must include such notice in a + location (such as a relevant directory) where a user would be likely + to look for such a notice. If You created one or more Modification(s) + You may add your name as a Contributor to the notice described in + Exhibit A. You must also duplicate this License in any documentation + for the Source Code where You describe recipients' rights or ownership + rights relating to Covered Code. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear than + any such warranty, support, indemnity or liability obligation is + offered by You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of warranty, + support, indemnity or liability terms You offer. + + 3.6. Distribution of Executable Versions. + You may distribute Covered Code in Executable form only if the + requirements of Section 3.1-3.5 have been met for that Covered Code, + and if You include a notice stating that the Source Code version of + the Covered Code is available under the terms of this License, + including a description of how and where You have fulfilled the + obligations of Section 3.2. The notice must be conspicuously included + in any notice in an Executable version, related documentation or + collateral in which You describe recipients' rights relating to the + Covered Code. You may distribute the Executable version of Covered + Code or ownership rights under a license of Your choice, which may + contain terms different from this License, provided that You are in + compliance with the terms of this License and that the license for the + Executable version does not attempt to limit or alter the recipient's + rights in the Source Code version from the rights set forth in this + License. If You distribute the Executable version under a different + license You must make it absolutely clear that any terms which differ + from this License are offered by You alone, not by the Initial + Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such + terms You offer. + + 3.7. Larger Works. + You may create a Larger Work by combining Covered Code with other code + not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Code. + +4. Inability to Comply Due to Statute or Regulation. + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description + must be included in the LEGAL file described in Section 3.4 and must + be included with all distributions of the Source Code. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Application of this License. + + This License applies to code to which the Initial Developer has + attached the notice in Exhibit A and to related Covered Code. + +6. Versions of the License. + + 6.1. New Versions. + Netscape Communications Corporation ("Netscape") may publish revised + and/or new versions of the License from time to time. Each version + will be given a distinguishing version number. + + 6.2. Effect of New Versions. + Once Covered Code has been published under a particular version of the + License, You may always continue to use it under the terms of that + version. You may also choose to use such Covered Code under the terms + of any subsequent version of the License published by Netscape. No one + other than Netscape has the right to modify the terms applicable to + Covered Code created under this License. + + 6.3. Derivative Works. + If You create or use a modified version of this License (which you may + only do in order to apply it to code which is not already Covered Code + governed by this License), You must (a) rename Your license so that + the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", + "MPL", "NPL" or any confusingly similar phrase do not appear in your + license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license + contains terms which differ from the Mozilla Public License and + Netscape Public License. (Filling in the name of the Initial + Developer, Original Code or Contributor in the notice described in + Exhibit A shall not of themselves be deemed to be modifications of + this License.) + +7. DISCLAIMER OF WARRANTY. + + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE + IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, + YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE + COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER + OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +8. TERMINATION. + + 8.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall + survive any termination of this License. Provisions which, by their + nature, must remain in effect beyond the termination of this License + shall survive. + + 8.2. If You initiate litigation by asserting a patent infringement + claim (excluding declatory judgment actions) against Initial Developer + or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + + (a) such Participant's Contributor Version directly or indirectly + infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License + shall, upon 60 days notice from Participant terminate prospectively, + unless if within 60 days after receipt of notice You either: (i) + agree in writing to pay Participant a mutually agreeable reasonable + royalty for Your past and future use of Modifications made by such + Participant, or (ii) withdraw Your litigation claim with respect to + the Contributor Version against such Participant. If within 60 days + of notice, a reasonable royalty and payment arrangement are not + mutually agreed upon in writing by the parties or the litigation claim + is not withdrawn, the rights granted by Participant to You under + Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + + (b) any software, hardware, or device, other than such Participant's + Contributor Version, directly or indirectly infringes any patent, then + any rights granted to You by such Participant under Sections 2.1(b) + and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that + Participant. + + 8.3. If You assert a patent infringement claim against Participant + alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as + by license or settlement) prior to the initiation of patent + infringement litigation, then the reasonable value of the licenses + granted by such Participant under Sections 2.1 or 2.2 shall be taken + into account in determining the amount or value of any payment or + license. + + 8.4. In the event of termination under Sections 8.1 or 8.2 above, + all end user license agreements (excluding distributors and resellers) + which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination. + +9. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, + OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR + ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY + CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, + WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE + EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO + THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +10. U.S. GOVERNMENT END USERS. + + The Covered Code is a "commercial item," as that term is defined in + 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer + software" and "commercial computer software documentation," as such + terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 + C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), + all U.S. Government End Users acquire Covered Code with only those + rights set forth herein. + +11. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed by + California law provisions (except to the extent applicable law, if + any, provides otherwise), excluding its conflict-of-law provisions. + With respect to disputes in which at least one party is a citizen of, + or an entity chartered or registered to do business in the United + States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern + District of California, with venue lying in Santa Clara County, + California, with the losing party responsible for costs, including + without limitation, court costs and reasonable attorneys' fees and + expenses. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. + Any law or regulation which provides that the language of a contract + shall be construed against the drafter shall not apply to this + License. + +12. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, + out of its utilization of rights under this License and You agree to + work with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + +13. MULTIPLE-LICENSED CODE. + + Initial Developer may designate portions of the Covered Code as + "Multiple-Licensed". "Multiple-Licensed" means that the Initial + Developer permits you to utilize portions of the Covered Code under + Your choice of the NPL or the alternative licenses, if any, specified + by the Initial Developer in the file described in Exhibit A. + +EXHIBIT A -Mozilla Public License. + + ``The contents of this file are subject to the Mozilla Public License + Version 1.1 (the "License"); you may not use this file except in + compliance with the License. You may obtain a copy of the License at + http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + License for the specific language governing rights and limitations + under the License. + + The Original Code is RabbitMQ. + + The Initial Developer of the Original Code is GoPivotal, Inc. + Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved.'' + + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.] diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/Makefile b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/Makefile new file mode 100644 index 0000000..482105a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/Makefile @@ -0,0 +1 @@ +include ../umbrella.mk diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/README.md b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/README.md new file mode 100644 index 0000000..d9a4b6e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/README.md @@ -0,0 +1,22 @@ + +RabbitMQ-Web-Stomp-Examples plugin +================================== + +This project contains few basic examples of RabbitMq-Web-Stomp plugin +usage. + +Once installed the server will bind to port 15670 and serve few static +html files from there: + + * http://127.0.0.1:15670/ + +Installation +------------ + +Generic build instructions are at: + + * http://www.rabbitmq.com/plugin-development.html + +Instructions on how to install a plugin into RabbitMQ broker: + + * http://www.rabbitmq.com/plugins.html#installing-plugins diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/package.mk b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/package.mk new file mode 100644 index 0000000..01e3b9d --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/package.mk @@ -0,0 +1,6 @@ +RELEASABLE:=true +DEPS:=rabbitmq-web-dispatch rabbitmq-web-stomp rabbitmq-server + +define construct_app_commands + cp -r $(PACKAGE_DIR)/priv $(APP_DIR) +endef diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/bunny.html b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/bunny.html new file mode 100644 index 0000000..bb0d34c --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/bunny.html @@ -0,0 +1,141 @@ + + + + + + + + RabbitMQ Web STOMP Examples: Bunny Drawing + + +

    RabbitMQ Web STOMP Examples > Bunny Drawing

    + + + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/bunny.png b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/bunny.png new file mode 100644 index 0000000000000000000000000000000000000000..6c2284ba84fc437a45854b9a2c48e3d192fe8de5 GIT binary patch literal 38296 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 literal 0 HcmV?d00001 diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/echo.html b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/echo.html new file mode 100644 index 0000000..752e18f --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/echo.html @@ -0,0 +1,111 @@ + + + + + + + RabbitMQ Web STOMP Examples : Echo Server + + +

    RabbitMQ Web STOMP Examples > Echo Server

    + +
    +

    Received

    +
    +
    +
    + +
    +

    Logs

    +
    +
    + + + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/index.html b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/index.html new file mode 100644 index 0000000..f722add --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/index.html @@ -0,0 +1,16 @@ + + + + + RabbitMQ Web STOMP Examples + + + +

    RabbitMQ Web STOMP Examples

    + + + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/main.css b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/main.css new file mode 100644 index 0000000..a5cdcda --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/main.css @@ -0,0 +1,38 @@ +body { + font-family: "Arial"; + color: #444; +} + +h1, h2 { + color: #f60; + font-weight: normal; +} + +h1 { + font-size: 1.5em; +} + +h2 { + font-size: 1.2em; + margin: 0; +} + +a { + color: #f60; + border: 1px solid #fda; + background: #fff0e0; + border-radius: 3px; -moz-border-radius: 3px; + padding: 2px; + text-decoration: none; + /* font-weight: bold; */ +} + +ul.menu { + list-style-type: none; + padding: 0; + margin: 0; +} + +ul.menu li { + padding: 5px 0; +} diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/pencil.cur b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/pencil.cur new file mode 100755 index 0000000000000000000000000000000000000000..a3e3598bf0e5128c6a24beb18e2a333b66e30a69 GIT binary patch literal 2238 zcmd6nc~nka6vlrkDj}4N384_m98t-X6e1-GDf67MXf_W;AwwBLAwyBd2D2h%o~ew< zkf}nv?tR}fEpOji-#_1X?t0EWXFt!```KsRbwNRR>eWM-RftjnEkIv@5W-A=7?%zu z&`^^>2#SPsC@3gUrc4Y`SPf#sZpUq1u9mo zNTo`ZP*+!{a^=cYsZxciRjZ<*p@F8RCe^A{LrY7G>eZ`LqecyC)~rdbTD7QMyEb*| z)InQYo4R%DqNAfD_^wC&`t@nhpaHtNx-@LqkVcIf(YSGA^z`)5*Vm^>lO{B6+LUI^ znqgpIK=bC!Y0;tuEnBw4(9jSgBO_Y1YDMeTt!dMy4Q<=DrCqyrv~S;@4jnpRY-~)& zjveXLsS_q9CUoxHnJ!(rU}|cLnVA{p=H_(m+7$~63%YgdM)&UB>CvMHJ$v?~SFc`J zT3XV(cWB-!=bD1}99$sEvczb)}k&%%^MMbf5=T3I*+QshOyNQmDX3w5I?A^PUef##YfB$}B zVq%Dmjpe|B0~|bfkhr)w;^X5vbm$O=4aS{>|NK8y5DJhBM zg(u5k70RjysT#`WvhxpCtLH*enL)~#FIzI~fJckXca?p^NPyT|?e z_j&N(0S_NOBr`LUtgI}uv$J{h=n;<}Kjz7kCp>-nlxNSL@%;I7a&mHb@!|!!xw*W2 z`I1+!Uh(?%Yu>zhLtb7UZ{NP<-Me?ZfB&8jA3pH$<45xI^C>7Ops=uzPoF;V`SWMK zeEC9AQ4wFie&yS@Z%8B(*$?1 _ref; i = 0 <= _ref ? ++_i : --_i) { + line = headerLines[i]; + idx = line.indexOf(':'); + headers[trim(line.substring(0, idx))] = trim(line.substring(idx + 1)); + } + body = ''; + start = divider + 2; + if (headers['content-length']) { + len = parseInt(headers['content-length']); + body = ('' + data).substring(start, start + len); + } else { + chr = null; + for (i = _j = start, _ref1 = data.length; start <= _ref1 ? _j < _ref1 : _j > _ref1; i = start <= _ref1 ? ++_j : --_j) { + chr = data.charAt(i); + if (chr === Byte.NULL) { + break; + } + body += chr; + } + } + return new Frame(command, headers, body); + }; + + Frame.unmarshall = function(datas) { + var data; + return (function() { + var _i, _len, _ref, _results; + _ref = datas.split(RegExp("" + Byte.NULL + Byte.LF + "*")); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + data = _ref[_i]; + if ((data != null ? data.length : void 0) > 0) { + _results.push(Frame._unmarshallSingle(data)); + } + } + return _results; + })(); + }; + + Frame.marshall = function(command, headers, body) { + var frame; + frame = new Frame(command, headers, body); + return frame.toString() + Byte.NULL; + }; + + return Frame; + + })(); + + Client = (function() { + function Client(ws) { + this.ws = ws; + this.ws.binaryType = "arraybuffer"; + this.counter = 0; + this.connected = false; + this.heartbeat = { + outgoing: 10000, + incoming: 10000 + }; + this.maxWebSocketFrameSize = 16 * 1024; + this.subscriptions = {}; + } + + Client.prototype.debug = function(message) { + var _ref; + return typeof window !== "undefined" && window !== null ? (_ref = window.console) != null ? _ref.log(message) : void 0 : void 0; + }; + + Client.prototype._transmit = function(command, headers, body) { + var out; + out = Frame.marshall(command, headers, body); + if (typeof this.debug === "function") { + this.debug(">>> " + out); + } + while (true) { + if (out.length > this.maxWebSocketFrameSize) { + this.ws.send(out.substring(0, this.maxWebSocketFrameSize)); + out = out.substring(this.maxWebSocketFrameSize); + if (typeof this.debug === "function") { + this.debug("remaining = " + out.length); + } + } else { + return this.ws.send(out); + } + } + }; + + Client.prototype._setupHeartbeat = function(headers) { + var serverIncoming, serverOutgoing, ttl, v, _ref, _ref1, + _this = this; + if ((_ref = headers.version) !== Stomp.VERSIONS.V1_1 && _ref !== Stomp.VERSIONS.V1_2) { + return; + } + _ref1 = (function() { + var _i, _len, _ref1, _results; + _ref1 = headers['heart-beat'].split(","); + _results = []; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + v = _ref1[_i]; + _results.push(parseInt(v)); + } + return _results; + })(), serverOutgoing = _ref1[0], serverIncoming = _ref1[1]; + if (!(this.heartbeat.outgoing === 0 || serverIncoming === 0)) { + ttl = Math.max(this.heartbeat.outgoing, serverIncoming); + if (typeof this.debug === "function") { + this.debug("send PING every " + ttl + "ms"); + } + this.pinger = typeof window !== "undefined" && window !== null ? window.setInterval(function() { + _this.ws.send(Byte.LF); + return typeof _this.debug === "function" ? _this.debug(">>> PING") : void 0; + }, ttl) : void 0; + } + if (!(this.heartbeat.incoming === 0 || serverOutgoing === 0)) { + ttl = Math.max(this.heartbeat.incoming, serverOutgoing); + if (typeof this.debug === "function") { + this.debug("check PONG every " + ttl + "ms"); + } + return this.ponger = typeof window !== "undefined" && window !== null ? window.setInterval(function() { + var delta; + delta = Date.now() - _this.serverActivity; + if (delta > ttl * 2) { + if (typeof _this.debug === "function") { + _this.debug("did not receive server activity for the last " + delta + "ms"); + } + return _this.ws.close(); + } + }, ttl) : void 0; + } + }; + + Client.prototype.connect = function(login, passcode, connectCallback, errorCallback, vhost) { + var _this = this; + this.connectCallback = connectCallback; + if (typeof this.debug === "function") { + this.debug("Opening Web Socket..."); + } + this.ws.onmessage = function(evt) { + var arr, c, data, frame, onreceive, _i, _len, _ref, _results; + data = typeof ArrayBuffer !== 'undefined' && evt.data instanceof ArrayBuffer ? (arr = new Uint8Array(evt.data), typeof _this.debug === "function" ? _this.debug("--- got data length: " + arr.length) : void 0, ((function() { + var _i, _len, _results; + _results = []; + for (_i = 0, _len = arr.length; _i < _len; _i++) { + c = arr[_i]; + _results.push(String.fromCharCode(c)); + } + return _results; + })()).join('')) : evt.data; + _this.serverActivity = Date.now(); + if (data === Byte.LF) { + if (typeof _this.debug === "function") { + _this.debug("<<< PONG"); + } + return; + } + if (typeof _this.debug === "function") { + _this.debug("<<< " + data); + } + _ref = Frame.unmarshall(data); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + frame = _ref[_i]; + switch (frame.command) { + case "CONNECTED": + if (typeof _this.debug === "function") { + _this.debug("connected to server " + frame.headers.server); + } + _this.connected = true; + _this._setupHeartbeat(frame.headers); + _results.push(typeof _this.connectCallback === "function" ? _this.connectCallback(frame) : void 0); + break; + case "MESSAGE": + onreceive = _this.subscriptions[frame.headers.subscription] || _this.onreceive; + if (onreceive) { + _results.push(onreceive(frame)); + } else { + _results.push(typeof _this.debug === "function" ? _this.debug("Unhandled received MESSAGE: " + frame) : void 0); + } + break; + case "RECEIPT": + _results.push(typeof _this.onreceipt === "function" ? _this.onreceipt(frame) : void 0); + break; + case "ERROR": + _results.push(typeof errorCallback === "function" ? errorCallback(frame) : void 0); + break; + default: + _results.push(typeof _this.debug === "function" ? _this.debug("Unhandled frame: " + frame) : void 0); + } + } + return _results; + }; + this.ws.onclose = function() { + var msg; + msg = "Whoops! Lost connection to " + _this.ws.url; + if (typeof _this.debug === "function") { + _this.debug(msg); + } + _this._cleanUp(); + return typeof errorCallback === "function" ? errorCallback(msg) : void 0; + }; + return this.ws.onopen = function() { + var headers; + if (typeof _this.debug === "function") { + _this.debug('Web Socket Opened...'); + } + headers = { + "accept-version": Stomp.VERSIONS.supportedVersions(), + "heart-beat": [_this.heartbeat.outgoing, _this.heartbeat.incoming].join(',') + }; + if (vhost) { + headers.host = vhost; + } + if (login) { + headers.login = login; + } + if (passcode) { + headers.passcode = passcode; + } + return _this._transmit("CONNECT", headers); + }; + }; + + Client.prototype.disconnect = function(disconnectCallback) { + this._transmit("DISCONNECT"); + this.ws.onclose = null; + this.ws.close(); + this._cleanUp(); + return typeof disconnectCallback === "function" ? disconnectCallback() : void 0; + }; + + Client.prototype._cleanUp = function() { + this.connected = false; + if (this.pinger) { + if (typeof window !== "undefined" && window !== null) { + window.clearInterval(this.pinger); + } + } + if (this.ponger) { + return typeof window !== "undefined" && window !== null ? window.clearInterval(this.ponger) : void 0; + } + }; + + Client.prototype.send = function(destination, headers, body) { + if (headers == null) { + headers = {}; + } + if (body == null) { + body = ''; + } + headers.destination = destination; + return this._transmit("SEND", headers, body); + }; + + Client.prototype.subscribe = function(destination, callback, headers) { + if (headers == null) { + headers = {}; + } + if (!headers.id) { + headers.id = "sub-" + this.counter++; + } + headers.destination = destination; + this.subscriptions[headers.id] = callback; + this._transmit("SUBSCRIBE", headers); + return headers.id; + }; + + Client.prototype.unsubscribe = function(id) { + delete this.subscriptions[id]; + return this._transmit("UNSUBSCRIBE", { + id: id + }); + }; + + Client.prototype.begin = function(transaction) { + return this._transmit("BEGIN", { + transaction: transaction + }); + }; + + Client.prototype.commit = function(transaction) { + return this._transmit("COMMIT", { + transaction: transaction + }); + }; + + Client.prototype.abort = function(transaction) { + return this._transmit("ABORT", { + transaction: transaction + }); + }; + + Client.prototype.ack = function(messageID, subscription, headers) { + if (headers == null) { + headers = {}; + } + headers["message-id"] = messageID; + headers.subscription = subscription; + return this._transmit("ACK", headers); + }; + + Client.prototype.nack = function(messageID, subscription, headers) { + if (headers == null) { + headers = {}; + } + headers["message-id"] = messageID; + headers.subscription = subscription; + return this._transmit("NACK", headers); + }; + + return Client; + + })(); + + Stomp = { + libVersion: "2.0.0-next", + VERSIONS: { + V1_0: '1.0', + V1_1: '1.1', + V1_2: '1.2', + supportedVersions: function() { + return '1.1,1.0'; + } + }, + client: function(url, protocols) { + var klass, ws; + if (protocols == null) { + protocols = ['v10.stomp', 'v11.stomp']; + } + klass = Stomp.WebSocketClass || WebSocket; + ws = new klass(url, protocols); + return new Client(ws); + }, + over: function(ws) { + return new Client(ws); + }, + Frame: Frame + }; + + if (typeof window !== "undefined" && window !== null) { + window.Stomp = Stomp; + } else if (typeof exports !== "undefined" && exports !== null) { + exports.Stomp = Stomp; + Stomp.WebSocketClass = require('./test/server.mock.js').StompServerMock; + } else { + self.Stomp = Stomp; + } + +}).call(this); diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/temp-queue.html b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/temp-queue.html new file mode 100644 index 0000000..0bdb158 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/priv/temp-queue.html @@ -0,0 +1,102 @@ + + + + + + + RabbitMQ Web STOMP Examples : Temporary Queue + + +

    RabbitMQ Web STOMP Examples > Temporary Queue

    + +

    When you type text in the form's input, the application will send a message to the /queue/test destination + with the reply-to header set to /temp-queue/foo.

    +

    The STOMP client sets a default onreceive callback to receive messages from this temporary queue and display the message's text.

    +

    Finally, the client subscribes to the /queue/test destination. When it receives message from this destination, it reverses the message's + text and reply by sending the reversed text to the destination defined by the message's reply-to header.

    + +
    +

    Received

    +
    +
    +
    + +
    +

    Logs

    +
    +
    + + + diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/src/rabbit_web_stomp_examples_app.erl b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/src/rabbit_web_stomp_examples_app.erl new file mode 100644 index 0000000..91a3c27 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/src/rabbit_web_stomp_examples_app.erl @@ -0,0 +1,38 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2012-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_web_stomp_examples_app). + +-behaviour(application). +-export([start/2,stop/1]). + +%% Dummy supervisor - see Ulf Wiger's comment at +%% http://erlang.2086793.n4.nabble.com/initializing-library-applications-without-processes-td2094473.html +-behaviour(supervisor). +-export([init/1]). + +start(_Type, _StartArgs) -> + {ok, Listener} = application:get_env(rabbitmq_web_stomp_examples, listener), + {ok, _} = rabbit_web_dispatch:register_static_context( + web_stomp_examples, Listener, "web-stomp-examples", ?MODULE, + "priv", "WEB-STOMP: examples"), + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +stop(_State) -> + rabbit_web_dispatch:unregister_context(web_stomp_examples), + ok. + +init([]) -> {ok, {{one_for_one, 3, 10}, []}}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/src/rabbitmq_web_stomp_examples.app.src b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/src/rabbitmq_web_stomp_examples.app.src new file mode 100644 index 0000000..832438c --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp-examples/src/rabbitmq_web_stomp_examples.app.src @@ -0,0 +1,8 @@ +{application, rabbitmq_web_stomp_examples, + [{description, "Rabbit WEB-STOMP - examples"}, + {vsn, "%%VSN%%"}, + {modules, []}, + {registered, []}, + {mod, {rabbit_web_stomp_examples_app, []}}, + {env, [{listener, [{port, 15670}]}]}, + {applications, [kernel, stdlib, rabbitmq_web_dispatch, rabbitmq_web_stomp]}]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/.srcdist_done b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/.srcdist_done new file mode 100644 index 0000000..e69de29 diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/LICENSE b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/LICENSE new file mode 100644 index 0000000..e1fbfaf --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/LICENSE @@ -0,0 +1,5 @@ +This package, the rabbitmq-web-stomp, is licensed under the MPL. For +the MPL, please see LICENSE-MPL-RabbitMQ. + +If you have any questions regarding licensing, please contact us at +info@rabbitmq.com. diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/LICENSE-MPL-RabbitMQ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/LICENSE-MPL-RabbitMQ new file mode 100644 index 0000000..c87c1a3 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/LICENSE-MPL-RabbitMQ @@ -0,0 +1,455 @@ + MOZILLA PUBLIC LICENSE + Version 1.1 + + --------------- + +1. Definitions. + + 1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. + + 1.1. "Contributor" means each entity that creates or contributes to + the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications + made by that particular Contributor. + + 1.3. "Covered Code" means the Original Code or Modifications or the + combination of the Original Code and Modifications, in each case + including portions thereof. + + 1.4. "Electronic Distribution Mechanism" means a mechanism generally + accepted in the software development community for the electronic + transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source + Code. + + 1.6. "Initial Developer" means the individual or entity identified + as the Initial Developer in the Source Code notice required by Exhibit + A. + + 1.7. "Larger Work" means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + + 1.8. "License" means this document. + + 1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + A. Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. + + B. Any new file that contains any part of the Original Code or + previous Modifications. + + 1.10. "Original Code" means Source Code of computer software code + which is described in the Source Code notice required by Exhibit A as + Original Code, and which, at the time of its release under this + License is not already Covered Code governed by this License. + + 1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, + and apparatus claims, in any patent Licensable by grantor. + + 1.11. "Source Code" means the preferred form of the Covered Code for + making modifications to it, including all modules it contains, plus + any associated interface definition files, scripts used to control + compilation and installation of an Executable, or source code + differential comparisons against either the Original Code or another + well known, available Covered Code of the Contributor's choice. The + Source Code can be in a compressed or archival form, provided the + appropriate decompression or de-archiving software is widely available + for no charge. + + 1.12. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License or a future version of this License issued under Section 6.1. + For legal entities, "You" includes any entity which controls, is + controlled by, or is under common control with You. For purposes of + this definition, "control" means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty percent + (50%) of the outstanding shares or beneficial ownership of such + entity. + +2. Source Code License. + + 2.1. The Initial Developer Grant. + The Initial Developer hereby grants You a world-wide, royalty-free, + non-exclusive license, subject to third party intellectual property + claims: + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original + Code (or portions thereof) with or without Modifications, and/or + as part of a Larger Work; and + + (b) under Patents Claims infringed by the making, using or + selling of Original Code, to make, have made, use, practice, + sell, and offer for sale, and/or otherwise dispose of the + Original Code (or portions thereof). + + (c) the licenses granted in this Section 2.1(a) and (b) are + effective on the date Initial Developer first distributes + Original Code under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) + separate from the Original Code; or 3) for infringements caused + by: i) the modification of the Original Code or ii) the + combination of the Original Code with other software or devices. + + 2.2. Contributor Grant. + Subject to third party intellectual property claims, each Contributor + hereby grants You a world-wide, royalty-free, non-exclusive license + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor, to use, reproduce, modify, + display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an + unmodified basis, with other Modifications, as Covered Code + and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either alone + and/or in combination with its Contributor Version (or portions + of such combination), to make, use, sell, offer for sale, have + made, and/or otherwise dispose of: 1) Modifications made by that + Contributor (or portions thereof); and 2) the combination of + Modifications made by that Contributor with its Contributor + Version (or portions of such combination). + + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of + the Covered Code. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: 1) for any code that Contributor has deleted from the + Contributor Version; 2) separate from the Contributor Version; + 3) for infringements caused by: i) third party modifications of + Contributor Version or ii) the combination of Modifications made + by that Contributor with other software (except as part of the + Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by + that Contributor. + +3. Distribution Obligations. + + 3.1. Application of License. + The Modifications which You create or to which You contribute are + governed by the terms of this License, including without limitation + Section 2.2. The Source Code version of Covered Code may be + distributed only under the terms of this License or a future version + of this License released under Section 6.1, and You must include a + copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code + version that alters or restricts the applicable version of this + License or the recipients' rights hereunder. However, You may include + an additional document offering the additional rights described in + Section 3.5. + + 3.2. Availability of Source Code. + Any Modification which You create or to which You contribute must be + made available in Source Code form under the terms of this License + either on the same media as an Executable version or via an accepted + Electronic Distribution Mechanism to anyone to whom you made an + Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) + months after the date it initially became available, or at least six + (6) months after a subsequent version of that particular Modification + has been made available to such recipients. You are responsible for + ensuring that the Source Code version remains available even if the + Electronic Distribution Mechanism is maintained by a third party. + + 3.3. Description of Modifications. + You must cause all Covered Code to which You contribute to contain a + file documenting the changes You made to create that Covered Code and + the date of any change. You must include a prominent statement that + the Modification is derived, directly or indirectly, from Original + Code provided by the Initial Developer and including the name of the + Initial Developer in (a) the Source Code, and (b) in any notice in an + Executable version or related documentation in which You describe the + origin or ownership of the Covered Code. + + 3.4. Intellectual Property Matters + (a) Third Party Claims. + If Contributor has knowledge that a license under a third party's + intellectual property rights is required to exercise the rights + granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code + distribution titled "LEGAL" which describes the claim and the + party making the claim in sufficient detail that a recipient will + know whom to contact. If Contributor obtains such knowledge after + the Modification is made available as described in Section 3.2, + Contributor shall promptly modify the LEGAL file in all copies + Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) + reasonably calculated to inform those who received the Covered + Code that new knowledge has been obtained. + + (b) Contributor APIs. + If Contributor's Modifications include an application programming + interface and Contributor has knowledge of patent licenses which + are reasonably necessary to implement that API, Contributor must + also include this information in the LEGAL file. + + (c) Representations. + Contributor represents that, except as disclosed pursuant to + Section 3.4(a) above, Contributor believes that Contributor's + Modifications are Contributor's original creation(s) and/or + Contributor has sufficient rights to grant the rights conveyed by + this License. + + 3.5. Required Notices. + You must duplicate the notice in Exhibit A in each file of the Source + Code. If it is not possible to put such notice in a particular Source + Code file due to its structure, then You must include such notice in a + location (such as a relevant directory) where a user would be likely + to look for such a notice. If You created one or more Modification(s) + You may add your name as a Contributor to the notice described in + Exhibit A. You must also duplicate this License in any documentation + for the Source Code where You describe recipients' rights or ownership + rights relating to Covered Code. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear than + any such warranty, support, indemnity or liability obligation is + offered by You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of warranty, + support, indemnity or liability terms You offer. + + 3.6. Distribution of Executable Versions. + You may distribute Covered Code in Executable form only if the + requirements of Section 3.1-3.5 have been met for that Covered Code, + and if You include a notice stating that the Source Code version of + the Covered Code is available under the terms of this License, + including a description of how and where You have fulfilled the + obligations of Section 3.2. The notice must be conspicuously included + in any notice in an Executable version, related documentation or + collateral in which You describe recipients' rights relating to the + Covered Code. You may distribute the Executable version of Covered + Code or ownership rights under a license of Your choice, which may + contain terms different from this License, provided that You are in + compliance with the terms of this License and that the license for the + Executable version does not attempt to limit or alter the recipient's + rights in the Source Code version from the rights set forth in this + License. If You distribute the Executable version under a different + license You must make it absolutely clear that any terms which differ + from this License are offered by You alone, not by the Initial + Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such + terms You offer. + + 3.7. Larger Works. + You may create a Larger Work by combining Covered Code with other code + not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Code. + +4. Inability to Comply Due to Statute or Regulation. + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description + must be included in the LEGAL file described in Section 3.4 and must + be included with all distributions of the Source Code. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Application of this License. + + This License applies to code to which the Initial Developer has + attached the notice in Exhibit A and to related Covered Code. + +6. Versions of the License. + + 6.1. New Versions. + Netscape Communications Corporation ("Netscape") may publish revised + and/or new versions of the License from time to time. Each version + will be given a distinguishing version number. + + 6.2. Effect of New Versions. + Once Covered Code has been published under a particular version of the + License, You may always continue to use it under the terms of that + version. You may also choose to use such Covered Code under the terms + of any subsequent version of the License published by Netscape. No one + other than Netscape has the right to modify the terms applicable to + Covered Code created under this License. + + 6.3. Derivative Works. + If You create or use a modified version of this License (which you may + only do in order to apply it to code which is not already Covered Code + governed by this License), You must (a) rename Your license so that + the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", + "MPL", "NPL" or any confusingly similar phrase do not appear in your + license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license + contains terms which differ from the Mozilla Public License and + Netscape Public License. (Filling in the name of the Initial + Developer, Original Code or Contributor in the notice described in + Exhibit A shall not of themselves be deemed to be modifications of + this License.) + +7. DISCLAIMER OF WARRANTY. + + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE + IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, + YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE + COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER + OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +8. TERMINATION. + + 8.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall + survive any termination of this License. Provisions which, by their + nature, must remain in effect beyond the termination of this License + shall survive. + + 8.2. If You initiate litigation by asserting a patent infringement + claim (excluding declatory judgment actions) against Initial Developer + or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + + (a) such Participant's Contributor Version directly or indirectly + infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License + shall, upon 60 days notice from Participant terminate prospectively, + unless if within 60 days after receipt of notice You either: (i) + agree in writing to pay Participant a mutually agreeable reasonable + royalty for Your past and future use of Modifications made by such + Participant, or (ii) withdraw Your litigation claim with respect to + the Contributor Version against such Participant. If within 60 days + of notice, a reasonable royalty and payment arrangement are not + mutually agreed upon in writing by the parties or the litigation claim + is not withdrawn, the rights granted by Participant to You under + Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + + (b) any software, hardware, or device, other than such Participant's + Contributor Version, directly or indirectly infringes any patent, then + any rights granted to You by such Participant under Sections 2.1(b) + and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that + Participant. + + 8.3. If You assert a patent infringement claim against Participant + alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as + by license or settlement) prior to the initiation of patent + infringement litigation, then the reasonable value of the licenses + granted by such Participant under Sections 2.1 or 2.2 shall be taken + into account in determining the amount or value of any payment or + license. + + 8.4. In the event of termination under Sections 8.1 or 8.2 above, + all end user license agreements (excluding distributors and resellers) + which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination. + +9. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, + OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR + ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY + CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, + WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE + EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO + THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +10. U.S. GOVERNMENT END USERS. + + The Covered Code is a "commercial item," as that term is defined in + 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer + software" and "commercial computer software documentation," as such + terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 + C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), + all U.S. Government End Users acquire Covered Code with only those + rights set forth herein. + +11. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed by + California law provisions (except to the extent applicable law, if + any, provides otherwise), excluding its conflict-of-law provisions. + With respect to disputes in which at least one party is a citizen of, + or an entity chartered or registered to do business in the United + States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern + District of California, with venue lying in Santa Clara County, + California, with the losing party responsible for costs, including + without limitation, court costs and reasonable attorneys' fees and + expenses. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. + Any law or regulation which provides that the language of a contract + shall be construed against the drafter shall not apply to this + License. + +12. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, + out of its utilization of rights under this License and You agree to + work with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + +13. MULTIPLE-LICENSED CODE. + + Initial Developer may designate portions of the Covered Code as + "Multiple-Licensed". "Multiple-Licensed" means that the Initial + Developer permits you to utilize portions of the Covered Code under + Your choice of the NPL or the alternative licenses, if any, specified + by the Initial Developer in the file described in Exhibit A. + +EXHIBIT A -Mozilla Public License. + + ``The contents of this file are subject to the Mozilla Public License + Version 1.1 (the "License"); you may not use this file except in + compliance with the License. You may obtain a copy of the License at + http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + License for the specific language governing rights and limitations + under the License. + + The Original Code is RabbitMQ. + + The Initial Developer of the Original Code is GoPivotal, Inc. + Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved.'' + + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.] diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/Makefile b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/Makefile new file mode 100644 index 0000000..482105a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/Makefile @@ -0,0 +1 @@ +include ../umbrella.mk diff --git a/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/README.md b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/README.md new file mode 100644 index 0000000..e7fa61a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/rabbitmq-web-stomp/README.md @@ -0,0 +1,45 @@ +RabbitMQ-Web-Stomp plugin +========================= + +This project is a simple bridge between "RabbitMQ-stomp" plugin and +SockJS. + +Once started the plugin opens a SockJS endpoint on prefix "/stomp" on +port 15674, for example a valid SockJS endpoint url may look like: +"http://127.0.0.1:15674/stomp". + +Once the server is started you should be able to establish a SockJS +connection to this url. You will be able to communicate using the +usual STOMP protocol over it. For example, a page using Jeff Mesnil's +"stomp-websocket" project may look like this: + + + + + \r\n">>]. + +--spec fmt_jsonp(iodata(), iodata()) -> iodata(). ++%% -spec fmt_jsonp(iodata(), iodata()) -> iodata(). + fmt_jsonp(Body, Callback) -> + %% Yes, JSONed twice, there isn't a a better way, we must pass + %% a string back, and the script, will be evaled() by the +@@ -259,7 +259,7 @@ fmt_jsonp(Body, Callback) -> + + %% -------------------------------------------------------------------------- + +--spec websocket(req(), headers(), service()) -> req(). ++%% -spec websocket(req(), headers(), service()) -> req(). + websocket(Req, Headers, Service) -> + {_Any, Req1, {R1, R2}} = sockjs_handler:is_valid_ws(Service, Req), + case {R1, R2} of +@@ -274,6 +274,6 @@ websocket(Req, Headers, Service) -> + "This WebSocket request can't be handled.", Req1) + end. + +--spec rawwebsocket(req(), headers(), service()) -> req(). ++%% -spec rawwebsocket(req(), headers(), service()) -> req(). + rawwebsocket(Req, Headers, Service) -> + websocket(Req, Headers, Service). +diff --git a/src/sockjs_app.erl b/src/sockjs_app.erl +index 1b8e77c..54aceb6 100644 +--- a/src/sockjs_app.erl ++++ b/src/sockjs_app.erl +@@ -4,11 +4,11 @@ + + -export([start/2, stop/1]). + +--spec start(_, _) -> {ok, pid()}. ++%% -spec start(_, _) -> {ok, pid()}. + start(_StartType, _StartArgs) -> + sockjs_session:init(), + sockjs_session_sup:start_link(). + +--spec stop(_) -> ok. ++%% -spec stop(_) -> ok. + stop(_State) -> + ok. +diff --git a/src/sockjs_filters.erl b/src/sockjs_filters.erl +index 15aa8e3..fba43cc 100644 +--- a/src/sockjs_filters.erl ++++ b/src/sockjs_filters.erl +@@ -9,7 +9,7 @@ + + %% -------------------------------------------------------------------------- + +--spec cache_for(req(), headers()) -> {headers(), req()}. ++%% -spec cache_for(req(), headers()) -> {headers(), req()}. + cache_for(Req, Headers) -> + Expires = calendar:gregorian_seconds_to_datetime( + calendar:datetime_to_gregorian_seconds( +@@ -18,7 +18,7 @@ cache_for(Req, Headers) -> + {"Expires", httpd_util:rfc1123_date(Expires)}], + {H ++ Headers, Req}. + +--spec h_sid(req(), headers()) -> {headers(), req()}. ++%% -spec h_sid(req(), headers()) -> {headers(), req()}. + h_sid(Req, Headers) -> + %% Some load balancers do sticky sessions, but only if there is + %% a JSESSIONID cookie. If this cookie isn't yet set, we shall +@@ -31,12 +31,12 @@ h_sid(Req, Headers) -> + end, + {H ++ Headers, Req2}. + +--spec h_no_cache(req(), headers()) -> {headers(), req()}. ++%% -spec h_no_cache(req(), headers()) -> {headers(), req()}. + h_no_cache(Req, Headers) -> + H = [{"Cache-Control", "no-store, no-cache, must-revalidate, max-age=0"}], + {H ++ Headers, Req}. + +--spec xhr_cors(req(), headers()) -> {headers(), req()}. ++%% -spec xhr_cors(req(), headers()) -> {headers(), req()}. + xhr_cors(Req, Headers) -> + {OriginH, Req1} = sockjs_http:header('Origin', Req), + Origin = case OriginH of +@@ -54,15 +54,15 @@ xhr_cors(Req, Headers) -> + {"Access-Control-Allow-Credentials", "true"}], + {H ++ AllowHeaders ++ Headers, Req2}. + +--spec xhr_options_post(req(), headers()) -> {headers(), req()}. ++%% -spec xhr_options_post(req(), headers()) -> {headers(), req()}. + xhr_options_post(Req, Headers) -> + xhr_options(Req, Headers, ["OPTIONS", "POST"]). + +--spec xhr_options_get(req(), headers()) -> {headers(), req()}. ++%% -spec xhr_options_get(req(), headers()) -> {headers(), req()}. + xhr_options_get(Req, Headers) -> + xhr_options(Req, Headers, ["OPTIONS", "GET"]). + +--spec xhr_options(req(), headers(), list(string())) -> {headers(), req()}. ++%% -spec xhr_options(req(), headers(), list(string())) -> {headers(), req()}. + xhr_options(Req, Headers, Methods) -> + H = [{"Access-Control-Allow-Methods", string:join(Methods, ", ")}, + {"Access-Control-Max-Age", integer_to_list(?YEAR)}], +diff --git a/src/sockjs_handler.erl b/src/sockjs_handler.erl +index ebb3982..b706453 100644 +--- a/src/sockjs_handler.erl ++++ b/src/sockjs_handler.erl +@@ -11,7 +11,7 @@ + + %% -------------------------------------------------------------------------- + +--spec init_state(binary(), callback(), any(), list(tuple())) -> service(). ++%% -spec init_state(binary(), callback(), any(), list(tuple())) -> service(). + init_state(Prefix, Callback, State, Options) -> + #service{prefix = binary_to_list(Prefix), + callback = Callback, +@@ -34,7 +34,7 @@ init_state(Prefix, Callback, State, Options) -> + + %% -------------------------------------------------------------------------- + +--spec is_valid_ws(service(), req()) -> {boolean(), req(), tuple()}. ++%% -spec is_valid_ws(service(), req()) -> {boolean(), req(), tuple()}. + is_valid_ws(Service, Req) -> + case get_action(Service, Req) of + {{match, WS}, Req1} when WS =:= websocket orelse +@@ -44,7 +44,7 @@ is_valid_ws(Service, Req) -> + {false, Req1, {}} + end. + +--spec valid_ws_request(service(), req()) -> {boolean(), req(), tuple()}. ++%% -spec valid_ws_request(service(), req()) -> {boolean(), req(), tuple()}. + valid_ws_request(_Service, Req) -> + {R1, Req1} = valid_ws_upgrade(Req), + {R2, Req2} = valid_ws_connection(Req1), +@@ -73,7 +73,7 @@ valid_ws_connection(Req) -> + {lists:member("upgrade", Vs), Req2} + end. + +--spec get_action(service(), req()) -> {nomatch | {match, atom()}, req()}. ++%% -spec get_action(service(), req()) -> {nomatch | {match, atom()}, req()}. + get_action(Service, Req) -> + {Dispatch, Req1} = dispatch_req(Service, Req), + case Dispatch of +@@ -93,20 +93,20 @@ strip_prefix(LongPath, Prefix) -> + end. + + +--type(dispatch_result() :: +- nomatch | +- {match, {send | recv | none , atom(), +- server(), session(), list(atom())}} | +- {bad_method, list(atom())}). ++%% -type(dispatch_result() :: ++%% nomatch | ++%% {match, {send | recv | none , atom(), ++%% server(), session(), list(atom())}} | ++%% {bad_method, list(atom())}). + +--spec dispatch_req(service(), req()) -> {dispatch_result(), req()}. ++%% -spec dispatch_req(service(), req()) -> {dispatch_result(), req()}. + dispatch_req(#service{prefix = Prefix}, Req) -> + {Method, Req1} = sockjs_http:method(Req), + {LongPath, Req2} = sockjs_http:path(Req1), + {ok, PathRemainder} = strip_prefix(LongPath, Prefix), + {dispatch(Method, PathRemainder), Req2}. + +--spec dispatch(atom(), nonempty_string()) -> dispatch_result(). ++%% -spec dispatch(atom(), nonempty_string()) -> dispatch_result(). + dispatch(Method, Path) -> + lists:foldl( + fun ({Match, MethodFilters}, nomatch) -> +@@ -163,7 +163,7 @@ re(Path, S) -> + + %% -------------------------------------------------------------------------- + +--spec handle_req(service(), req()) -> req(). ++%% -spec handle_req(service(), req()) -> req(). + handle_req(Service = #service{logger = Logger}, Req) -> + Req0 = Logger(Service, Req, http), + +@@ -202,14 +202,14 @@ handle({match, {Type, Action, _Server, Session, Filters}}, Service, Req) -> + + %% -------------------------------------------------------------------------- + +--spec default_logger(service(), req(), websocket | http) -> req(). ++%% -spec default_logger(service(), req(), websocket | http) -> req(). + default_logger(_Service, Req, _Type) -> + {LongPath, Req1} = sockjs_http:path(Req), + {Method, Req2} = sockjs_http:method(Req1), + io:format("~s ~s~n", [Method, LongPath]), + Req2. + +--spec extract_info(req()) -> {info(), req()}. ++%% -spec extract_info(req()) -> {info(), req()}. + extract_info(Req) -> + {Peer, Req0} = sockjs_http:peername(Req), + {Sock, Req1} = sockjs_http:sockname(Req0), +diff --git a/src/sockjs_http.erl b/src/sockjs_http.erl +index 9754119..5cdf431 100644 +--- a/src/sockjs_http.erl ++++ b/src/sockjs_http.erl +@@ -8,22 +8,22 @@ + + %% -------------------------------------------------------------------------- + +--spec path(req()) -> {string(), req()}. ++%% -spec path(req()) -> {string(), req()}. + path({cowboy, Req}) -> {Path, Req1} = cowboy_http_req:raw_path(Req), + {binary_to_list(Path), {cowboy, Req1}}. + +--spec method(req()) -> {atom(), req()}. ++%% -spec method(req()) -> {atom(), req()}. + method({cowboy, Req}) -> {Method, Req1} = cowboy_http_req:method(Req), + case is_binary(Method) of + true -> {binary_to_atom(Method, utf8), {cowboy, Req1}}; + false -> {Method, {cowboy, Req1}} + end. + +--spec body(req()) -> {binary(), req()}. ++%% -spec body(req()) -> {binary(), req()}. + body({cowboy, Req}) -> {ok, Body, Req1} = cowboy_http_req:body(Req), + {Body, {cowboy, Req1}}. + +--spec body_qs(req()) -> {binary(), req()}. ++%% -spec body_qs(req()) -> {binary(), req()}. + body_qs(Req) -> + {H, Req1} = header('Content-Type', Req), + case H of +@@ -42,7 +42,7 @@ body_qs2({cowboy, Req}) -> + {V, {cowboy, Req1}} + end. + +--spec header(atom(), req()) -> {nonempty_string() | undefined, req()}. ++%% -spec header(atom(), req()) -> {nonempty_string() | undefined, req()}. + header(K, {cowboy, Req})-> + {H, Req2} = cowboy_http_req:header(K, Req), + {V, Req3} = case H of +@@ -55,7 +55,7 @@ header(K, {cowboy, Req})-> + _ -> {binary_to_list(V), {cowboy, Req3}} + end. + +--spec jsessionid(req()) -> {nonempty_string() | undefined, req()}. ++%% -spec jsessionid(req()) -> {nonempty_string() | undefined, req()}. + jsessionid({cowboy, Req}) -> + {C, Req2} = cowboy_http_req:cookie(<<"JSESSIONID">>, Req), + case C of +@@ -65,7 +65,7 @@ jsessionid({cowboy, Req}) -> + {undefined, {cowboy, Req2}} + end. + +--spec callback(req()) -> {nonempty_string() | undefined, req()}. ++%% -spec callback(req()) -> {nonempty_string() | undefined, req()}. + callback({cowboy, Req}) -> + {CB, Req1} = cowboy_http_req:qs_val(<<"c">>, Req), + case CB of +@@ -73,12 +73,12 @@ callback({cowboy, Req}) -> + _ -> {binary_to_list(CB), {cowboy, Req1}} + end. + +--spec peername(req()) -> {{inet:ip_address(), non_neg_integer()}, req()}. ++%% -spec peername(req()) -> {{inet:ip_address(), non_neg_integer()}, req()}. + peername({cowboy, Req}) -> + {P, Req1} = cowboy_http_req:peer(Req), + {P, {cowboy, Req1}}. + +--spec sockname(req()) -> {{inet:ip_address(), non_neg_integer()}, req()}. ++%% -spec sockname(req()) -> {{inet:ip_address(), non_neg_integer()}, req()}. + sockname({cowboy, Req} = R) -> + {ok, _T, S} = cowboy_http_req:transport(Req), + %% Cowboy has peername(), but doesn't have sockname() equivalent. +@@ -92,18 +92,18 @@ sockname({cowboy, Req} = R) -> + + %% -------------------------------------------------------------------------- + +--spec reply(non_neg_integer(), headers(), iodata(), req()) -> req(). ++%% -spec reply(non_neg_integer(), headers(), iodata(), req()) -> req(). + reply(Code, Headers, Body, {cowboy, Req}) -> + Body1 = iolist_to_binary(Body), + {ok, Req1} = cowboy_http_req:reply(Code, enbinary(Headers), Body1, Req), + {cowboy, Req1}. + +--spec chunk_start(non_neg_integer(), headers(), req()) -> req(). ++%% -spec chunk_start(non_neg_integer(), headers(), req()) -> req(). + chunk_start(Code, Headers, {cowboy, Req}) -> + {ok, Req1} = cowboy_http_req:chunked_reply(Code, enbinary(Headers), Req), + {cowboy, Req1}. + +--spec chunk(iodata(), req()) -> {ok | error, req()}. ++%% -spec chunk(iodata(), req()) -> {ok | error, req()}. + chunk(Chunk, {cowboy, Req} = R) -> + case cowboy_http_req:chunk(Chunk, Req) of + ok -> {ok, R}; +@@ -112,25 +112,25 @@ chunk(Chunk, {cowboy, Req} = R) -> + %% should catch tco socket closure before. + end. + +--spec chunk_end(req()) -> req(). ++%% -spec chunk_end(req()) -> req(). + chunk_end({cowboy, _Req} = R) -> R. + + enbinary(L) -> [{list_to_binary(K), list_to_binary(V)} || {K, V} <- L]. + + +--spec hook_tcp_close(req()) -> req(). ++%% -spec hook_tcp_close(req()) -> req(). + hook_tcp_close(R = {cowboy, Req}) -> + {ok, T, S} = cowboy_http_req:transport(Req), + T:setopts(S,[{active,once}]), + R. + +--spec unhook_tcp_close(req()) -> req(). ++%% -spec unhook_tcp_close(req()) -> req(). + unhook_tcp_close(R = {cowboy, Req}) -> + {ok, T, S} = cowboy_http_req:transport(Req), + T:setopts(S,[{active,false}]), + R. + +--spec abruptly_kill(req()) -> req(). ++%% -spec abruptly_kill(req()) -> req(). + abruptly_kill(R = {cowboy, Req}) -> + {ok, T, S} = cowboy_http_req:transport(Req), + T:close(S), +diff --git a/src/sockjs_internal.hrl b/src/sockjs_internal.hrl +index 4f696d8..629b2fe 100644 +--- a/src/sockjs_internal.hrl ++++ b/src/sockjs_internal.hrl +@@ -1,32 +1,32 @@ + +--type(req() :: {cowboy, any()}). ++%% -type(req() :: {cowboy, any()}). + +--type(user_session() :: nonempty_string()). +--type(emittable() :: init|closed|{recv, binary()}). +--type(callback() :: fun((user_session(), emittable(), any()) -> ok)). +--type(logger() :: fun((any(), req(), websocket|http) -> req())). ++%% -type(user_session() :: nonempty_string()). ++%% -type(emittable() :: init|closed|{recv, binary()}). ++%% -type(callback() :: fun((user_session(), emittable(), any()) -> ok)). ++%% -type(logger() :: fun((any(), req(), websocket|http) -> req())). + +--record(service, {prefix :: nonempty_string(), +- callback :: callback(), +- state :: any(), +- sockjs_url :: nonempty_string(), +- cookie_needed :: boolean(), +- websocket :: boolean(), +- disconnect_delay :: non_neg_integer(), +- heartbeat_delay :: non_neg_integer(), +- response_limit :: non_neg_integer(), +- logger :: logger() ++-record(service, {prefix , %% nonempty_string(), ++ callback , %% callback() ++ state , %% any() ++ sockjs_url , %% nonempty_string() ++ cookie_needed , %% boolean() ++ websocket , %% boolean() ++ disconnect_delay , %% non_neg_integer() ++ heartbeat_delay , %% non_neg_integer() ++ response_limit , %% non_neg_integer() ++ logger %% logger() + }). + +--type(service() :: #service{}). ++%% -type(service() :: #service{}). + +--type(headers() :: list({nonempty_string(), nonempty_string()})). +--type(server() :: nonempty_string()). +--type(session() :: nonempty_string()). ++%% -type(headers() :: list({nonempty_string(), nonempty_string()})). ++%% -type(server() :: nonempty_string()). ++%% -type(session() :: nonempty_string()). + +--type(frame() :: {open, nil} | +- {close, {non_neg_integer(), string()}} | +- {data, list(iodata())} | +- {heartbeat, nil} ). ++%% -type(frame() :: {open, nil} | ++%% {close, {non_neg_integer(), string()}} | ++%% {data, list(iodata())} | ++%% {heartbeat, nil} ). + +--type(info() :: [{atom(), any()}]). ++%% -type(info() :: [{atom(), any()}]). +diff --git a/src/sockjs_json.erl b/src/sockjs_json.erl +index e61f4b9..d3dae20 100644 +--- a/src/sockjs_json.erl ++++ b/src/sockjs_json.erl +@@ -4,11 +4,11 @@ + + %% -------------------------------------------------------------------------- + +--spec encode(any()) -> iodata(). ++%% -spec encode(any()) -> iodata(). + encode(Thing) -> + mochijson2_fork:encode(Thing). + +--spec decode(iodata()) -> {ok, any()} | {error, any()}. ++%% -spec decode(iodata()) -> {ok, any()} | {error, any()}. + decode(Encoded) -> + try mochijson2_fork:decode(Encoded) of + V -> {ok, V} +diff --git a/src/sockjs_session.erl b/src/sockjs_session.erl +index 66c5df0..7e4ae00 100644 +--- a/src/sockjs_session.erl ++++ b/src/sockjs_session.erl +@@ -11,39 +11,39 @@ + handle_cast/2]). + + -include("sockjs_internal.hrl"). +--type(handle() :: {?MODULE, {pid(), info()}}). +- +--record(session, {id :: session(), +- outbound_queue = queue:new() :: queue(), +- response_pid :: pid(), +- disconnect_tref :: reference(), +- disconnect_delay = 5000 :: non_neg_integer(), +- heartbeat_tref :: reference() | triggered, +- heartbeat_delay = 25000 :: non_neg_integer(), +- ready_state = connecting :: connecting | open | closed, +- close_msg :: {non_neg_integer(), string()}, ++%% -type(handle() :: {?MODULE, {pid(), info()}}). ++ ++-record(session, {id , %% session(), ++ outbound_queue = queue:new() , %% queue() ++ response_pid , %% pid() ++ disconnect_tref , %% reference() ++ disconnect_delay = 5000 , %% non_neg_integer() ++ heartbeat_tref , %% reference() | triggered ++ heartbeat_delay = 25000 , %% non_neg_integer() ++ ready_state = connecting , %% connecting | open | closed ++ close_msg , %% {non_neg_integer(), string()} + callback, + state, +- handle :: handle() ++ handle %% handle() + }). + -define(ETS, sockjs_table). + + +--type(session_or_undefined() :: session() | undefined). +--type(session_or_pid() :: session() | pid()). ++%% -type(session_or_undefined() :: session() | undefined). ++%% -type(session_or_pid() :: session() | pid()). + + %% -------------------------------------------------------------------------- + +--spec init() -> ok. ++%% -spec init() -> ok. + init() -> + _ = ets:new(?ETS, [public, named_table]), + ok. + +--spec start_link(session_or_undefined(), service(), info()) -> {ok, pid()}. ++%% -spec start_link(session_or_undefined(), service(), info()) -> {ok, pid()}. + start_link(SessionId, Service, Info) -> + gen_server:start_link(?MODULE, {SessionId, Service, Info}, []). + +--spec maybe_create(session_or_undefined(), service(), info()) -> pid(). ++%% -spec maybe_create(session_or_undefined(), service(), info()) -> pid(). + maybe_create(SessionId, Service, Info) -> + case ets:lookup(?ETS, SessionId) of + [] -> {ok, SPid} = sockjs_session_sup:start_child( +@@ -53,7 +53,7 @@ maybe_create(SessionId, Service, Info) -> + end. + + +--spec received(list(iodata()), session_or_pid()) -> ok. ++%% -spec received(list(iodata()), session_or_pid()) -> ok. + received(Messages, SessionPid) when is_pid(SessionPid) -> + case gen_server:call(SessionPid, {received, Messages}, infinity) of + ok -> ok; +@@ -63,27 +63,27 @@ received(Messages, SessionPid) when is_pid(SessionPid) -> + received(Messages, SessionId) -> + received(Messages, spid(SessionId)). + +--spec send(iodata(), handle()) -> ok. ++%% -spec send(iodata(), handle()) -> ok. + send(Data, {?MODULE, {SPid, _}}) -> + gen_server:cast(SPid, {send, Data}), + ok. + +--spec close(non_neg_integer(), string(), handle()) -> ok. ++%% -spec close(non_neg_integer(), string(), handle()) -> ok. + close(Code, Reason, {?MODULE, {SPid, _}}) -> + gen_server:cast(SPid, {close, Code, Reason}), + ok. + +--spec info(handle()) -> info(). ++%% -spec info(handle()) -> info(). + info({?MODULE, {_SPid, Info}}) -> + Info. + +--spec reply(session_or_pid()) -> +- wait | session_in_use | {ok | close, frame()}. ++%% -spec reply(session_or_pid()) -> ++%% wait | session_in_use | {ok | close, frame()}. + reply(Session) -> + reply(Session, true). + +--spec reply(session_or_pid(), boolean()) -> +- wait | session_in_use | {ok | close, frame()}. ++%% -spec reply(session_or_pid(), boolean()) -> ++%% wait | session_in_use | {ok | close, frame()}. + reply(SessionPid, Multiple) when is_pid(SessionPid) -> + gen_server:call(SessionPid, {reply, self(), Multiple}, infinity); + reply(SessionId, Multiple) -> +@@ -154,7 +154,7 @@ unmark_waiting(RPid, State = #session{response_pid = Pid, + when Pid =/= undefined andalso Pid =/= RPid -> + State. + +--spec emit(emittable(), #session{}) -> #session{}. ++%% -spec emit(emittable(), #session{}) -> #session{}. + emit(What, State = #session{callback = Callback, + state = UserState, + handle = Handle}) -> +@@ -175,7 +175,7 @@ emit(What, State = #session{callback = Callback, + + %% -------------------------------------------------------------------------- + +--spec init({session_or_undefined(), service(), info()}) -> {ok, #session{}}. ++%% -spec init({session_or_undefined(), service(), info()}) -> {ok, #session{}}. + init({SessionId, #service{callback = Callback, + state = UserState, + disconnect_delay = DisconnectDelay, +diff --git a/src/sockjs_session_sup.erl b/src/sockjs_session_sup.erl +index 4197ce3..71c7ff4 100644 +--- a/src/sockjs_session_sup.erl ++++ b/src/sockjs_session_sup.erl +@@ -7,7 +7,7 @@ + + %% -------------------------------------------------------------------------- + +--spec start_link() -> ignore | {'ok', pid()} | {'error', any()}. ++%% -spec start_link() -> ignore | {'ok', pid()} | {'error', any()}. + start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +diff --git a/src/sockjs_util.erl b/src/sockjs_util.erl +index be3f972..9b9969d 100644 +--- a/src/sockjs_util.erl ++++ b/src/sockjs_util.erl +@@ -8,7 +8,7 @@ + + %% -------------------------------------------------------------------------- + +--spec rand32() -> non_neg_integer(). ++%% -spec rand32() -> non_neg_integer(). + rand32() -> + case get(random_seeded) of + undefined -> +@@ -21,7 +21,7 @@ rand32() -> + random:uniform(erlang:trunc(math:pow(2,32)))-1. + + +--spec encode_frame(frame()) -> iodata(). ++%% -spec encode_frame(frame()) -> iodata(). + encode_frame({open, nil}) -> + <<"o">>; + encode_frame({close, {Code, Reason}}) -> +@@ -34,7 +34,7 @@ encode_frame({heartbeat, nil}) -> + <<"h">>. + + +--spec url_escape(string(), string()) -> iolist(). ++%% -spec url_escape(string(), string()) -> iolist(). + url_escape(Str, Chars) -> + [case lists:member(Char, Chars) of + true -> hex(Char); +diff --git a/src/sockjs_ws_handler.erl b/src/sockjs_ws_handler.erl +index bcf463d..c011c89 100644 +--- a/src/sockjs_ws_handler.erl ++++ b/src/sockjs_ws_handler.erl +@@ -6,7 +6,7 @@ + + %% -------------------------------------------------------------------------- + +--spec received(websocket|rawwebsocket, pid(), binary()) -> ok | shutdown. ++%% -spec received(websocket|rawwebsocket, pid(), binary()) -> ok | shutdown. + %% Ignore empty + received(_RawWebsocket, _SessionPid, <<>>) -> + ok; +@@ -30,7 +30,7 @@ session_received(Messages, SessionPid) -> + no_session -> shutdown + end. + +--spec reply(websocket|rawwebsocket, pid()) -> {close|open, binary()} | wait. ++%% -spec reply(websocket|rawwebsocket, pid()) -> {close|open, binary()} | wait. + reply(websocket, SessionPid) -> + case sockjs_session:reply(SessionPid) of + {W, Frame} when W =:= ok orelse W =:= close-> +@@ -52,7 +52,7 @@ reply(rawwebsocket, SessionPid) -> + wait + end. + +--spec close(websocket|rawwebsocket, pid()) -> ok. ++%% -spec close(websocket|rawwebsocket, pid()) -> ok. + close(_RawWebsocket, SessionPid) -> + SessionPid ! force_shutdown, + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/0001-a2b-b2a.diff b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/0001-a2b-b2a.diff new file mode 100644 index 0000000..b9a4d3e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/0001-a2b-b2a.diff @@ -0,0 +1,22 @@ +diff --git a/src/sockjs_http.erl b/src/sockjs_http.erl +index 5cdf431..837b64f 100644 +--- a/src/sockjs_http.erl ++++ b/src/sockjs_http.erl +@@ -15,7 +15,7 @@ path({cowboy, Req}) -> {Path, Req1} = cowboy_http_req:raw_path(Req), + %% -spec method(req()) -> {atom(), req()}. + method({cowboy, Req}) -> {Method, Req1} = cowboy_http_req:method(Req), + case is_binary(Method) of +- true -> {binary_to_atom(Method, utf8), {cowboy, Req1}}; ++ true -> {list_to_atom(binary_to_list(Method)), {cowboy, Req1}}; + false -> {Method, {cowboy, Req1}} + end. + +@@ -47,7 +47,7 @@ header(K, {cowboy, Req})-> + {H, Req2} = cowboy_http_req:header(K, Req), + {V, Req3} = case H of + undefined -> +- cowboy_http_req:header(atom_to_binary(K, utf8), Req2); ++ cowboy_http_req:header(list_to_binary(atom_to_list(K)), Req2); + _ -> {H, Req2} + end, + case V of diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/0002-parameterised-modules-r16a.diff b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/0002-parameterised-modules-r16a.diff new file mode 100644 index 0000000..29ba8a2 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/0002-parameterised-modules-r16a.diff @@ -0,0 +1,477 @@ +diff --git a/src/pmod_pt.erl b/src/pmod_pt.erl +new file mode 100644 +index 0000000..db21974 +--- /dev/null ++++ b/src/pmod_pt.erl +@@ -0,0 +1,461 @@ ++%% ++%% %CopyrightBegin% ++%% ++%% Copyright Ericsson AB 2013. All Rights Reserved. ++%% ++%% The contents of this file are subject to the Erlang Public License, ++%% Version 1.1, (the "License"); you may not use this file except in ++%% compliance with the License. You should have received a copy of the ++%% Erlang Public License along with this software. If not, it can be ++%% retrieved online at http://www.erlang.org/. ++%% ++%% Software distributed under the License is distributed on an "AS IS" ++%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See ++%% the License for the specific language governing rights and limitations ++%% under the License. ++%% ++%% %CopyrightEnd% ++%% ++ ++-module(pmod_pt). ++-export([parse_transform/2, ++ format_error/1]). ++ ++%% Expand function definition forms of parameterized module. ++%% The code is based on the code in sys_expand_pmod which used to be ++%% included in the compiler, but details are different because ++%% sys_pre_expand has not been run. In particular: ++%% ++%% * Record definitions are still present and must be handled. ++%% ++%% * (Syntatic) local calls may actually be calls to an imported ++%% funtion or a BIF. It is a local call if and only if there ++%% is a definition for the function in the module. ++%% ++%% * When we introduce the module parameters and 'THIS' in each ++%% function, we must artificially use it to avoid a warning for ++%% unused variables. ++%% ++%% * On the other hand, we don't have to worry about module_info/0,1 ++%% because they have not been added yet. ++ ++-record(pmod, {parameters, ++ defined ++ }). ++ ++parse_transform(Forms0, _Options) -> ++ put(?MODULE, []), ++ Forms = transform(Forms0), ++ case erase(?MODULE) of ++ [] -> ++ Forms; ++ [_|_]=Errors -> ++ File = get_file(Forms), ++ {error,[{File,Errors}],[]} ++ end. ++ ++format_error(extends_self) -> ++ "cannot extend from self"; ++format_error(define_instance) -> ++ "defining instance function not allowed in parameterized module". ++ ++add_error(Line, Error) -> ++ put(?MODULE, get(?MODULE) ++ [{Line,?MODULE,Error}]). ++ ++get_file([{attribute,_,file,{File,_}}|_]) -> File; ++get_file([_|T]) -> get_file(T). ++ ++transform(Forms0) -> ++ Def = collect_defined(Forms0), ++ {Base,ModAs,Forms1} = attribs(Forms0, [], undefined, []), ++ {Mod,Ps0} = case ModAs of ++ {M0,P0} -> {M0,P0}; ++ M0 -> {M0,undefined} ++ end, ++ Forms2 = case Ps0 of ++ undefined -> ++ Forms1; ++ _ -> ++ pmod_expand(Forms1, Mod, Base, Ps0, Def) ++ end, ++ ++ %% Add new functions. ++ NewFs0 = maybe_extend(Base, Mod, Ps0), ++ NewExps = collect_defined(NewFs0), ++ Forms3 = add_attributes(Forms2, [{attribute,0,export,NewExps}]), ++ add_new_funcs(Forms3, NewFs0). ++ ++pmod_expand(Forms0, Mod, Base, Ps0, Def) -> ++ Ps = if is_atom(Base) -> ++ ['BASE' | Ps0]; ++ true -> ++ Ps0 ++ end, ++ St0 = #pmod{parameters=Ps,defined=gb_sets:from_list(Def)}, ++ {Forms1,_} = forms(Forms0, St0), ++ Forms2 = update_exps(Forms1), ++ Forms3 = update_forms(Forms2), ++ NewFs0 = add_instance(Mod, Ps, []), ++ NewFs = ensure_new(Base, Ps0, NewFs0), ++ Forms = add_new_funcs(Forms3, NewFs), ++ NewExps = collect_defined(NewFs), ++ add_attributes(Forms, [{attribute,0,export,NewExps}]). ++ ++add_attributes([{attribute,_,module,_}=F|Fs], Attrs) -> ++ [F|Attrs++Fs]; ++add_attributes([F|Fs], Attrs) -> ++ [F|add_attributes(Fs, Attrs)]. ++ ++add_new_funcs([{eof,_}|_]=Fs, NewFs) -> ++ NewFs ++ Fs; ++add_new_funcs([F|Fs], Es) -> ++ [F|add_new_funcs(Fs, Es)]. ++ ++maybe_extend([], _, _) -> ++ %% No 'extends' attribute. ++ []; ++maybe_extend(Base, _Mod, undefined) -> ++ %% There is a an 'extends' attribute; the module is not parameterized. ++ Name = '$handle_undefined_function', ++ Args = [{var,0,'Func'},{var,0,'Args'}], ++ Body = [make_apply({atom,0,Base}, {var,0,'Func'}, {var,0,'Args'})], ++ F = {function,0,Name,2,[{clause,0,Args,[],Body}]}, ++ [F]; ++maybe_extend(Base, Mod, Ps) -> ++ %% There is a an 'extends' attribute; the module is parameterized. ++ Name = '$handle_undefined_function', ++ Args = [{var,0,'Func'},{var,0,'Args'}], ++ DontCares = [{var,0,'_'} || _ <- Ps], ++ TuplePs = {tuple,0,[{atom,0,Mod},{var,0,'BaseVars'}|DontCares]}, ++ G = [{call,0,{atom,0,is_atom}, ++ [{call,0,{atom,0,element}, ++ [{integer,0,1},{var,0,'BaseVars'}]}]}], ++ FixedArgs = make_lists_rev([{var,0,'Rs'}, ++ {cons,0,{var,0,'BaseVars'},{nil,0}}]), ++ Body = [{'case',0,make_lists_rev([{var,0,'Args'}]), ++ [{clause,0,[{cons,0,TuplePs,{var,0,'Rs'}}],[G], ++ [make_apply({atom,0,Base}, {var,0,'Func'}, FixedArgs)]}, ++ {clause,0,[{var,0,'_'}],[], ++ [make_apply({atom,0,Base}, {var,0,'Func'}, {var,0,'Args'})]} ++ ]}], ++ F = {function,0,Name,2,[{clause,0,Args,[],Body}]}, ++ [F]. ++ ++make_apply(M, F, A) -> ++ {call,0,{remote,0,{atom,0,erlang},{atom,0,apply}},[M,F,A]}. ++ ++make_lists_rev(As) -> ++ {call,0,{remote,0,{atom,0,lists},{atom,0,reverse}},As}. ++ ++ensure_new(Base, Ps, Fs) -> ++ case has_new(Fs) of ++ true -> ++ Fs; ++ false -> ++ add_new(Base, Ps, Fs) ++ end. ++ ++has_new([{function,_L,new,_A,_Cs} | _Fs]) -> ++ true; ++has_new([_ | Fs]) -> ++ has_new(Fs); ++has_new([]) -> ++ false. ++ ++add_new(Base, Ps, Fs) -> ++ Vs = [{var,0,V} || V <- Ps], ++ As = if is_atom(Base) -> ++ [{call,0,{remote,0,{atom,0,Base},{atom,0,new}},Vs} | Vs]; ++ true -> ++ Vs ++ end, ++ Body = [{call,0,{atom,0,instance},As}], ++ add_func(new, Vs, Body, Fs). ++ ++add_instance(Mod, Ps, Fs) -> ++ Vs = [{var,0,V} || V <- Ps], ++ AbsMod = [{tuple,0,[{atom,0,Mod}|Vs]}], ++ add_func(instance, Vs, AbsMod, Fs). ++ ++add_func(Name, Args, Body, Fs) -> ++ A = length(Args), ++ F = {function,0,Name,A,[{clause,0,Args,[],Body}]}, ++ [F|Fs]. ++ ++collect_defined(Fs) -> ++ [{N,A} || {function,_,N,A,_} <- Fs]. ++ ++attribs([{attribute,Line,module,{Mod,_}=ModAs}|T], Base, _, Acc) -> ++ attribs(T, Base, ModAs, [{attribute,Line,module,Mod}|Acc]); ++attribs([{attribute,_,module,Mod}=H|T], Base, _, Acc) -> ++ attribs(T, Base, Mod, [H|Acc]); ++attribs([{attribute,Line,extends,Base}|T], Base0, Ps, Acc) when is_atom(Base) -> ++ Mod = case Ps of ++ {Mod0,_} -> Mod0; ++ Mod0 -> Mod0 ++ end, ++ case Mod of ++ Base -> ++ add_error(Line, extends_self), ++ attribs(T, Base0, Ps, Acc); ++ _ -> ++ attribs(T, Base, Ps, Acc) ++ end; ++attribs([H|T], Base, Ps, Acc) -> ++ attribs(T, Base, Ps, [H|Acc]); ++attribs([], Base, Ps, Acc) -> ++ {Base,Ps,lists:reverse(Acc)}. ++ ++%% This is extremely simplistic for now; all functions get an extra ++%% parameter, whether they need it or not, except for static functions. ++ ++update_function_name({F,A}) when F =/= new -> ++ {F,A+1}; ++update_function_name(E) -> ++ E. ++ ++update_forms([{function,L,N,A,Cs}|Fs]) when N =/= new -> ++ [{function,L,N,A+1,Cs}|update_forms(Fs)]; ++update_forms([F|Fs]) -> ++ [F|update_forms(Fs)]; ++update_forms([]) -> ++ []. ++ ++update_exps([{attribute,Line,export,Es0}|T]) -> ++ Es = [update_function_name(E) || E <- Es0], ++ [{attribute,Line,export,Es}|update_exps(T)]; ++update_exps([H|T]) -> ++ [H|update_exps(T)]; ++update_exps([]) -> ++ []. ++ ++%% Process the program forms. ++ ++forms([F0|Fs0],St0) -> ++ {F1,St1} = form(F0,St0), ++ {Fs1,St2} = forms(Fs0,St1), ++ {[F1|Fs1],St2}; ++forms([], St0) -> ++ {[], St0}. ++ ++%% Only function definitions are of interest here. State is not updated. ++form({function,Line,instance,_Arity,_Clauses}=F,St) -> ++ add_error(Line, define_instance), ++ {F,St}; ++form({function,Line,Name0,Arity0,Clauses0},St) when Name0 =/= new -> ++ {Name,Arity,Clauses} = function(Name0, Arity0, Clauses0, St), ++ {{function,Line,Name,Arity,Clauses},St}; ++%% Pass anything else through ++form(F,St) -> {F,St}. ++ ++function(Name, Arity, Clauses0, St) -> ++ Clauses1 = clauses(Clauses0,St), ++ {Name,Arity,Clauses1}. ++ ++clauses([C|Cs],#pmod{parameters=Ps}=St) -> ++ {clause,L,H,G,B0} = clause(C,St), ++ T = {tuple,L,[{var,L,V} || V <- ['_'|Ps]]}, ++ B = [{match,L,{var,L,'_'},{var,L,V}} || V <- ['THIS'|Ps]] ++ B0, ++ [{clause,L,H++[{match,L,T,{var,L,'THIS'}}],G,B}|clauses(Cs,St)]; ++clauses([],_St) -> []. ++ ++clause({clause,Line,H,G,B0},St) -> ++ %% We never update H and G, so we will just copy them. ++ B1 = exprs(B0,St), ++ {clause,Line,H,G,B1}. ++ ++pattern_grp([{bin_element,L1,E1,S1,T1} | Fs],St) -> ++ S2 = case S1 of ++ default -> ++ default; ++ _ -> ++ expr(S1,St) ++ end, ++ T2 = case T1 of ++ default -> ++ default; ++ _ -> ++ bit_types(T1) ++ end, ++ [{bin_element,L1,expr(E1,St),S2,T2} | pattern_grp(Fs,St)]; ++pattern_grp([],_St) -> ++ []. ++ ++bit_types([]) -> ++ []; ++bit_types([Atom | Rest]) when is_atom(Atom) -> ++ [Atom | bit_types(Rest)]; ++bit_types([{Atom, Integer} | Rest]) when is_atom(Atom), is_integer(Integer) -> ++ [{Atom, Integer} | bit_types(Rest)]. ++ ++exprs([E0|Es],St) -> ++ E1 = expr(E0,St), ++ [E1|exprs(Es,St)]; ++exprs([],_St) -> []. ++ ++expr({var,_L,_V}=Var,_St) -> ++ Var; ++expr({integer,_Line,_I}=Integer,_St) -> Integer; ++expr({float,_Line,_F}=Float,_St) -> Float; ++expr({atom,_Line,_A}=Atom,_St) -> Atom; ++expr({string,_Line,_S}=String,_St) -> String; ++expr({char,_Line,_C}=Char,_St) -> Char; ++expr({nil,_Line}=Nil,_St) -> Nil; ++expr({cons,Line,H0,T0},St) -> ++ H1 = expr(H0,St), ++ T1 = expr(T0,St), ++ {cons,Line,H1,T1}; ++expr({lc,Line,E0,Qs0},St) -> ++ Qs1 = lc_bc_quals(Qs0,St), ++ E1 = expr(E0,St), ++ {lc,Line,E1,Qs1}; ++expr({bc,Line,E0,Qs0},St) -> ++ Qs1 = lc_bc_quals(Qs0,St), ++ E1 = expr(E0,St), ++ {bc,Line,E1,Qs1}; ++expr({tuple,Line,Es0},St) -> ++ Es1 = expr_list(Es0,St), ++ {tuple,Line,Es1}; ++expr({record,Line,Name,Is0},St) -> ++ Is = record_fields(Is0,St), ++ {record,Line,Name,Is}; ++expr({record,Line,E0,Name,Is0},St) -> ++ E = expr(E0,St), ++ Is = record_fields(Is0,St), ++ {record,Line,E,Name,Is}; ++expr({record_field,Line,E0,Name,Key},St) -> ++ E = expr(E0,St), ++ {record_field,Line,E,Name,Key}; ++expr({block,Line,Es0},St) -> ++ Es1 = exprs(Es0,St), ++ {block,Line,Es1}; ++expr({'if',Line,Cs0},St) -> ++ Cs1 = icr_clauses(Cs0,St), ++ {'if',Line,Cs1}; ++expr({'case',Line,E0,Cs0},St) -> ++ E1 = expr(E0,St), ++ Cs1 = icr_clauses(Cs0,St), ++ {'case',Line,E1,Cs1}; ++expr({'receive',Line,Cs0},St) -> ++ Cs1 = icr_clauses(Cs0,St), ++ {'receive',Line,Cs1}; ++expr({'receive',Line,Cs0,To0,ToEs0},St) -> ++ To1 = expr(To0,St), ++ ToEs1 = exprs(ToEs0,St), ++ Cs1 = icr_clauses(Cs0,St), ++ {'receive',Line,Cs1,To1,ToEs1}; ++expr({'try',Line,Es0,Scs0,Ccs0,As0},St) -> ++ Es1 = exprs(Es0,St), ++ Scs1 = icr_clauses(Scs0,St), ++ Ccs1 = icr_clauses(Ccs0,St), ++ As1 = exprs(As0,St), ++ {'try',Line,Es1,Scs1,Ccs1,As1}; ++expr({'fun',_,{function,_,_,_}}=ExtFun,_St) -> ++ ExtFun; ++expr({'fun',Line,Body},St) -> ++ case Body of ++ {clauses,Cs0} -> ++ Cs1 = fun_clauses(Cs0,St), ++ {'fun',Line,{clauses,Cs1}}; ++ {function,F,A} = Function -> ++ {F1,A1} = update_function_name({F,A}), ++ if A1 =:= A -> ++ {'fun',Line,Function}; ++ true -> ++ %% Must rewrite local fun-name to a fun that does a ++ %% call with the extra THIS parameter. ++ As = make_vars(A, Line), ++ As1 = As ++ [{var,Line,'THIS'}], ++ Call = {call,Line,{atom,Line,F1},As1}, ++ Cs = [{clause,Line,As,[],[Call]}], ++ {'fun',Line,{clauses,Cs}} ++ end; ++ {function,_M,_F,_A} = Fun4 -> %This is an error in lint! ++ {'fun',Line,Fun4} ++ end; ++expr({call,Lc,{atom,_,instance}=Name,As0},St) -> ++ %% All local functions 'instance(...)' are static by definition, ++ %% so they do not take a 'THIS' argument when called ++ As1 = expr_list(As0,St), ++ {call,Lc,Name,As1}; ++expr({call,Lc,{atom,_,new}=Name,As0},St) -> ++ %% All local functions 'new(...)' are static by definition, ++ %% so they do not take a 'THIS' argument when called ++ As1 = expr_list(As0,St), ++ {call,Lc,Name,As1}; ++expr({call,Lc,{atom,_Lf,F}=Atom,As0}, #pmod{defined=Def}=St) -> ++ As1 = expr_list(As0,St), ++ case gb_sets:is_member({F,length(As0)}, Def) of ++ false -> ++ %% BIF or imported function. ++ {call,Lc,Atom,As1}; ++ true -> ++ %% Local function call - needs THIS parameter. ++ {call,Lc,Atom,As1 ++ [{var,0,'THIS'}]} ++ end; ++expr({call,Line,F0,As0},St) -> ++ %% Other function call ++ F1 = expr(F0,St), ++ As1 = expr_list(As0,St), ++ {call,Line,F1,As1}; ++expr({'catch',Line,E0},St) -> ++ E1 = expr(E0,St), ++ {'catch',Line,E1}; ++expr({match,Line,P,E0},St) -> ++ E1 = expr(E0,St), ++ {match,Line,P,E1}; ++expr({bin,Line,Fs},St) -> ++ Fs2 = pattern_grp(Fs,St), ++ {bin,Line,Fs2}; ++expr({op,Line,Op,A0},St) -> ++ A1 = expr(A0,St), ++ {op,Line,Op,A1}; ++expr({op,Line,Op,L0,R0},St) -> ++ L1 = expr(L0,St), ++ R1 = expr(R0,St), ++ {op,Line,Op,L1,R1}; ++%% The following are not allowed to occur anywhere! ++expr({remote,Line,M0,F0},St) -> ++ M1 = expr(M0,St), ++ F1 = expr(F0,St), ++ {remote,Line,M1,F1}. ++ ++expr_list([E0|Es],St) -> ++ E1 = expr(E0,St), ++ [E1|expr_list(Es,St)]; ++expr_list([],_St) -> []. ++ ++record_fields([{record_field,L,K,E0}|T],St) -> ++ E = expr(E0,St), ++ [{record_field,L,K,E}|record_fields(T,St)]; ++record_fields([],_) -> []. ++ ++icr_clauses([C0|Cs],St) -> ++ C1 = clause(C0,St), ++ [C1|icr_clauses(Cs,St)]; ++icr_clauses([],_St) -> []. ++ ++lc_bc_quals([{generate,Line,P,E0}|Qs],St) -> ++ E1 = expr(E0,St), ++ [{generate,Line,P,E1}|lc_bc_quals(Qs,St)]; ++lc_bc_quals([{b_generate,Line,P,E0}|Qs],St) -> ++ E1 = expr(E0,St), ++ [{b_generate,Line,P,E1}|lc_bc_quals(Qs,St)]; ++lc_bc_quals([E0|Qs],St) -> ++ E1 = expr(E0,St), ++ [E1|lc_bc_quals(Qs,St)]; ++lc_bc_quals([],_St) -> []. ++ ++fun_clauses([C0|Cs],St) -> ++ C1 = clause(C0,St), ++ [C1|fun_clauses(Cs,St)]; ++fun_clauses([],_St) -> []. ++ ++make_vars(N, L) -> ++ make_vars(1, N, L). ++ ++make_vars(N, M, L) when N =< M -> ++ V = list_to_atom("X"++integer_to_list(N)), ++ [{var,L,V} | make_vars(N + 1, M, L)]; ++make_vars(_, _, _) -> ++ []. +diff --git a/src/sockjs_multiplex_channel.erl b/src/sockjs_multiplex_channel.erl +index cbb8274..5afcfa3 100644 +--- a/src/sockjs_multiplex_channel.erl ++++ b/src/sockjs_multiplex_channel.erl +@@ -1,3 +1,5 @@ ++-compile({parse_transform,pmod_pt}). ++ + -module(sockjs_multiplex_channel, [Conn, Topic]). + + -export([send/1, close/0, close/2, info/0]). diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/0003-websocket-subprotocol b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/0003-websocket-subprotocol new file mode 100644 index 0000000..be298cb --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/0003-websocket-subprotocol @@ -0,0 +1,93 @@ +diff --git a/src/sockjs_cowboy_handler.erl b/src/sockjs_cowboy_handler.erl +index 3b1ffe3..d2f05ae 100644 +--- a/src/sockjs_cowboy_handler.erl ++++ b/src/sockjs_cowboy_handler.erl +@@ -30,21 +30,35 @@ terminate(_Req, _Service) -> + + %% -------------------------------------------------------------------------- + +-websocket_init(_TransportName, Req, Service = #service{logger = Logger}) -> +- Req0 = Logger(Service, {cowboy, Req}, websocket), ++websocket_init(_TransportName, Req, ++ Service = #service{logger = Logger, ++ subproto_pref = SubProtocolPref}) -> ++ Req3 = case cowboy_http_req:header(<<"Sec-Websocket-Protocol">>, Req) of ++ {undefined, Req1} -> ++ Req1; ++ {SubProtocols, Req1} -> ++ SelectedSubProtocol = ++ choose_subprotocol_bin(SubProtocols, SubProtocolPref), ++ {ok, Req2} = cowboy_http_req:set_resp_header( ++ <<"Sec-Websocket-Protocol">>, ++ SelectedSubProtocol, Req1), ++ Req2 ++ end, ++ ++ Req4 = Logger(Service, {cowboy, Req3}, websocket), + + Service1 = Service#service{disconnect_delay = 5*60*1000}, + +- {Info, Req1} = sockjs_handler:extract_info(Req0), ++ {Info, Req5} = sockjs_handler:extract_info(Req4), + SessionPid = sockjs_session:maybe_create(undefined, Service1, Info), +- {RawWebsocket, {cowboy, Req3}} = +- case sockjs_handler:get_action(Service, Req1) of +- {{match, WS}, Req2} when WS =:= websocket orelse ++ {RawWebsocket, {cowboy, Req7}} = ++ case sockjs_handler:get_action(Service, Req5) of ++ {{match, WS}, Req6} when WS =:= websocket orelse + WS =:= rawwebsocket -> +- {WS, Req2} ++ {WS, Req6} + end, + self() ! go, +- {ok, Req3, {RawWebsocket, SessionPid}}. ++ {ok, Req7, {RawWebsocket, SessionPid}}. + + websocket_handle({text, Data}, Req, {RawWebsocket, SessionPid} = S) -> + case sockjs_ws_handler:received(RawWebsocket, SessionPid, Data) of +@@ -69,3 +83,15 @@ websocket_info(shutdown, Req, S) -> + websocket_terminate(_Reason, _Req, {RawWebsocket, SessionPid}) -> + sockjs_ws_handler:close(RawWebsocket, SessionPid), + ok. ++ ++%% -------------------------------------------------------------------------- ++ ++choose_subprotocol_bin(SubProtocols, Pref) -> ++ choose_subprotocol(re:split(SubProtocols, ", *"), Pref). ++choose_subprotocol(SubProtocols, undefined) -> ++ erlang:hd(lists:reverse(lists:sort(SubProtocols))); ++choose_subprotocol(SubProtocols, Pref) -> ++ case lists:filter(fun (E) -> lists:member(E, SubProtocols) end, Pref) of ++ [Hd | _] -> Hd; ++ [] -> choose_subprotocol(SubProtocols, undefined) ++ end. +diff --git a/src/sockjs_handler.erl b/src/sockjs_handler.erl +index b706453..81d4ef7 100644 +--- a/src/sockjs_handler.erl ++++ b/src/sockjs_handler.erl +@@ -29,7 +29,9 @@ init_state(Prefix, Callback, State, Options) -> + response_limit = + proplists:get_value(response_limit, Options, 128*1024), + logger = +- proplists:get_value(logger, Options, fun default_logger/3) ++ proplists:get_value(logger, Options, fun default_logger/3), ++ subproto_pref = ++ proplists:get_value(subproto_pref, Options) + }. + + %% -------------------------------------------------------------------------- +diff --git a/src/sockjs_internal.hrl b/src/sockjs_internal.hrl +index 629b2fe..eed5597 100644 +--- a/src/sockjs_internal.hrl ++++ b/src/sockjs_internal.hrl +@@ -15,7 +15,8 @@ + disconnect_delay , %% non_neg_integer() + heartbeat_delay , %% non_neg_integer() + response_limit , %% non_neg_integer() +- logger %% logger() ++ logger , %% logger() ++ subproto_pref %% [binary()] + }). + + %% -type(service() :: #service{}). diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/Makefile b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/Makefile new file mode 100644 index 0000000..482105a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/Makefile @@ -0,0 +1 @@ +include ../umbrella.mk diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/generate-0000-remove-spec-patch.sh b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/generate-0000-remove-spec-patch.sh new file mode 100644 index 0000000..fc2067f --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/generate-0000-remove-spec-patch.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# To update the patch run this script. +cd sockjs-erlang-git/src +git checkout * +sed 's#^\(-type.*\)#%% \1#g' -i * +sed 's#^\(-spec.*\)#%% \1#g' -i * +sed 's#^\(-record.*\)::\(.*\)$#\1, %% \2#g' * -i +sed 's#^\( .*\)::\(.*\),$#\1, %% \2#g' * -i +sed 's#^\( .*\)::\(.*\)$#\1 %% \2#g' * -i +git diff > ../../0000-remove-spec-patch.diff diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/hash.mk b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/hash.mk new file mode 100644 index 0000000..45a2ab1 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/hash.mk @@ -0,0 +1 @@ +UPSTREAM_SHORT_HASH:=3132eb9 diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/package.mk b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/package.mk new file mode 100644 index 0000000..3613309 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/package.mk @@ -0,0 +1,27 @@ +APP_NAME:=sockjs +DEPS:=cowboy-wrapper + +UPSTREAM_GIT:=https://github.com/rabbitmq/sockjs-erlang.git +UPSTREAM_REVISION:=3132eb920aea9abd5c5e65349331c32d8cfa961e # 0.3.4 +RETAIN_ORIGINAL_VERSION:=true +WRAPPER_PATCHES:=\ + 0000-remove-spec-patch.diff \ + 0001-a2b-b2a.diff \ + 0002-parameterised-modules-r16a.diff \ + 0003-websocket-subprotocol + +ORIGINAL_APP_FILE:=$(CLONE_DIR)/src/$(APP_NAME).app.src +DO_NOT_GENERATE_APP_FILE=true + +ERLC_OPTS:=$(ERLC_OPTS) -D no_specs + +define construct_app_commands + cp $(CLONE_DIR)/LICENSE-* $(APP_DIR) + rm $(APP_DIR)/ebin/pmod_pt.beam +endef + +define package_rules + +$(CLONE_DIR)/ebin/sockjs_multiplex_channel.beam: $(CLONE_DIR)/ebin/pmod_pt.beam + +endef diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/.done b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/.done new file mode 100644 index 0000000..e69de29 diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/COPYING b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/COPYING new file mode 100644 index 0000000..4f1c463 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/COPYING @@ -0,0 +1,10 @@ +All code is released under the MIT license (see LICENSE-MIT-SockJS) +with the exception of following files: + + * src/mochijson2_fork.erl and src/mochinum_fork.erl, which are forked + from Mochiweb project (https://github.com/mochi/mochiweb) and + covered by LICENSE-MIT-Mochiweb. + + * rebar, which is a compiled binary from Rebar project + (https://github.com/basho/rebar) and covered by LICENSE-APL2-Rebar. + diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/Changelog b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/Changelog new file mode 100644 index 0000000..e82a9ca --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/Changelog @@ -0,0 +1,30 @@ +0.3.4 +===== + + * #41 - fix a traceback when websocket is too slow or too + busy to prompty process incoming and outgoing data + * #37 - make porting to new cowboy a tiny bit easier + (allow 'method' to be a binary) + + +0.3.3 +===== + + * sockjs/sockjs-protocol#56 Fix for iOS 6 caching POSTs + + +0.3.0 +===== + + * Fixed {odd_info, heartbeat_triggered} exception (Isaev Ivan) + * Changes to pass sockjs-protocol-0.3 + * Fixed sockname badmatch (Egobrain) + * Updated README + * Introduced parametrized module API (to get multiplexer working). + * Introduced Multiplexer example. + * Fixed invalid catch in sockjs_json:decode (Isaev Ivan) + * Bumped Cowboy version. + * Specs were moved around to make R13 happy + * Dropped milsultin support. + + diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/LICENSE-APL2-Rebar b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/LICENSE-APL2-Rebar new file mode 100644 index 0000000..e454a52 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/LICENSE-APL2-Rebar @@ -0,0 +1,178 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/LICENSE-MIT-Mochiweb b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/LICENSE-MIT-Mochiweb new file mode 100644 index 0000000..7b7c506 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/LICENSE-MIT-Mochiweb @@ -0,0 +1,22 @@ +This is the MIT license. + +Copyright (c) 2007 Mochi Media, Inc. + +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/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/LICENSE-MIT-SockJS b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/LICENSE-MIT-SockJS new file mode 100644 index 0000000..a897167 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/LICENSE-MIT-SockJS @@ -0,0 +1,19 @@ +Copyright (C) 2011 VMware, Inc. + +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/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/Makefile b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/Makefile new file mode 100644 index 0000000..2dd20d6 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/Makefile @@ -0,0 +1,80 @@ +REBAR=./rebar + +.PHONY: all clean distclean +all: deps + $(REBAR) compile + +deps: + $(REBAR) get-deps + +clean:: + $(REBAR) clean + rm -rf priv/www + +distclean:: + rm -rf deps priv ebin + + +# **** serve **** + +.PHONY: serve +SERVE_SCRIPT=./examples/cowboy_test_server.erl +serve: + @if [ -e .pidfile.pid ]; then \ + kill `cat .pidfile.pid`; \ + rm .pidfile.pid; \ + fi + + @while [ 1 ]; do \ + $(REBAR) compile && ( \ + echo " [*] Running erlang"; \ + $(SERVE_SCRIPT) & \ + SRVPID=$$!; \ + echo $$SRVPID > .pidfile.pid; \ + echo " [*] Pid: $$SRVPID"; \ + ); \ + inotifywait -r -q -e modify src/*erl examples/*erl src/*hrl; \ + test -e .pidfile.pid && kill `cat .pidfile.pid`; \ + rm -f .pidfile.pid; \ + sleep 0.1; \ + done + + +# **** dialyzer **** + +.dialyzer_generic.plt: + dialyzer \ + --build_plt \ + --output_plt .dialyzer_generic.plt \ + --apps erts kernel stdlib compiler sasl os_mon mnesia \ + tools public_key crypto ssl + +.dialyzer_sockjs.plt: .dialyzer_generic.plt + dialyzer \ + --no_native \ + --add_to_plt \ + --plt .dialyzer_generic.plt \ + --output_plt .dialyzer_sockjs.plt -r deps/*/ebin + +distclean:: + rm -f .dialyzer_sockjs.plt + +dialyze: .dialyzer_sockjs.plt + @dialyzer \ + --plt .dialyzer_sockjs.plt \ + --no_native \ + --fullpath \ + -Wrace_conditions \ + -Werror_handling \ + -Wunmatched_returns \ + ebin + +.PHONY: xref +xref: + $(REBAR) xref | egrep -v unused + + +# **** release **** +# 1. Commit +# 2. Bump version in "src/sockjs.app.src" +# 3. git tag -s "vx.y.z" -m "Release vx.y.z" diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/README.md b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/README.md new file mode 100644 index 0000000..6c63917 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/README.md @@ -0,0 +1,220 @@ +SockJS family: + + * [SockJS-client](https://github.com/sockjs/sockjs-client) JavaScript client library + * [SockJS-node](https://github.com/sockjs/sockjs-node) Node.js server + * [SockJS-erlang](https://github.com/sockjs/sockjs-erlang) Erlang server + + +SockJS-erlang server +==================== + +[SockJS](http://sockjs.org) server written in Erlang. Can run with +[Cowboy](https://github.com/extend/cowboy) http server. SockJS-erlang +is in core web-framework agnostic (up to version +[v0.2.1](https://github.com/sockjs/sockjs-erlang/tree/v0.2.1 ) we also +supported +[Misultin](https://github.com/ostinelli/misultin)). SockJS-erlang is +compatible with +[SockJS client version 0.3](http://sockjs.github.com/sockjs-protocol/sockjs-protocol-0.3.html). See +https://github.com/sockjs/sockjs-client for more information on +SockJS. + + +Show me the code! +----------------- + +A simplistic echo SockJS server using Cowboy may look more or less +like this: + +```erlang +main(_) -> + application:start(sockjs), + application:start(cowboy), + + SockjsState = sockjs_handler:init_state( + <<"/echo">>, fun service_echo/3, state, []), + + Routes = [{'_', [{[<<"echo">>, '...'], + sockjs_cowboy_handler, SockjsState}]}], + + cowboy:start_listener(http, 100, + cowboy_tcp_transport, [{port, 8081}], + cowboy_http_protocol, [{dispatch, Routes}]), + receive + _ -> ok + end. + +service_echo(_Conn, init, state) -> {ok, state}; +service_echo(Conn, {recv, Data}, state) -> Conn:send(Data); +service_echo(_Conn, closed, state) -> {ok, state}. +``` + +Dig into the `examples` directory to get working code: + + * https://github.com/sockjs/sockjs-erlang/examples/cowboy_echo.erl + + +How to run the examples? +------------------------ + +You may need a recent version of Erlang/OTP, at least R14B is recommended. + +To run Cowboy example: + + cd sockjs-erlang + ./rebar get-deps + ./rebar compile + ./examples/cowboy_echo.erl + +This will start a simple `/echo` SockJS server on +`http://localhost:8081`. Open this link in a browser and play +around. + + +SockJS-erlang API +----------------- + +Except for the web framework-specific API's, SockJS-erlang is rather +simple. It has just a couple of methods: + + * **sockjs_handler:init_state(prefix, callback, state, options) -> service()** + + Initializes the state of a SockJS service (ie: a thing you can + access from the browser, it has an url and a code on the server + side). `prefix` is a binary that must exacty match the url prefix + of the service, for example, if service will be listening on + '/echo', this parameter must be set to `<<"/echo">>`. `callback` + function will be called when a new SockJS connection is + established, data received or a connection is closed. The value of + `state` will be passed to the callback and preserved if returned + value has changed. Options is a proplist that can contain + following tuples: + + * `{sockjs_url, string()}` - Transports which don't support + cross-domain communication natively ('eventsource' to name one) + use an iframe trick. A simple page is served from the SockJS + server (using its foreign domain) and is placed in an invisible + iframe. Code run from this iframe doesn't need to worry about + cross-domain issues, as it's being run from domain local to the + SockJS server. This iframe also does need to load SockJS + javascript client library, and this option lets you specify its + url (if you're unsure, point it to the latest + minified SockJS client release, this is the default). + * `{websocket, boolean()}` - are native websockets enabled? This + can be usefull when your loadbalancer doesn't support them. + * `{cookie_needed, boolean()}` - is your load balancer relying on + cookies to get sticky sessions working? + * `{heartbeat_delay, integer()}` - how often to send heartbeat + packets (in ms). + * `{disconnect_delay, integer()}` - how long to hold session state + after the client was last connected (in ms). + * `{response_limit, integer()}` - the maximum size of a single + http streaming response (in bytes). + * `{logger, fun/3}` - a function called on every request, used + to print request to the logs (or on the screen by default). + + For more explanation, please do take a look at + [SockJS-node readme](https://github.com/sockjs/sockjs-node/blob/master/README.md). + + * **Connection:send(payload) -> ok** + + Send data over an active SockJS connection. Payload should be of + iodata() type. Messages sent after connection gets closed will be + lost. + + * **Connection:close(code, reason) -> ok** + + Close an active SockJS connection with code and reason. If code + and reason are skipped, the defaults are used. + + * **Connection:info() -> proplist()** + + Sometimes you may want to know more about the underlying + connection. This method returns a proplist with few attributes + extracted from the first HTTP/websocket request that was coming + to this connection. You should see: + + * peername - ip address and port of the remote host + * sockname - ip address and port of the local endpoint + * path - the path used by the request that started the connection + * headers - a set of headers extracted from the request that + may be handy (don't expect to retrieve Cookie header). + + +The framework-specific calls are more problematic. Instead of trying +to explain how to use them, please take a look at the examples. + + * **type(req() :: {cowboy, request()})** + * **sockjs_handler:handle_req(service(), req()) -> req()** + * **sockjs_handler:handle_ws(service(), req()) -> req()** + + +Stability +--------- + +SockJS-erlang is quite new, but should be reasonably stable. Cowboy is passes all the +[SockJS-protocol tests](https://github.com/sockjs/sockjs-protocol). + +Deployment and load balancing +----------------------------- + +SockJS servers should work well behind many load balancer setups, but +it sometimes requres some additional twaks. For more details, please +do take a look at the 'Deployment' section in +[SockJS-node readme](https://github.com/sockjs/sockjs-node/blob/master/README.md). + + +Development and testing +----------------------- + +You need [rebar](https://github.com/basho/rebar) +([instructions](https://github.com/basho/rebar/wiki/Building-rebar)). +Due to a bug in rebar config handling you need a reasonably recent +version - newer than late Oct 2011. Alternatively, SockJS-erlang is +bundeled with a recent rebar binary. + +SockJS-erlang contains a `test_server`, a simple server used for +testing. + +To run Cowboy test_server: + + cd sockjs-erlang + ./rebar get-deps + ./rebar compile + ./examples/cowboy_test_server.erl + +That should start test_server on port 8081. Currently, there are two +separate test suits using test_server. + +### SockJS-protocol Python tests + +Once test_server is listening on `http://localhost:8081` you may test it +using SockJS-protocol: + + cd sockjs-protocol + make test_deps + ./venv/bin/python sockjs-protocol-dev.py + +For details see +[SockJS-protocol README](https://github.com/sockjs/sockjs-protocol#readme). + +### SockJS-client QUnit tests + +You need to start a second web server (by default listening on 8080) +that is serving various static html and javascript files: + + cd sockjs-client + make test + +At that point you should have two web servers running: sockjs-erlang on +8081 and sockjs-client on 8080. When you open the browser on +[http://localhost:8080/](http://localhost:8080/) you should be able +run the QUnit tests against your sockjs-node server. + +For details see +[SockJS-client README](https://github.com/sockjs/sockjs-client#readme). + +Additionally, if you're doing more serious development consider using +`make serve`, which will automatically the server when you modify the +source code. diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/cowboy_echo.erl b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/cowboy_echo.erl new file mode 100755 index 0000000..41f969d --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/cowboy_echo.erl @@ -0,0 +1,50 @@ +#!/usr/bin/env escript +%%! -smp disable +A1 +K true -pa ebin deps/cowboy/ebin -input +-module(cowboy_echo). +-mode(compile). + +-export([main/1]). + +%% Cowboy callbacks +-export([init/3, handle/2, terminate/2]). + + +main(_) -> + Port = 8081, + application:start(sockjs), + application:start(cowboy), + + SockjsState = sockjs_handler:init_state( + <<"/echo">>, fun service_echo/3, state, []), + + VhostRoutes = [{[<<"echo">>, '...'], sockjs_cowboy_handler, SockjsState}, + {'_', ?MODULE, []}], + Routes = [{'_', VhostRoutes}], % any vhost + + io:format(" [*] Running at http://localhost:~p~n", [Port]), + cowboy:start_listener(http, 100, + cowboy_tcp_transport, [{port, Port}], + cowboy_http_protocol, [{dispatch, Routes}]), + receive + _ -> ok + end. + +%% -------------------------------------------------------------------------- + +init({_Any, http}, Req, []) -> + {ok, Req, []}. + +handle(Req, State) -> + {ok, Data} = file:read_file("./examples/echo.html"), + {ok, Req1} = cowboy_http_req:reply(200, [{<<"Content-Type">>, "text/html"}], + Data, Req), + {ok, Req1, State}. + +terminate(_Req, _State) -> + ok. + +%% -------------------------------------------------------------------------- + +service_echo(_Conn, init, state) -> {ok, state}; +service_echo(Conn, {recv, Data}, state) -> Conn:send(Data); +service_echo(_Conn, closed, state) -> {ok, state}. diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/cowboy_test_server.erl b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/cowboy_test_server.erl new file mode 100755 index 0000000..72a09a8 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/cowboy_test_server.erl @@ -0,0 +1,99 @@ +#!/usr/bin/env escript +%%! -smp disable +A1 +K true -pa ebin deps/cowboy/ebin -input +-module(cowboy_test_server). +-mode(compile). + +-export([main/1]). + +%% Cowboy callbacks +-export([init/3, handle/2, terminate/2]). + + +main(_) -> + Port = 8081, + application:start(sockjs), + application:start(cowboy), + + StateEcho = sockjs_handler:init_state( + <<"/echo">>, fun service_echo/3, state, + [{response_limit, 4096}]), + StateClose = sockjs_handler:init_state( + <<"/close">>, fun service_close/3, state, []), + StateAmplify = sockjs_handler:init_state( + <<"/amplify">>, fun service_amplify/3, state, []), + StateBroadcast = sockjs_handler:init_state( + <<"/broadcast">>, fun service_broadcast/3, state, []), + StateDWSEcho = sockjs_handler:init_state( + <<"/disabled_websocket_echo">>, fun service_echo/3, state, + [{websocket, false}]), + StateCNEcho = sockjs_handler:init_state( + <<"/cookie_needed_echo">>, fun service_echo/3, state, + [{cookie_needed, true}]), + + VRoutes = [{[<<"echo">>, '...'], sockjs_cowboy_handler, StateEcho}, + {[<<"close">>, '...'], sockjs_cowboy_handler, StateClose}, + {[<<"amplify">>, '...'], sockjs_cowboy_handler, StateAmplify}, + {[<<"broadcast">>, '...'], sockjs_cowboy_handler, StateBroadcast}, + {[<<"disabled_websocket_echo">>, '...'], sockjs_cowboy_handler, + StateDWSEcho}, + {[<<"cookie_needed_echo">>, '...'], sockjs_cowboy_handler, + StateCNEcho}, + {'_', ?MODULE, []}], + Routes = [{'_', VRoutes}], % any vhost + + io:format(" [*] Running at http://localhost:~p~n", [Port]), + cowboy:start_listener(http, 100, + cowboy_tcp_transport, [{port, Port}], + cowboy_http_protocol, [{dispatch, Routes}]), + receive + _ -> ok + end. + +%% -------------------------------------------------------------------------- + +init({_Any, http}, Req, []) -> + {ok, Req, []}. + +handle(Req, State) -> + {ok, Req2} = cowboy_http_req:reply(404, [], + <<"404 - Nothing here (via sockjs-erlang fallback)\n">>, Req), + {ok, Req2, State}. + +terminate(_Req, _State) -> + ok. + +%% -------------------------------------------------------------------------- + +service_echo(_Conn, init, state) -> {ok, state}; +service_echo(Conn, {recv, Data}, state) -> Conn:send(Data); +service_echo(_Conn, closed, state) -> {ok, state}. + +service_close(Conn, _, _State) -> + Conn:close(3000, "Go away!"). + +service_amplify(Conn, {recv, Data}, _State) -> + N0 = list_to_integer(binary_to_list(Data)), + N = if N0 > 0 andalso N0 < 19 -> N0; + true -> 1 + end, + Conn:send(list_to_binary( + string:copies("x", round(math:pow(2, N))))); +service_amplify(_Conn, _, _State) -> + ok. + +service_broadcast(Conn, init, _State) -> + case ets:info(broadcast_table, memory) of + undefined -> + ets:new(broadcast_table, [public, named_table]); + _Any -> + ok + end, + true = ets:insert(broadcast_table, {Conn}), + ok; +service_broadcast(Conn, closed, _State) -> + true = ets:delete_object(broadcast_table, {Conn}), + ok; +service_broadcast(_Conn, {recv, Data}, _State) -> + ets:foldl(fun({Conn1}, _Acc) -> Conn1:send(Data) end, + [], broadcast_table), + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/echo.html b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/echo.html new file mode 100644 index 0000000..180cb4a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/echo.html @@ -0,0 +1,72 @@ + + + + + + +

    SockJS-erlang Echo example

    +
    + +
    +
    + + diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/multiplex/cowboy_multiplex.erl b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/multiplex/cowboy_multiplex.erl new file mode 100755 index 0000000..087374b --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/multiplex/cowboy_multiplex.erl @@ -0,0 +1,86 @@ +#!/usr/bin/env escript +%%! -smp disable +A1 +K true -pa ebin deps/cowboy/ebin -input +-module(cowboy_multiplex). +-mode(compile). + +-export([main/1]). + +%% Cowboy callbacks +-export([init/3, handle/2, terminate/2]). + +main(_) -> + Port = 8081, + application:start(sockjs), + application:start(cowboy), + + MultiplexState = sockjs_multiplex:init_state( + [{"ann", fun service_ann/3, []}, + {"bob", fun service_bob/3, []}, + {"carl", fun service_carl/3, []}]), + + SockjsState = sockjs_handler:init_state( + <<"/multiplex">>, sockjs_multiplex, MultiplexState, []), + + VhostRoutes = [{[<<"multiplex">>, '...'], sockjs_cowboy_handler, SockjsState}, + {'_', ?MODULE, []}], + Routes = [{'_', VhostRoutes}], % any vhost + + io:format(" [*] Running at http://localhost:~p~n", [Port]), + cowboy:start_listener(http, 100, + cowboy_tcp_transport, [{port, Port}], + cowboy_http_protocol, [{dispatch, Routes}]), + receive + _ -> ok + end. + +%% -------------------------------------------------------------------------- + +init({_Any, http}, Req, []) -> + {ok, Req, []}. + +handle(Req, State) -> + {Path, Req1} = cowboy_http_req:path(Req), + {ok, Req2} = case Path of + [<<"multiplex.js">>] -> + {ok, Data} = file:read_file("./examples/multiplex/multiplex.js"), + cowboy_http_req:reply(200, [{<<"Content-Type">>, "application/javascript"}], + Data, Req1); + [] -> + {ok, Data} = file:read_file("./examples/multiplex/index.html"), + cowboy_http_req:reply(200, [{<<"Content-Type">>, "text/html"}], + Data, Req1); + _ -> + cowboy_http_req:reply(404, [], + <<"404 - Nothing here\n">>, Req1) + end, + {ok, Req2, State}. + +terminate(_Req, _State) -> + ok. + +%% -------------------------------------------------------------------------- + +service_ann(Conn, init, State) -> + Conn:send("Ann says hi!"), + {ok, State}; +service_ann(Conn, {recv, Data}, State) -> + Conn:send(["Ann nods: ", Data]), + {ok, State}; +service_ann(_Conn, closed, State) -> + {ok, State}. + +service_bob(Conn, init, State) -> + Conn:send("Bob doesn't agree."), + {ok, State}; +service_bob(Conn, {recv, Data}, State) -> + Conn:send(["Bob says no to: ", Data]), + {ok, State}; +service_bob(_Conn, closed, State) -> + {ok, State}. + +service_carl(Conn, init, State) -> + Conn:send("Carl says goodbye!"), + Conn:close(), + {ok, State}; +service_carl(_Conn, _, State) -> + {ok, State}. diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/multiplex/index.html b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/multiplex/index.html new file mode 100644 index 0000000..5efe2fc --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/multiplex/index.html @@ -0,0 +1,96 @@ + + + + + + + +

    SockJS Multiplex example

    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + + + diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/multiplex/multiplex.js b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/multiplex/multiplex.js new file mode 100644 index 0000000..f525c1c --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/examples/multiplex/multiplex.js @@ -0,0 +1,80 @@ +// **** + +var DumbEventTarget = function() { + this._listeners = {}; +}; +DumbEventTarget.prototype._ensure = function(type) { + if(!(type in this._listeners)) this._listeners[type] = []; +}; +DumbEventTarget.prototype.addEventListener = function(type, listener) { + this._ensure(type); + this._listeners[type].push(listener); +}; +DumbEventTarget.prototype.emit = function(type) { + this._ensure(type); + var args = Array.prototype.slice.call(arguments, 1); + if(this['on' + type]) this['on' + type].apply(this, args); + for(var i=0; i < this._listeners[type].length; i++) { + this._listeners[type][i].apply(this, args); + } +}; + + +// **** + +var MultiplexedWebSocket = function(ws) { + var that = this; + this.ws = ws; + this.channels = {}; + this.ws.addEventListener('message', function(e) { + var t = e.data.split(','); + var type = t.shift(), name = t.shift(), payload = t.join(); + if(!(name in that.channels)) { + return; + } + var sub = that.channels[name]; + + switch(type) { + case 'uns': + delete that.channels[name]; + sub.emit('close', {}); + break; + case 'msg': + sub.emit('message', {data: payload}); + break + } + }); +}; +MultiplexedWebSocket.prototype.channel = function(raw_name) { + return this.channels[escape(raw_name)] = + new Channel(this.ws, escape(raw_name), this.channels); +}; + + +var Channel = function(ws, name, channels) { + DumbEventTarget.call(this); + var that = this; + this.ws = ws; + this.name = name; + this.channels = channels; + var onopen = function() { + that.ws.send('sub,' + that.name); + that.emit('open'); + }; + if(ws.readyState > 0) { + setTimeout(onopen, 0); + } else { + this.ws.addEventListener('open', onopen); + } +}; +Channel.prototype = new DumbEventTarget() + +Channel.prototype.send = function(data) { + this.ws.send('msg,' + this.name + ',' + data); +}; +Channel.prototype.close = function() { + var that = this; + this.ws.send('uns,' + this.name); + delete this.channels[this.name]; + setTimeout(function(){that.emit('close', {})},0); +}; diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/rebar b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/rebar new file mode 100755 index 0000000000000000000000000000000000000000..0d080407d8b2d66e9603ca3ffc30d96a7a9bb27b GIT binary patch literal 114094 zcmZ6SV{j(W)}>?Hwr#z!ZQFJ_wv&!+cWfse+v(Wo=#6bpe|KhT>Q2@Eaq9d$wQKKM z&sv9^l-b?QmD$wBk=er0gT%tk%+<#E2OK3ODG8&albf}Lz5V|jY#g24f50irfkR+` zfPlb&uxQH)GhAq=l%s%v#1epj!2i3oviRZT{DaBV!o(q5$IcmFBmL?r?XYb>r)5hv zR~SSD9${Q@BPm&15n(mmVlNRTm>^=+ctUkEol#EIHqso*Qehuqf(3W_y(bJb{n;$2 zAAvz^{3f%uw${SVw%XETosLd#=_`|1?o$2I@p4K1()0IbygXZ19p9PTAAxJPtCDT8 zuKM%zUyt|T6JG|Onii7DYtYFQmv;)8nx(eh)t$QA4_U3Mb^P-jIycL;x{dsM&2)>F zomZ_?c4?bEmbIFd-f|-AZ-T;=RV)N`K=vV8*4>9Yw30FltWD zECRo7O*`k<^q4Y*rw1oZaoI*frkuZU$x1qXdU!x*=k*~L3kt>|cT z-Wjs*v|DwWXR_**G;PzT{t_~;oTW#u%?h}A$mo+FOHCE#dI(?Xrc`Sx%%Q3}wzbfx zJBc)~1E(`}7G1+BGn|-pQz+WL5Ol>LtmLt*P%L__0rUC{dcZIpJECfJStLdgReEO{6NzjtVsx0ExC#@OFYEz+}gHqYqyEx3L&W!*iB!#A* zwgNwGR{4wwHgoB*rQ*fU_Y)~F`Q_3!XR@6bw1QeONBD4wN>v+rcX~BF?Vk}4bJ0Ru z;6g{obvjLJA!ZPBr8#~G6z}07OnD4Y;Y|!cHC-~J_B20kg>^PL9!)w|v)H!(P;1=B zuV1osP510prAy5oljEIYo1%9B&VR_8w1o9Kl=jmI!=(b;9M`p&@#B8Sm}!VfP|c_= zYq3qCub9i3qz_K%Si0>nh~9@`+t40sO{>E-(9L=`55?cjZ1p8wF=9JkV#5WR`|UE(ts z2KkJL6GPTk8H95VGz}6CQg`Fxbn(Yc1Hz2uYk1}gOPshl<t1V4l?AdSW z4qc|G+ebVq(YZD%dV1_pZ<GoL*@ciIdb42r4K8@ah^hqm~am4_aj*08EK|H*3 z$577(hgi>+7_|?wKtP|iCeztoM%eq1d=7#egZ;JxKZbHic$(ItGkZ#tDnph|Ra;~g z;SGdo@Ib$Gp|G9678kVAW2JLgWhMIC^oNwOI7DMm&5$G> z)N^j#(EgqD)Fh==xJ7B`f-1lX!O+T|LiH8GwB710<(Q8&bn(a<4iSFydaJIUxN~<~mcMzaYjTae z9Jw34CDw@l`OPpuShD0KnfyoMtbO>^vSsgIiDD{fOQ93#=Nq00+sdgI3GPn#qA!KQ zB3d|aG$i~_In!@6Xlb@R-CV{vtc9*LZ;yOgLGmLS@buNdhk9T`Wq9s^{jQ4q&C8r(nWtt9-GCB=z;as`~@!!(T`6=OLIgxa;P9W08b_jEu&`-ytHJQ&t9K4KbP9skJ z14Z+6tNe5GFSoL$qp;?WfF7agK63h1<}^y-mDAB*epb;yCnK_*hK_V*NByY=FLDN> zkYfdx0CI+1FORAA=Pm3cUQ9w-j?obO)AEayh8aAALCLPzb-UG>-nm9ND z^_H@9Bt1hV02^?W-rVuA-Q4MSUf|}9!Q}Zjc4pKYsm!g-=Bzn)KOU65I?z%~mMFSpM6>Z!3_u;700&0*rq0XLNp#tIwp8uBd%T9>MZS;w2zG+YR?g>g8G6c7V{Id9d$~>e$x&Gx-Nny3Zov zHtL>d(-N%b538{6pweDvDWQfLKZz5g{%Ol46QlfWl*bo1E|sQ*`g1{OCQ$JJ}tpM3C;drI9xu7r6u&YUk&QEq0EVQkbHFk zA%i>g^g<#NJv2OC*R9oEe+`d6wrMxO^1ybX-Cg*vulJCRxmTQ-{e|sL2QlYhFF}uQ zN!EM@o|KUThb_BOg=$)HUL2zB47YBFC4|ZN-YY52EJcg|hzSMe_SrVZ) zJ|9?`ZWk7EV4<%6E>lco7t1JdWFttrC%r{@XpJa?p?uSFNcx8eQPclPmb4H2gZuGn zU5)Y!wO;$-Hzq*sG`EBU#~mY5rm=WIMmW0&=xwd|vhV?^^Gd%ETl*2dVvolxGEb7n z9tm^y7~EKHh#ONn`0-?nNHaNpuBkdc!rF@%Uf2{JDWjH5Yki z34px3(~Rw#&Rvd1qoE0=pm!oK2_3%$)F%JrP{UMGA{sMCI}K=$4E$U6&a>dZrTBbj z9>dqOyqf3t&9B5=^PD@qMg%t|#?zjc8)&4tqc)F3h_bDViYGDK(^sLO=o9u zq*g5a!9Ix^MQV{(UKq^^JPvMjlhi z8!+bv!f^yT4#M0IZ4K4}^1g4+B4W4ke0F!MHF!;yrZ)ag}d2?`iHR$eKkdyt* zm~|mNqF2j6K`qlKTz?u<+{I6*?miK&`Ji$<)9xyf<9Mk2yxqZ^!<4gbw!2k8Pf9hUVX# z3ci0S_ZWQ+Z3ywdFU7#CJrnsd>dXpodE9O~)H($|PDn^4w0*By2pOLHzU+JwtH;Y5 z3cPgfA`?bmuw-urK9(m`fAu|oI-I=)3i=dZ&Mz6PmJ0z|8?SqMzN;Wccs;vs=g-#z zk-rz?wd!B?uAcn^y`H&;`ceX?^1a>z$&>=KP6cmnldz$8UJnF}BEDNFp91bp@pcSe$>IjQ3={!mAM*m6MuE!P!<*H%r}H^BbBcppN;=q2kyk1k(Nn`YNn zBGOD6<&H*O&DlngKJ90^#?bv#DCGjnx_aW;j!zbpt4g5s&60h7$S;K%;G`O6QvGyhscKzsk!nuEx@P8k)q_l=pc9G{J1XY87z7P( zbOa+uOP>j^nZ@lv6#Jq38(!l}pMPW-yr@x@vJndEshJNrLLDYaXpL9bfG)wLC^s|W+RLHGviGt8-6|8& zDJpV@e1W^e%frXkP0!0%yJ7yriH*x0zhrw`($n*qEXCEIjGfPKX70+yW<-1*F3Zd; zr?_#(qc{7s3ok{)D{=a#~O%-}P5&{T_Iywjl;y<3@ z;O_Rr#LU{_KSU!=8_^l#2zRIFnyKBhlhU^x8x2MXp9?lI(LAN+Lm%wrh-m^fKr{^f zve1ypGv`p+)${5S7K`CW#bgv2J1(jOJ>#G{U#sx1#YQ#pB;B#BbkM9x^%^8~NK{Nh!W9Sq2}LU^5A6U%zqzi6%Vzu=Z%BJ}G_34e-x_v1`te3r{9?vA5^y(Nd zgo{3gk{psYO4bYkfcd}}6(fE$24x$Z7xqWJ7z&Hi=(xiXi^tH0zz&D4jMYk=?0vyW zjI(APw^SW-a)dcJK>~OPLd;$eg|18T`5qrDaYGk4FsKLRi+5&S*WL59Z;QRvkhq;U zuaeWhM7KUBrIr&`!-1{yB8AuI{n3ILc(N@{I|rQ|PYx|L$>nzRTX*T z@k3m$LSb#%j6&2*n0}7B*XR)`nsHt$TN?>XTAM=CQiOFMUlZZ@FNFYD!lhcG#s-o~ zVI3vG&}EtRD6C3ZXx0=dtYQP*P&Ym_3r8okPB^&XN}O&GRrbj535+Na<`B-l)zPWD zD=d-=H?{F&;0s;4ln3`Jxw~mrY)oWuv^LNMcY{oYrFnuU@vwGKCE3;(ebRLkm7E}4 zK6pD0=PFE`-SGsV29uQ`3QXy_!03g9dH0fixDC-CQAEAGlF4BKmY9S7(h2wMwnsJB z$<47~b;8bBH}$_WY}1BnWq2{SEmZ=N72dSJs^y3zfl0{Tlvb!1{oDlqIpdHs79VVi zfMFKm6w7yne=zI4wr{pJagau~f)bzEcrsdI;gtYgU^{jkqIo*mgLU$Ib9&bf6(=5A z?Qmz1(gnlTA{Unub+SVqf;4?9_o^m?y2SbqO|fD^God<8>p@|>dJ!}qkJ$53qtx)F z@$wY|yPQ4jKx`zA$$YZ}8e5*AFyD;BKA(Mc(wYgnc!xcTdptK$|95S9l;PatTXR2# zh*(k&n3OloF4%;9an}9_O9idD^Y#{Ob`~6Y4&puiZPViLzo$i~U8i>aqVveM_XPYR z0uN23Pxy2c&;kj@2W6-wI0%tKfR0C-Hs5nG&Gog)?Mgn{Iv|V4G0vmW%L!7BtWDFu zBFR|(y-`}-SQinZCxHe{mE?X*O%^vsjsACZB%If@M+Nux5O$0Vd}(pimz~gu9s4i; z&{R5Du3WQoI>>A(#H>t^n{uR^dgQ%)P;(-@l}Kc>alZ}-4}7OH)(@T`!a2ZiXavat zWA&RjRkL1|tISBuC!CWw%XxriM1r?-)#rQRW=tq|h@&&XZ>R@h5~w_)%ra>dg2Ox! z5KBl&ku62bQwF!T^BY8#5k4m0<{9EheH7l%gYBH;#a%->K&?(xB$O{YDo#Wln5{`Lz)cuOAC z$@CG&Q@EMb&}aT2PYHGab}Q++(M{icrx(#-Tz!%={Aby!%YSb+RK z1+yOPAFTcC*uIBY`LI<-i10C#f9ciOT z)ARC}sk(^I-biqYwoTmZAut)LxJ=DnP}Bpxc(JxcIh+dijAYQbt|{zM42&#}%=Dlk zrTP$Z&Mo~VOvzF`C|wdfRSal)$fUO4MxN~T1%AI%#N(s+k|7XEd!y5mTomuJAjfI5 z41_(TBjBD2#io+SwFmiD4dut5iu9S*?@c*Zcb z%NKtA%xdE7_jYTLQT9{g;+AB^j={8LS&(R5iP|03mZV<05b=i2!)wFGur&#m8&MD& zf9)*6K@3zJEdeIvxAirafX~{1w&sdeu7bx+tftF0xVBKs527}=Qt+L>VDh+R*YaQ& z_oFe$=b>wt=uk)894DD96&*z))ZMJ^QT+UdG%4o&Lu~^uGNfuYmZ-nB;4BShF36;_ zumvv2=0WQn8-#{_%xsmQc()-rqW^)BhkbBjePIZCN4p?XDVv{GB@3pR8ed>qs5^}+ z*0i-v>{j-Squ5hK1_yw{aHQf!K}~`;;qil;v+oSRIzh96@&;)SMckwyHy21?~W=B1=q#SNqo{36KV z6H|P7%gv07=!UIWZ=mLF_lu7Qd}_quC`s8~+) z1ocKF0$;}SUdHd9sIY1xWoGe8K@Du$vzt1#%Xgu6U0le7dd=*|E?pJKGQ3SV3e4yj zC3@H4e_Z;5C#C3+A}i;oP~eSp&A9cc!q6C?E%;r2H6M}l)VHs{^msH8hYtm}^7{?0 z&7k*VIjPz%#%nis;a-bA(t*0`a*WtrsM)oE`5HKRu=~`*`my<3JZ8xER&wyoxB2zh z-FJ~AMBC#w!FVk||54U?7dK3A*G_EX<#$u+cdO8S@aIa3jKSyi;1k=^adYkFX{)Gm z#`jM=Z(vX0tM!*!={5P|rA9b>e)XsGp9EhxLBAW5u|(Z=AnYry!#inP?@j!w(Z~L* zzl`4I0d@7y{G2=Ufv%XN%~FkPi43W1rrtOwz3;JHJ}-9EdA zm25=4duD>}@4bkBz>C}avfQR7W8Ipr-|w=|>G2ke^G%QG-cR6j!FV4VmY+k< zQQOS-#}Rn_4rYM$n-z4tfamM^4rfkp$D?}1=lICoTY1+L@~<7j8xKFj`{3r6IJ*G8 z>reWV#)NvpuZZ7I%boov=;>>}+OMknuC@AvdR)#&pq~xVp6|4{JOtIBPt4*JD8V-w z8K3Qa9X1X8`}F#b29wL5Ic9t8?v;Qw83mtz!_Tmh0=_;5z#IRp$UB^_FE`xJb+U(M z%<|uym6j&7q)+p>kF3Am?jlWZI-9?j8|)KSC-ilnk4V)S!hCGH086F@lxAZ-1J}I0 z#@06q1iQPhf1GG!?P8uAdY*(XN3RMo@^GBmr|M=$q1aO)c6%Y_z69YRbff5N;(zdm z!d%e>(INVxT073NvjC2HnK=(3G8PecmdkADwy+(j07*7;7^QRDl{~K^W4vcMo0=0P z`N!zb4xiz^qu%Xyh^*uA<*fr@eBzxU(WpgI8mhWvhW^#XqnPe-ee^rXvqtB))5BZY z(#hA@v}EHksx@Cll&P`_v0=-;4_?tNSaBl!%?sEu=&*$N#Wjsy<~)QN#Y@9h`63X% zty7!c9Zz*Ph~PXJ&vA<3&Psp#RQ4*uEicQsySwXt)}xg(Kv1@7`Mf6_d-V%3Egzw?mS0iw3060h+9(L5E zhx&sL+#Vuy3)~+&AqJ8l_$|tB;iQ5;t5$P1&$)Fut}RmZBWh-^``t6Hb6>as{bA7{ z-|IMcx+Ph1keGr68LMBug8r^toHFio9a~>`)sGh8KkP_Dz?sRj#6P$X$Wd$wcq&H= zT60bk9yJSlp>&Xmy>%OabRw1uYTiKlQlMLUG>M2(gLN6>KCXlWH7EIn7=%TE6nW+ZJ8^LNrvp0gSUyE zBYJ}3^^O?0V89md!eR?(V=?sc_}kiP8|Ckuk1Q1AaE$M0LTxY&E#7??b15&iu>1|F zIiSJ0aL_HhZY8j_hlaRB3dL(u{L?=?3HpKI2_A5^GXap$^K7XtQPg!XOJrDeotG((Osn)JKxb=CkDngfUjILt>e+5d3xWy);*0|V@~`3kPq+QYb=QX1$2i(3FnMBQ zh((wND-NNLk*^3E3~<6Q~z^kMjsq7d?9g1Ny9h-UZLz-+zw$ca90$eD0PQ5b)0! z>gjUSv2MIskJMUU zA&{|2!V0-V?%%hW0NBp2-Ei#*ZZw-b?cO2$x z#0_m-{#>)QFfby9>k$vBFjablx`Wlc%*R()-kdm%t^=KPU2~VnFR7+M=wN%K9PRZ6 zdw0LVREkV8PFgp3yHcoMf~37(d2J0_;F$tR$R3(}c#)OJoQErTbE0MwrmPNX(_wgW;|(Re!wb z8*;K8d8~6V&C}gnif~hlOVb}QAp8>lYXh?}8$*V`Pc(auf~mN3DWxqKD2$8I>0x+q z452**dwVXrHlqbl&FL`AHW=0K-avX4wiL#lQ=Jps7ANzSJ}qJ-Qp%Ec5UN_X9AC zixPo%lbHOZVJrNzen zTl>vbdT1|5%A&tJgBsB&Imb?*qTQ%a)e^B zgDJU84x+el1A$1{qZBE` z8{FR>He$VR?dK^Xq$0`k77|bvJ5kBqud>*s0|rbszCx*< zpQ@ovWfQQ=LQKXGb>J%*WkRMYmzm^o_5_hys2M@tOPXIGn<5(?YX}1~jEy#pA`3Cy z*HPe*cvhL`ky+%ckqvIpLNacKBg3n-i>buPjfEm2H*;Yti!2kti;Sg%QI#59nW!8rYJV&&FqLj_SOXF?0*oqZ-k_~EL>5Ono{05BGAGwB z){yY~LP{TboWEGKHdSa%X;DqtVRrzuJ54C>MG4cEmKy!LRrxrw{>Y>ebvDKLklOh@ z^Co_!GbGS3A}q}z0OV8HvNq0-x*vxXE=$(qk0&CLX?Qa*Ewk#S?YY6B-ni+D4fY&& zB$zC=Al~v6wPv^z4g$evu*qh{2o?DVssg2tDxQDM@$5)#iZf-$CICTkP+G4K%22x;*$9K#i85sEL)4@fhch3X%>ZS(Y+qqLC*UmMYI&LD^1yFo2Kk(ML`Tt zCoBy=2Y)%Of`%mrE0X7Wu%VBbi#`?cHIdn%Z*bG5>SM^(VaR$ATZ~TFpOVNAYZJ_( z7#~t7^YYk8D#T7pm>hymi)W5Qry!^(r8FQzlH@=%xg@%(leGUPq+L!SipMnA?$_p^ zOus>j36E{HrqYX|8xw$$GN%3Osqqv_(R;r*C0jWO zj178~ak}vA*L9_BLy?*C*g}#GM~ps&FUgM^zZ2%nLq4%Xea0)HF< zoZ5wJG8`+m1Wtxq#l1rG>=#_+WeL^3Iiqd!hxyGPSW+33sw~#z0_!d@F3pPB7WyW zb>~usVdX~5Nl2YDp9DY9e|MUkz&@=G4+?Qfg=f(iekoR;^*#Rx7-&Ll>&4_l!4QK| z{DPI~FV`6tLpd9l&us~r13hborvaeag#2EyQ`r{PR>nm*+jyj!4Bm1p#Blvvgh<_~ zN1KZnxq*fEC=3lU@VB@jhnD_sKA0;CT93bDsVsz^fO^wp4N8m};ofBda>xV6qn`zd z8enn);j|5z8X5G5NWHSCWLSE+QrPpwSdEB%Ia=P^qzE4CP5nX8R9DzqoLH5UNT_>= zE|?RPHdtMl2L%=b2?GiP6oYoE zMIl#b$Jol;p)V&YaGSXCJ4+RwZ}47$tQoTUG-19=iDhUmo+Mxc?Ys@s^z$ zy*r`YZ#sUoTE~4~Rv0j@Y7qrfA=hEeVpp;XLl*R1lU?{ecS)E^WJF|_2Bw+ z_?Iq8w(9C51FCiz`FX$oEISTxeb5z9YMIGvEps24U8VNR+BUiq4!g=Yd^wQIsYCKN z{qp_;@m>{L@7MY4Yw6B#Nq?7H@Ho*sYCGk_1}O6N(x@Tdb}{E^yP3@1FF(5qeCsWh z@+?|Lpjx%0eq#6zxbuI49b579v>Ar~ z=HHk9Tq}69y)3O>bu!1adjmfedXx`@DcC3&(BJa;WxtU?uh^a4MP|D$wiWJZMLLde^Ic(#`i{jZ;-gX($m40 z`g`pZot&9UwaPnYcLdG0wH`@BKVifD6UZ8L_33Ifb?|diOij&}d24n5V-+0}5Rq$S zWZ9wZ;jrc_G+ghUP;h{&fodmxV?Xmf_W4H%U+9i6Ls!E|yDLCnt*3XbK%SO zF7V3eNnw`?3;g!9Dwp zqyHEIblyOyU8A(udvSf6#7!7hAlx(re4a%d^x__lFxN4j@e^U8e(K*1p;jVFX-QmY zmR&W#USPk@drk3{ckUjuFq~e}(sjI=d5z(^?*7ZCS^1_?3p)3FD_ESaDYX)!cftLm zQ6>v_UIzxZ{+n)VTbOd(On!qtmq7l{Rb>AL&egAt^IyIU%k8@_OYNuS1=LtQvoS3L8~a|qqeFUp!#LWnR$z$*F@~egYtFN-l*?Fn1Zr@CFVLR7pwsoG77J+F zbbA35fXv(Jz|2@Je_XC#>y%;lSHJfdF{9?z9bBLiK>NT2ohhm*q7YDzt(rm$ALBjKsz$xCwj*UZH9=aS5@Ty`Dt1R$m9yn7%fh-U9A6 zCqJjhHhZ3#d@jv9z6#$49=?)y+w_S&|J)W0s&#o11@zFneoEO9uVH+1Kh}LHy&2i& z^&E#rd2CaXaW+d~BUpfD)IQt%urNTZ zn17M)-hnBHR8~hT*Z0!7cbw`bYQEnaI(Cg6_;vn7;^MQo(&X(c=*Q_h^tzQy?)ezt zdX{H~3!lYT(4*qm)7W>usF>yXU8jV6H52;YD0a1cQnzQ`E7L25l5o`luprtFXJiXN zocnR{l())YmJZH2{&rHANij7~$gcvN7civXh16k7c!*dnQQ& zmo0*WK(1RxXmq!5f&FvH1q=OhX7mx+c6@SL^@9G-QhHeCSFe>NCB>sLre6{ZS|tAN zCsK;2uK(wlbP6qmier-bPEmnVM(OWNDGr)YJr(Iu874vy%OGHj$?--bG({;LvtK z;N}>4nDRmHNWB%0>y2wkp$4H(v{M<9WAN$86uan*N-!%Sjr6m((D)d@>R+~ayvIJ^ z(#x)fagnuvyx!i4VCXDHnYum1?)wIkUE1wDo66T7kKzGmHEl<*@wVP`h(&4eCjMdPknU8wdK%;1h zS~F=ZvWcY%$l5|?)SPEu)%h^mf};#sAf0VUCaAR+16ys{7HO}0xvZ9*TNs@x9Zd00 zMzKIi&Ryzxl|SRmI7~ZW;~)ND!W1!$kgkcjxIlPgghwT)gv1)N|Gce$*W3>xtgjp; z12^28Jmvv60eku+N zB1VrI{+4Q7Lyls$1r=sMwn6gNryYkXBEA`x;MZnG%dnG}mnu>Q!IliJ7iUgD_kATX z#qTEAJb6S%h{S$|%oY5xCkR3Js7k|9>T%J8yQ(-p81Zx4mcB~1HyNZo6fpS+R|El| z2R$HVTcZC#v9=CNw}T2dvd&9Ngl`kkqU`wiM&g@cbO<{-M#_XFgX1)+gH9&Iw66;k z)g_RCg}`;hC&3Yt{pGba{EdI0B^(0#SNC`?<8&PPy|V{`eTa%D1a^1RtRb&6Aplzga1@&r{^we%5Bom_Sg7;e0HKArhjV`b|mXd>)ci-%ckv|TY?-v zntPR49cHezo7~Kw^Ouso5>y=Q&lh^$6+5nc8;&g5oSl?hAM3Hx{o1^z1hkfp|6sKL z;dROE^SCV;l>*ciK%>{q%L(0H)*3l=xZJsZOs_STHEMBl(?QP)eH`w7VJj}~=?u}w zm&`COOdg@{LIJSN{Zn?^clr5&+q-rO4EMom%|^T)N4FVDejPSbwJ!MrKm35FY1H6{ zo8@P4DMy|{oi3O4daKmf7jG7O>=Un!VwVPj5O6I`#!Lod z_xUwn8+6sJn-A$Wdg z2V#97G`uAhTw*kk!t}On)<=f~&U$o$)tDNyh@$-IQ=o0qqY?yILWy#7A z)lB>q_#r|@sd*0-E<`VW(Nf z8W&K($BGJ91Lp_Q(xjRON5KIn3Nx2kOg6_+W@CB)PaNcyZ-k}RLUX+B_NQQ%_c_)VyhV&Q*szrB;)jvs#(^ z0Ub=tNV4WcI%j|cvt~CBJ*L~B+K4J?0ViCy#S+J}X`8vN;|$A>lFE3Mg$$ZGtPYz) zNU>E$h&yXZT0NcOMe;?)cqX$`YpTPhGQufLB>0LZZ~x`InbU}Wd0}-rV6&!5edbIY zI=9ecOFxY%JrE}w$#wg|jl8LJTV1u~xq+3IY0YU7 z`sG@Xeg`=Li`v{p1W-a@>${ht8yhJZk!n1=VbFxqpBziF8@PUBHuXY6Tq$1VcYj{+gv?5FSF-LIwG^GZ0<5+~56UaCePrN5I^2fd@ zVVOdZG5uhHu#_Cw+Ym%KR7FtOgC$KJOFG1|5F62+Sk~NITEQ7-poQxPhk`OKMi++Z zPk#EvhvgQGM9M3#3p5KhP@$~0TZaKgBXQL>zt-PQm#DkVjd&VP%I4Kv5%j;xG)|l3 zmk`f1FqTlGyQ27r+{eC^+JSKV=39@Jqe~Auu&Pg!RlEMVL1m4~UuDdyBE8Zb(8f!%9SXyR{C|eG(M|CMq(|BmHD1dZ*sek;C{>f2l(p?s z{0MEwp|G_}yh1Bw=?;r&iHcT1eBxY0H&G{HL17xesZ`CvkYeXRQHKi|+T27Nkf^}5 zBMHS+UlY&Hiyb8hK#rd-jR_MVx~?G3`;GHZYVfH5x))4Kp^wH5LISx~TETlTMmEZG7}ZD*sdis7LXtJxu%#tt_LKXsl2hC*`@wvc z6Vl%ho9=F+7v(9!cPo<^jYiqY!(WQatE3TJ!-9%1BO_nRWEgp%2Q)ZRY38X66eSD= zv6GhC7e+S%MCk8UnXW3F-tyE1T#Yrag<4-hmBTV5nZQekw+kmBfS!^+i^8q8{Zc}B z!?Sc0I#DF#l^Typp^j!DU{0={p`n2X#sd5w;89#B=CL4d@qZ5949E=6$+9PgE>b{^ zJjK9_1T5q4e^u|JP+wTVRThZtFn!Ny5=S(;ldtgwDdUkA!=PTO*Z)ky->0D#Ckn=C z?NfP32>yoI3u9d_7Y@j2NF)1Uhzgd=f>cFQfmG3(S%T-L?xGwQYAcF3=S>B&Mj=}` zqPFIRGAWvfjugUzGde?AAUaYFw;HTJf(}bQcSQl}HOv&qx9|G{jA^lRj-xN z<=gbc;O3v69saMWo#Vj!{T}S=p04w;KCkTFfR|n7t=Eq~JDI+HudOTB2GM>`%sU(G z%k)x@{B^gBvxYf8&I-Xs2m|nF&Xyp2x%O|&6h9jc;pV=ip1_{`NKAB~;*A(dH=3%2 zZz?U7=wi40o~Hr>F1u9E_DJ*ybo+?)bh5Il8A9UQFTGoy&otgABEgh`qn^6h%~mFl zYHlSsZcaGi^Mk9yR$WKnWIOihZn5ZJRci||$DhERvqI{WNqP@YJ4+(j%YpHwnlk4V zXt)}Ze-L9~UXr!+q*4=8PHmaigP}7rC=G5wJ3~XrA_7emoIu zJ9O!-7D`BPubL}qj~1UgOy}+B9FN&atC|yY<4uMuk|Zt;?hr(SoL%-iPXEl6MUjRbyf8)MZ(42VZ8k;Ms=lg-WbwgmqP&9-;1t@_ z9E?exQf;u#36p@D^#{Ocn2}_nRQ*J;2DiD+>vKd<*Y>$Pe&0Fnxqk1x_C408db)^H zQ4dlmmGX?^vy3jzY^VLXao!%go|`tKLAU&~$8U!QUvrj~%-n!oSCUoF9ZsUF)Ef9; zzr*kc6R3BLNPT|D@KJThP;z1Z7}C06pgTGcqSHD*KfVsNmIvurr_3?gnLI$m0)K=# z4&H2*JtEg4>OG5qI;53qI7A1Wyq}i-@vAgFIS0w8aaxwE&-f5qXRHb@KM6IUPI;xd zBkhtEn->8}s`2x&qQiP2ZvD*9jZt|%*Gf2>RlU&|(01Nzy=BeaDYwGKSgr$oUIZC+ zR<8-H&2>D%skC9zYC!~5#_Dwb>g085H5XRkPl_UL9_O>fLY|o!)X_K2Dhm}3(e2Wh z75)1m%BaAN&~f-TqMJ{>pH=h`P`RygX+N{vLqto-iJ9B(P_`lS=&lrQ zC+ZSXsHEn75?EAosl5Z{YSrTwSrcN`fD`XogukxF(&JX9G07|Ujy3sLa|Qs}d@;q` zFM#vAK0RkAb+HI*=R>r$+ZvwJqRsqF0|t%?48V*{m$Qfrob(nuzO)@_K*B1V+p(2^ z)|J~?inDj9)}313?`Y?-V>m4AVh}v$(KKm06XsM7WYMY-&5CZ#3aV`=*p=A<=|WBk2oGsUVtVsHi$K%pR9+#h^1rw`ryxFeI=}n<1?>_W_!i`3VR{FJ7QuG5W zkPn4bvBs3to*v|>YGjzG*}af!gd{YvUno*_0Z$I4ln@cLl%N$GHb^XLsLiVhLo@gz zR9@ExD+psM#Q!yCCF&S6Q5qx5)f3joc(8B3_dFdV|1*KDNTGrX^asx#t=F zK3)0aBBS(D5fq5N&U`R$PHD=n-c+Z|GD#W^1ENh9|o`*T@DW5%IuOQBz;7GH#8n3mLhN06{S>YPn zz;(4;uao1U$thZmbe4a}OqSk!`bTPG(@(NQ1MWFLMnj~T;$l^ZmK$k1sgDpV#xw$>c8(A) zlvV|9suCOwVHNp#xG=bWaw(6Dcp#5n4pNn-Np%dqnnIsgraHAH==6I5`I5ZIB7VV% z&WNlfJ4_9Xkt#?Dj=zoyf(WQnMCR!alQwk=S`fn_mcf$i)C*dflue8SQM2p31Gb3k zZ$)(hcql6KYSAj@5JDj}65=R@#oZ!K5WeTAm7`>DfU4o&5-~!ct8u5xy+$<>Z z{ttiMmzmLMqsADB+n4G-p!i5SKz(qzB$R}$Y<$>(yv4W;K=Q{>!72b_UVw z+B*)5TS&}|m&r)xHdBmV+x@38`fD9pX)Y%c!(o<~r>5*QeEtthpm3Ifgl$y2IsVER z$CFQMEr&G(mny+eZUh$_`YpfPlwc||o9yA2#TZY}2CXI^E^IGgp$(ZitynZ8mq=_S zCn5`cA3713{pi~KcImuL$Z;`+Q^U^!_HnVQVO#&w^x0)MKEy246#Y%952SulZab7L zSO3Ng6a&fWFSQ zSxy`1)0&V<8PZ^ZVZLIpRe7)+LgL1x;#}KF&$c;ROF;|+Vlbb)7mj7-Z#~%* z%3EnZAUQw*WN1O;%|*YiL$+=3ewNcrEPD8TKy$!5WNJTZqGz=GRzYn%d<<)XfDQij za2wE338WriBV}M6>DREM5bqU)OCr+(NI2xEEGmfM*9$B!HdZ^2a35r`BK=z4{=Uz` zB2r4RD7$xoeGoE!us{1Sh*2LgY`bG2*e=Y6b%g?a!R+1`H<9c7=Z)ERo{+<`@OSpc z1sXY|&4Syw_g(3B6f+%r_tOD^Ja(v~Lj!zN(pSH_$U&}-TJJ^oGE1*@{f*LW$DYrv z(a=JI?+%{o*q(crw{Gq74&08daG$s9YqFfr)6LcD7ax}auAUx_=MwwRH!hE9Y+iJp zp0CkZo<6RCjoeGmi{d#)U6q;}$Zp5`e#6$TZVZl$L*68K`_@+9JQ}GlZ%N(etD2uq zJJ++J>KEzi_qmuKic+%Nn4zmNzLDk4=L<5vp8>9^*kRrhHVL1Z^n+aCnaj7+km$=R zVB0FsL%Nw_A3`m5hvz`m7+c0xzP6vU`H5vkuAP6^(PN^LAJdnfuP1ohACaMntrau& z)hpg!PivvRAGS9;DLc&Hf`{*4H++pRYo0PY_g|vd?XGVuY+w9WK5vKRKigkJ@EzOh zpGxJ6&+T8eJheSPubw|`SC}m9``&k48#!0)&)qlaxhOln)o)XL8Svldvt!T$Z{Tp* z+jjpB{BwN-WT&!WQh~iqP$ZHFh1CX z>7QRA!rLZljS*OwG1 zOt->7YSo>0hwS4GqSpj5S)BCa?@`C!3{bb9%ZgKHK*6m3$=sEHC31~4E+lr$M=sv` zuq@X;k1SeImkb-!bB1@ASn0YrybR_DR~UEijt*a1`EoPZ^-i7T>955t5-ug3ZfvIV z<6NLf3aI5v!)PuTEw(^zFCNP7CZ{lx2pYp31uWqL7w4O{T8`RgtG zFS7rJkQjRm!nq=k3Nz2ZV|JFjLnci}A2)}?A)^<|Wr4%IjT*^9Bca{g<64j2Ww1=u zqF%{gTod{>CMoRNGDS9z@9+P!evU{j8YeouF&DqY@6gE`1 ziSTa{HHs}%ZT$gh2mgjNvVbK7qTF*XhSFE)vn8m@Jg&uqp$~-ick@=WQ~}MD(gwJh z^i(uc@hn$;c1Ol-1}q+#wW&ZjS`=N_fV$?Iw^<*<_AgK6IGjQr35FcE{MaO5g_(>+ z3l@tk8CEw~Ry6}~eNuXHffYdrOI*zYS_FttOw9oG)A1vUp$b45C7{=dX->bI_+#b8 zi`#^l)XWj3XNQ_fA(_JgO3RzZU~_s^=fzNCa0h>l5Zx8f)kP(a9;gdG z;-v4pqOVXilXsldmQBcSe~S(HlI0rj5OZQWlv*lp8o^g;M_h#c36hGB7eZ}-!W(gRp(S_v4u9K5QibFoZomkx zV)BacmnA_-vWu8OJ25#q`Ii{dHxnTN1aM{p`FTmumb^rU%3&TY_=1#CIcX5T5Z!Bx z`K4iRQC7n3HB>=`gI&D;LuC)WPZ*IN5MwTc98pGBuS$_q0=eyB#`ylM(g|jNNJW{@!%9ABJ1w^{9}IEi979iWmGWAB>1+So2*zQ$P#v^AVfB4Kr#g7cWa9gJ+B{t-f7wyn zaPTR>BiNSz-}{l1Apn9ZB%V@vwm69=F|1HctJ1LeX-Gw?CMQ1AKz(x_V%K17Jt7=oayOsHQ9Ssjof=)Xl(U;lh?)m`R;Zj?Qnz_WU?vur2BVr zmZLAZ(s&i_%e*6I2G}0D+IA-~1dlbd zq;>Y#c>thhi!(uO&c#jWmtNA6tw7Y27Q7rBGc?{ZP(>M%Wt>0wjl94F9(Aw~dhgt1 zFEkTdi1D8y8=i-CxgpW0xyeGUYHYsV^*bxjfvw~!V|$g!;L4jByVNqwsMOR zEAGE#wB&}!dIGM@EvhnkmYq?q1q^k$Q;1z`fyb*GKMB1XmNI_2pI$Y z#^NviG333h|N~XZc@K{7zw&ES-aL8qRfJG$b7-ufkdBek>5?*X3~spky*X= z)X+>@ld0pgK>P1ILEWmxwb@zO69@3EGUcLul!fkn|L(e_;!Isxl~VIbp&JHpQ`-{j zSc62DrcI~YB=z5)m@PAFOLy9Jm*$mbi|BJxiX%F%V37eQkQwugZ-@Nz41x1<3)H(9 z^h&Gr;ylgz-nzvrRP6J}KjDw^bc>UEV-Ba9MN0}It4-A&s|=RU#VTbP+B0lW+aWu_ zPTWV45khe!DC^>r)K%6HDqU6|)Le2c*&2NVT>m1a8_l7dA0o(-pjDKO?h|&ZSUM|x zYzOM;pd(FtJV@nAu`2x&)LD2OS4@RBWQLr8YPOt~rFzcTTNYr0Sp|fNu1i2&s_xjc zHV!A<+STz^Mm$$5{-J$@@TLQZ=b0==?AEH01F~Cp&E=2S6xy}J;a;Sea;2SZY_~OO zGjh=0(wt(ZTmD!B(*Qr+Hqdbky0H`?7YK_<5RF3#K&cA|A%`I$J8|nt35y{GI|r|H zunQUdK}>Rv$y19(I5bjEhD2fvkR?)=Ay|=o}mw3K29w z0OlzVb%grijDJTo-iERdDw-FPjBf(tl-axCFY?F!5ePf%Wzx%ziBAGlS9}e)N1Ng) zK(MP6ZZYO5>s}O$L5=nzC&DK|R*WOxnSTr2wH7-P<^W-*s{3*Fu&8Sf1aBH(qzUY=itcI7VXz}U82P2g1=Iq}_Jv>K=9AItl67B{N;8(ugc-d zWe$~JaNcYLd&JY-URLvOu{zMmI~v_J2}5x()BRsEB%MvuT_2Du2uzqcnCHJ>g>u;z zyZRtSP0j-y5W9l1(deS@w}?(KBXfojBOhTA&Dgwyk?TPFNub1*oH106P-QJe4A_Zq z`|_jCQk+5X_XgCQf$)Py+PSg$s|giG#e39Q2obZLFi`bS;yCoTZc$>P$#iBSg$)&^ z%o^4*Yoyp6cx%vhQLgMd*dJQ8R4|dE*w{v3P=SYtNBP-EXmL3j{KPn+3PnWrUhFi{ zqGuFFSWti`D|wNib~G282@wr~tn=quhMK8?=irwng0Q-->2vvP8~gN9;K&MSG_Q zh*^21%`nxF>~ixMo6V@3~Lg2pUXU4Sf0&lWd$0P^_YhODpm^*m_aF|!O=+- zP-Y$o^NHvMH4j0G*miWRD;aU(=&SUj^6FrOsWD#dJ(TB#IH5+ULi#)q3#Ry+CSi00 z2~Z2>GNUw*C6rd(qMJlfoYT&n+KhIE&*`CMO#0T4ojxi4{DOe^aqX#c8YO<-s8{|x ze!=HK!~qnzUt)h;^Uejn8||oq^`;N z=!San9T@V1a~ce#XC2ee5y&#U!QB|LF5s|+!(|#K&1jh~$*4_BGnT7!0@s0XlPG=I2`ES;7&NAvtO^AY~y~t z!mIW?f8R$br0m<4nci%faev7FjI9h)(XHN~*l^j_eh>cpiQIq>bp0so!ScA9orPCT zrTVT{1sB!9#GTCT_%XQR;`?y_KJ&ePd%cc^Pqo?hmU%s(^US>2N$kMm_PvS8{-*cw zx$JW6d6Refd|By!8S|_SmgRYUCxicDNzK zTX-RFl=l}&^8zqSyOS?@sNeo2?o3{OR)M1qyH0uL0N(`8C{gTVbt8q+wm&Tw&^of| z9-Zi@^ZiWa!o_&KU#I%qzD!L$I&{Hs)*I8wedKN3Qg>V{2;>d!Ew>tOTQ6IHo=~zt ztV+Xohfrndp;dDx->L*dYdkeuG#aI})tGb{^@De)uk+_oC9|9FO-z)cJycGVGNIbI zD##K)841piA$btSuQDL@BokfbJ7y4=zoh2KZ6V$ z2brlz$MPp+KFNjZ4p2I!y$zP5>U&nT4VIL$S!HoVLSmyztZ8Ijdl$dscsZ3#g?ycA z;LvsAS?~Sd@-6YiSudntQzZ;A007ee@8=AyjP-5)TeqdGAQ`j3k5Ju`JwuFXK%$9; zW)&xgw1KR~g^WZoF9jCg3pMt-;KJF`QP{%M3lyZsBf5)V2ZEO6M-sXV;pBt=ArQe6;a(WAb$%s6Jmg;6ZOTko|L33} zKB03|6Y*db+GQ&$*f>I4kZ5_3`^jt*o$v*23s2bv*%Kw%$tdPgQ}paQ1yuY2Y(@(= z+~k;gkgyGibCb8eH{378$@sRHf0#$xH7PWP$0~idOamr{)e7rhjFj;$X50bHVzW)& z$$n8GTMTDXkbXfR<7-W!tmT%xzT#~)v#v2XCXRndmAt5O9>pw@-1$2Wpc7fDm+Z9c zeWYvhOyT>6OSSbf$Kv)l;zWYP*O$gh^?^LJ8B+_LT3z+YZLWz5iq`Ubf`mzFP@c|7 z6Q>-G&v*LQYhLk=&ddYVs1d#k01%Kco2;sFmud(1R04x`(bj}*Gj);z*!F8_J3~2Hma$SAnz^aYbDm{ zHOA>F#yG-Pst?f}8g#Wtr^7|`bvGNP3uGS^rIwS9b4ozbXRl{&lpNLgnAMD3oD0 z4L^bv*P#+f$1nMDr%(sG83_0F?!{;4=EM6YezD{Ac?n9eyoZi0rG`3K2cF;)gMc~r z&V1uu6K7_tsde6(Dvw(RhOV6EPfH7Stu@DB@l9#yqktKgK!UXM_RVl-cH7ePr`%=} z&bbB;<>sRmn~e9)1M=ci8P+gPtsw{GIaf*tDis>!Va$_v?aitHMifJ__5`L}yf92; z9ytZ2A;&bL4-OJA(k%U4F&wPj-|EgU)MQUrSj8JO?%qG=lo%X8aGOt_kC6mZlaD?9PlPfw$}pQ2M3wx;J0XYk_MVMfAFn zWHq8e-hngth4M&3oGzM>6F(Wr>N;Yj7y>evpS*C-7Iqkm9))_uVS!QQ;=ss0>vRHy zVnS#s;vgbE-nlUxWdXm0lvvpaX$VD3tBcN%C#55zE?s~;+-NO{?p$KQEWu<_NeoVC zMPFwa3!Rr3GmSI=Uy|twP#qh=SxsO$0{95HVICpRM3JQoMrF$6-~!TU;&lUqDafk%Br-q=WW&f-pu^HDyz?LQ;b=u-b7%LrJY6gC(nT z1F5YZ12~8Ks65g?VnYV_)&n~(om5hO0|O<40t=%>=N6dRPpA!kfn<|HoW)6Q#3yIe zNaWD*5q=AxxC3W%q#{xx3fQ6wVRMDZO{HX(=GdXJfh<-06ZRxN`P348Q;Vigl%$mv zedrKxe)m=XA`elWRJsafHL!enk_>gZNhr5B2&ED@XW4*j$XkaB@^WGpX93P?BO}!3 zDI+Ymn;}K=>;MvW?Sw`2Ms>A|8EL?04JO4>foDJjCB->4Fo0jX1$FnI37WuX<_UQa z-bAIIYRNy3av1L1#Xz3Cg{AUFuEfOxTc<5{;d*y4tJL_^IXr{|lDEVO9=Z4 z-#B}RD}JtUZnc@HdQyFbl+uQG*m+F5wju!|5Swg4X(p`FdMM%w=*HwE;|6FL!g?uv z_m=_8qA6iuTnh6!{Tfwc25^xiW<~iF5$eX{jcQqy;=&H>+96iJ6&EHk1iPjE2(v6{ zh5*V8u|t%8EC80!^X}vYW{vZm_etIWO=TqCXb?uL3*4SZ00aAP8A>F6S^)m;2x@9^ zXzl!8BS6rFKu`I-KxpWe^PmL?PBf3TjqnQ=weYHw{r1c-AQ%i7kb}Hz19*`m>^TAP z$D4(pSR=RCAKiWU*fVx;1F_L|r0$dKwNbaSF=jdY*r>ymSp)m#F}eQsDV~O(E26jR z)26xm95v+wWl^W9zDOf~T30#qWc9KA^>5vOOL>;*yR;>$+t9GhqP+#lo2cGgU!DIBW6W#1BR(raqe*XpWn;5oLE>`PKJNCeVX15H;buK zOE_r$#(~K3`ru@lKFWdOpYLf3iFue>UK6R%VJ0U3sgvDs4XjbF*>~={#{% zHV(Is+e<+)Sa2IH(?5(o83*n#bC7u?CG}4So??6K)lN3*X$d?0Cv57d3cH54y1YAI zIxc=BeSa(uvPal338ZbG_7)eQY+uZUjPUtb)yWHpJG-AM0dq-08#e^eA*rV*0go4A zPF&$R0zgL^%?!l4A^BeEbe-A!{fK;l7G(O<0pVx>fswR{g-5DY=ue-hf1c}J@h{iE z;gHNWTrdN!aFUv~Vwg_nX+7LK^^NUYBNZmtROouv{&jm4*9XS@@fezoj@YhHdXy-> z4&nX2{-}3v~_{I_06~TKLbIt*K=1wSUUT@>I_uI z#WYsNgn|BSw_Hx(Q%S8|xHN(G{}kaqWjHtuyP{s>o#0|(>rl+faq3KAzX1O8-)B_L z6*h?m03b&S0093#)swBmf6?r`)I6QAmeN;VW2$~(#v-_&C*q`-i1-ns#7KH40yWkg zP_=pn!3sJJxQ3eH)R5xSf%jtO0DtpA$Wh3f@~u~`JBk;*XXjHCT+GiR6>j=%@;%{9 zXV!i=Jg2|6zPw#Uo-x$CrkigLI(8mjymxSJI-Yl*`Sl*Y_vZ2~L)M?t`SF`b5FohG zZ8(PnnRF}IaH^e)=0{Vhrff>pX%~1;IjzZb%Tyko_Vr5_Z9q2`rrztfV>oANnAOWUmn7;t2vg7N1shA|b;^#Q zpr;lm8kI{?laM^1qeYq(62>K*v)1QMrS^mn|IjjA*-XjEsKmIULmvq`Ql2trKAG%{ zlv&A|*EzxJ?6ueJC7CldM779q2_$xw%BT_|+A!n93RN$XUUUUJaKI;;W@&V^%G)+~ z{VfcyPA^y>cD%1%BV1FjNm}n4^Jvz(az(9NhBp4BM6IxMvu$&y_W1?~>) z%d<0E^9ajuFJ^Ww|HL_xJ#Uq{c`k%@LcgV^rDR%4*~~437mnih;nk5tb}M{}PGiP& zDDN5wyI&&zX&Hqnjb}%t0A%?!;fD4@E1j0UA;!oGDWoeR+3h z;huEjX1=gjja#E?v0g9HtVPK@L8Sp9TE69v=xR~DR)XcNc62R5W@q8sCELWDlUbdnX+y?3*hwZQP5uu(v6QMMCycczRua#y zBBiri&29e?&EM+=5++!4?5yH4=MJ-L2~t?d^&{8*ZG{qjti5!oLZ9RZl!X+6turL2 z*~)o)6pa}rEw7G~F5Yc|#cHr$7^#*HJng?}M>-U$X2A!cT5t!62}iLK+b-5O2K9{= zvc3DFY@=omPs4lUs`V5J70EkIS3d{Q`vvlrK!cA`h1I;e#EXPn2MlgjZ3<~o3nai> zc4qBlt%%hY9NzVup|50dJi?Y@oqc)uAb&&%co3CetM(%B^$hl~uYp#@skV2f z(-604>yvj${8p0jo?dU=<=Y`jUN_+T>A8V?N9G_5kHuoe7Ldyo=cTC~PeuKYr@6SxaO#j~dPauUOg@=Wd zP|MTm_WQ-4Qy|ai*1S-uPb%MsfOORlPbk+VRC&40u?Rck!_`b=P7mdjIpw)r|Cm9~;&By4XRH9*4e>&;-I71L?3b0}^t1TOC55!{}%4G1e&2 zZ(Kxpbrn=28_&wnyPM7DLl)gWL4R@!Ytqdt>_sWDgHI+4rGsL5DgK?CCbHC%@DLh= ziN`{>2Mc69FbV|ky-lG;2HSkeFGx4q5m{|T1<52thLu+sa+8W06sDx9ilTQQ&|Uy< z2OT)@fgg@8uvsre11%O1K(w&2i3%tIdxg#JMI_LCHA>!{V-M%U+i%sccaO{E>qZ`k z%F4kHgO3H5fsMVe?i-)|8)S_+E4*c7ggi^Nw?gGVn!{L7im(v}DOW->Clh`PhyR1Z z?+Z>Mf%!aw@J38CALQ@(jVI#-zd|G8mA0%_J@cp>k@wCu-+MGUO&Xxlxa7Q%bw@CNOEAAn&tJF94E>alUgO%JkOM5GRNpSb?RpQLVm)|J zJ^B66&$c@jtrV@=NRmv|H^`x2>pnJn#S8+Eyyqmh7PkUBmvmzH5v#9Y;j zS;i&%Z1qT?Xs;Vl2*G8j@XU->!=;*v2)Hm^_(O@6EuRS0_6lDgI+#;{qZ#$Cdr_(B z5Q;LtoY$`6Mxy)}}1Lg!h&<4S`RoIELSudsK4bEW|P6DN4w3RAD`jL||tibvv zY+ay0p{)zL<19=DXi(tQflb}l##Zh^HC@sJvh0~~jSgowkeZQ!br69Rg_5{ou3VEk zRI}2mJ5lJSNKLz?7aZk^ky3)gVNOzn`GkWaCM!usAwHKHVg+Cg7zI9wD0v@oYU<2T z$)~uULOoH`Pp}}EG|=1!l3u3RiEOPN`VU+mbO-qBkyV&_xj^^Cdbqxb6@wM%YF+V& zwz2;~zCbrP`}W1iosLn3^$n9cYfkgK75#Rgv{A9@;+u`q)5L-oI`Z0z*tJdq6}~Hm zL#R~_!2r`2A$Nc1048sN*+3Z{xRZ5rL3CRqOGHZimn7Cu*BANMhmeJk8il@?k;#t9 zmf@#_@WlQr6!wj-&!@ku7idgv5PD5i7(+xxADSe|`K(3(XoFKGeq^+BBrgom6a4|@ zW)Ik9Z45I=a-3OK1crf8)WVj%NO53!Io zO8sqZmy--(Jh7wW+RNmP_&SMSs%Y{QQmic-!fOQ~!Phw5a3Y5Qc^kBZAV{ zJnj6@Qt8g51rMT~;*n`vIz&Ye(~lq0&_sa8nglGd+b9|*MjR_RM*BhN${wAt-b(=J zj3Lsc+AiX^O4cCLu8_odsH*dFl=CQgVj^Vo!fY$q6a%Kzh=rE?oQ= z$++W{kE%1v1Ga#kLF;;)<4*PtH9uv#hahByfLM8GoW9XsvvPNlt5)p=a8ane8b4Gr z@J&GJsQ~{C0qPiRvR)CSC>(kIBGbk#j9OYS4}h3u@%P}1D2CRn*F&m~s3EfZ3X_gO zQJnX+!8XLLIDow}ghARE4}lmdL)eg3wb@7E|Go_PY{6sgnSTy9f_SkN=oc25qpx)e z($^wPlA*sQ6-X-_NiKMXEBRZ2Q?LnBUDDV{mAa=V3AnEon4v~&=rg`TFmnYwoCDPE zu~3DqLTn3=#4`$s1KA7~cnoiL&M;33@B&L;)RaK#fJgD@84=UI59{5)!1;BnmChFA z5u(aTSeW|+wSU(n!oh~s3VvE6Dnd}Tbh4L$Age3gV&p71>+P`tj3$M{PN9UkBd?g= zwz7<7&Q`P^@>!{M&}?6#v)X2UrWG_3wQNz$4WwWOs^5C`ACHXX|3)M9XIa72IWk}a zSxz+K`eK#8sW0>#mqc5Bf_GFV>79gsDrNqha5++;U=ylT;yb3>;CKj@x=3t;w+SUy zHw`rB#24utt%wdb8F-c-C=InuEQnO?_qgkA&1ZinK#@ zw^GX>iEE4=Xv%Hz!`laK;7^+%WEv-InkL~rt1-KM4(|Q^7+f2a=|sCtalTub{c_{> zVr?fKq6y6dZMoBH=AH%1XfjB#+|WaH3yYdR6*?hM9W(sN;!Br@TTseV}uKz0N?gm z)=S?v$DS@v-0=%zD;dxud_qr;n8p5SKC>@|HNU1f7&nYr+}~Y13r7aCkBE&e2x3wm z6C)1>%1i!xBE4G@0}wv$gyT|09m(7oja?Uy;K-w-V0<2iQM}MVJY5I+#es4sYQ@%j zQA2H`V6SG26QI+@)I+KV24T;n7$x^bSekhJ_gzkLeDK{fOdSKRY3UT1e72X2uMck4B(Ezc9K2OMHPG+V1*#y$ zpPXwreK#=07`KHQ{4Z?vcidy&U`5%2Qr+SJ59i+4Z+2_=KWKwpyKD!bM4jaW+_dSn zsgw3!n(W5Vk1Pe_fBOgAvu6;)-7KezZXDe_^x|?sFB*N8O^vBIph3AuvPgP6`Tu+a zGk4-SAL9w*6_4c=cxSrk^}3R?F7`KPM?7m=@%o8{nge8Z&yeO`=M|`Gx3rMez_D>N%?=xqZemt*7=;q%{`;U@#9w-;8 z;IFk`K2UrnVP(Q?aaUi?4yzZ3b-Ji;mJ!dD!l7MIp(=QXChRJO>A|ajH_QJem`(K4CV1U-z6<%8Gm-heYtGLhK5mc z;%s9yBc3=x?||4Os~V**G%yK)_%>-K$^yY26!7?)8q-{41YKb6jP40qBA6xt`T z0*n>in=6>a6Kw-xQqJCMZLPjpq?SkE2E?9sA-vO?-`>pbR@PnfrF@f|wNNvs_UvDB zWOcFi{)Dh&x-ECwUJlZS!glh)c!OY3SQSm~Dd~B!7pEe9e%UL%yog31b_t-C3$6Oh z-1xoJ!(w-qn)ymT^e_g30{P#$Fp2)Sbs3cS_e6cea|H>AQicTN>p!}AGWTO>^Je(H z{A`r9+UgBKrbR z?adK0&_3U7UeCqBncMoL%i`@b?ap04KBL-pkBs~{;Nk5}i84ID9h!JM)ZS0kJ|7cb z__@AlL^0UcSHpm%ZH(S3ZLy_%JH{ZsDj zDcaDt&2=h!bGjIeG&|?bD3NV78*2$hsFCuE9F+iGAi`{^0}s z9V7kxXeU9-DJf%&+|bq`j2ikuWq=@|5bL|Thw#Lec8n|H1|(hZUiUX#ZnbqpqFBWQ zAmGP`A<*T^;TE$`mDQE00qd7@Q1T*(g;A6tf?FkHKwovdvaoEX?_~YF<^FK&tZmuS z-F=>cSOK!?1=7z@27#s>a2Br3aZ+N6VCq3J7w`pqKuo`tqwAM}OZjECO*6WB&HxYp zWyc$2f>aTlB*hrF^TB?pXU82WF+|ZQ8}h-d+IW+Gw?-Z(?mk$$*QDrMc_q33QRU)3ccZIHoUmZf@YhTy8lzw0vz7nRt=D_c~UH z+0)@cGIaO8VY?D~;)w1@^lsw4Z5OjBYkR?dir>k1%0BY1v^8UfQ}upUv+ZCJdA+O7 z@Spzy!l%Yt%^h!pjLoUGlLnrLLwP_1r+3x;hp2a;mZ2_>@NwpqyIvB!C*M@rQ!8vk zR+}o1r_HQn!!?6v-&e^+Z7RBJO!MpJX!DA%#X^RE5E_M|lcjS`g#*tI{~UkC z%JyB@THD8vt0b(*%xv46isg@MHppj=`I?Zr;OIuZGm}E$a3RUGj@$#NEs-tb`Fr>w zYyj&}elG5XWThueAY1<%VfYVDPX%8I&^2`I`D^FK+rTknA6)HmEd&{%VWcC5Ekf*(^=DVGeD+YqQyy$U_US> zJRr)=2{(?kG0>zkOxYM`asCJmpfLLz8GM{4I6bazxOqP{ZZfddfERrcf_I>xxjBCJ zZ5kN;M@@e6YgnM|K@C8D2$Qz9VKcleM?dFYu>ChrE7;xOZz@0NWoMWL3v-j|u(zfv z>Xs<_u?4vajUo1bPw@s0e#CC=&geydn-xObQ~MS$RfSRK7yzS zIkzY~JX?_R>;)4kZO#xd1;yV_t$P8#`1AJGI!5;!2%8$9NV*W?f*?mzKf6 z(rSFj{35?JipH73_|6XKaF)240ETwP^w`^acFFD! zZfz7xYGj@)Kana=h(dIXHdDK&2x=e@kC&U{ z)mYJkGIehM>@v-UYnm%{$@{RKKq*yR1t(#TVkLR`sC0W)jV)y&z)e}(hs1VP&4M#e z9vjI1G|>S3%0Fv(o+&3NLm}Ee6;Q|=(yx#*GJq)n=T zuD_`*gpUxQxeH21G*Ro9syd$Z)|10E#T%4TfOIDjAvpN#cu7G*87Tn(+Rs3sK#qt+ zacRdJUMr$#hhV5VgOQCCaGcQs9SM|<(ol6nDIeGA0L?@z7{}1nIE~t z@5xs`A;=F>Dn;c0Nd{JXaSSTv;K{bPl?ly6K&b#QdYY;CQS=LbqGvhuy;19n)99Y( zgbwphV>{Zm@()=CJxr-oUGJsT4=!RDNyMGhASVEsSwa|I*VWpoq}T5_Q@mXnV_E5 zAX&Dcd8tTJlhN>jHD)V>9U0g7ML;~@JV;=J%ma&JlwtH0s_uS5K?ar?i3^=%)egT_ z=!3mEJAQhkXoBY{Adc_VYFH4dhIm_@1X8!^*!zFrQ!;5WsO0YDozm;kpB4rX3-t%R z4#Lza9pK;+}zt5P>L!)~o>k&){6$*J3&4rLp?0wx_D znIZNFuh9?6u3_AbkY^HpCus*|s@05-b%1njh?5Ik{LBpeqlF*sx7~^hJv!WreN&YA z!#>{D^0rsMDIFH&P$^U#Iw=EvsvM_(HB`n0g~{ot5KYtoC_)TlsBSpp(8!KIKyD4P zhlXH7X#hgsr~#roMdL+tMr=SoX&dr%yxhA=;a45ZB=7a~%rasf(geqzAK{+IN#BMF zR^KgFN4LgtyF}OzOZ6bluJ#+Sj$T9)EPfiGI-oJK>F+S0pdUUlZFdfPl)^(R7IZmS zUsb|<2z4<00nn#;#3yhLezau$2r_u}Y^FG7_D6ZkVY&w58Fj?^5YHwME$Q8eVtQHf z$NFiRv+0#sl+&;dRDkuMaaXsS?}$hWx}Z(#;=VjCHqn!<`Z3mRn=}tzR1?jsu0fe7 zlDmz{`BGS%J(yB1f?^vf&}#2_Vq{=ZDx;q+%uP6vOS~hlby^*XKB&??z^P+nMqGas zd)3%Rxo`~Gv~8=vqo4BXYdE?ykN#mBE-k9Pj0}6ss)0m3`E{o1=ygLBu_-%ZAo}%iACmJRkoB2cpO4>UG0YYOp<{nOs`G*)4tv z*zwjr{lf6zLvuRe{?(nhaHi5edo{9CdEa+6T9%J~rH$L%wjdV)3&ZeV=%={omlkRG zz?blo8OJURa&is?<~3pEcqSV#;@e%Q+2BdwPY{3v$nEbMMETpn8!>M7-;l*gllgN= zIsHBMc0+8l*H6~dQ$a|H0Nk)ub@xq+?aR#gAgZH>K(o0M`&9W=F~xZHP57gID+Yms zszJC4HIcP#cmK&k%12h~_KM7fdT1>e3e+>eOT#eVgWobM*h3~2xtGRuRbW>*$IjK`SQ={up!IfxC(T-@BBt&FP3a^a z@WSpYLfN5rimFV2WHQ2J_!OU@i$yN{kbkpe|{{Hihg{`7bNr-YMYr#N1!_2PElAA80qU`>$64u8T2@ z+7_f~0_bGbV@Ld$9nq9KBggOxR;pqrac$VH~z9;yNdDd#UDRlUI`s z*998XwS2j^!yhi$;nU@);g(=C#A99-USU~Zmw5b58tLg$8Y$V1s6mF_ks(b$!xy{AD(w=Xepgen%tgy3^ZTHXGu>Ji?dy+z-z9-unC$)6vav zH(2fkN*cy~85^k{$BWu}TW#!`=ID%;S?1t)v>zGR`hHyX_fI3!P4#Sf9aY;*rAnNH+>#)8LgK5Vo3Y(H1;u5^2aUi@Abv9UP&`WiDt_MLzIdeifD>lq5CO12O_ z^|SC*q4{;S^l~E0?cz4DMl*8lanl|+)7qrev+j1jIYjq;`HFSn^Yw*Ci}!P~>AyO@ zE4G4+?jh@K|9H4>*}bsU@$-p-?sEmr^5gJbGh%6RF1hXXsIX@A@U_!rI57Uj*X?!p zc*q2P6Gm*Zd|uey@94SHZBo^Bv(Kl~#pq=j*x7UZeBzqq^nbWIr(i*XDBE7!wr$(C zZQHi`+E!oNwr$(CZM>d06ETRWullHn{K~AI>#V($<=1(Aj=TG@N4>N~wHd1kTYBU9nGJl~Q{nTmGyK#6zVkJI8KeJ8D7h<0 zck5U3rx7m2g(bKNu=bXPdj}Ioz>{>|1G+2+Ed1;7#rX2SLGYM)l=>ko6CD@ zeQ#0@32>_o-6t?-PO{v~<#;-C_5oi4QX`u|9lL$iclG#&4?s!FW8F6c31tgh# zgE023SeJTk%YxU6NFe#}-AkOPBQrZ|F;=c%fcWe`M>6AhTeApP6cQWnxX0qU30c~L z($pybz6`<}U5b&9?x62C@4d$E%@|2zfg&91L<}J(9@Kcgf(7Kz^(|iHE9}!vhDmBn zDXpY53D)0%>9FhcSc_0dL0Kly{SVq_!{qkrr)0K$$-d#YjteE<3#Js+s4D?zu!>;vAw)?Jr%6$JzJeUMrlh? zp7285KCSBGYTX7kEb4SqLW$~?X`|f-U3!&E-8D+h;7b^8!$$dA(P^8DwM$E8RK?vm z(pF;=&l?L~oVnGWR#n2@ewI6bpRtgo$7r5gme_mssHfCA8(BFLIsfkD&p0v zR;Z;;5^eZuR~o}5i&Zp*1l6nhBiV0N5l9!3HvgVZr|jwEYtf>Vb~Zk)4_r}BpWh29 zVrEPqe@zYIf*8+GuL$|6Sag);`7A&WmnZIrg+yBol`ygEWoYT$+m)dk=rya<9`PKR zVsSF}rPHR3rYu|?Rk=sb$~s4KE)giO?MVhgE&R*B)>Q8`iuGX7q@CBb>`Ju1Yj3C3 zBDXfxUz}L!XU!T^|KQLiM;_iOBtRf7U}3_JreHO2snoJnUr{A{DFoy#e#nofy`Y&( zulxL~!a}#I;?lp$%rHx0SM?&JUN^D}U2a&UYAz})RVdH_D>Z9mRnbF`n{!d8+f~g* zj0yf&2#U$ej2bu`HC^|Cqs*l>$k;+t2|R3G;F$1U(Vh>t_VZy($rZHEO{-j`sFOy7 z6a^yoB5QoNG3{v8p$u4CrSRd$E-ZE~_Q8cL3eFKKUSQ-a^sg_0z5iL)dGazJ9Pumt zO)hbazmoMvZR}m9$7Xw$UyjP-tnMw$W!OBq0j!ZheaT+goezW!iXm8A^DjWneO~%8 zgQhp~m~4{AJ+~(on5;?pw$2z%iK+tY3yfdgqg%J z1N2SDOlX4RVmFxZ)|l%TBEZdu9E1`7qAX|LMmQY@Rpt|yGD2rs&4iQKhS7>WF?bI^ z1sJvtHICLO!DiASlU+e)VD+S2WYYD`uPc_NVFt>#TOT!YMT?YJHS`TFPa$1oL}y_N zMaHPdV7zRq!`CU*+Q`R~#@H}~QN!3EM#u*b&gK~bi(`a_OLypt#bp+zE~d_Cdd_1+ z{G`;jfN(CZwD;BXM%pP!gCPRrx}s;F8Y0Rl=QU1_VYy@yr`dF6uE}YUHQi8L`yoTg zHMHE)FvCEZk#u%s5XHwHf-k`^6AG5aWI~~bmsZYYLnr_i15ix70XZD`2^jfoVUGEW z(1~S4+#QG7ZR4SAVsz@D+j;d8?68J3o<@c%f zlCmcPdB5njkG-gBFib_Ew~VM&A8BMgihW$fjJX=u*gQSpbIoDlb3y!c16`v!Udi2N~H4<7>PN{AcB*TwgsN4tAg8PQaOYond&!Sg0}F zK^i6-u+19F!C*-W7F+gwx`W18f}2QeCX?MHifYeIxQ~*J00H}3pvw@Lb0Z~NReXL5 zQ47J$s)tNRoC0$2bDpb8hkWK8oW0=Tbb4Dr2#l3q2-s|T^$K`(KWuZ4(%~tvVh11? zgsZ<$m3d5no`B|#K!iX!zG%6#3(qb@Dcz8B5;0rDw2X}o;Vr`b99F`dhfat625)Se z#35vI@UI&oBL`OR7^0++1k&KJ0mj_hFk}Kt5|JhRf%!AmOkGUb8d%0ExvG7kiNzd5 z?TvU^hhu<8@wA(P>ftz`V?L+Jh;l4OG&XVuqWMxxCms8i0ts&>ldm>dl99EKw2qA~ zA#MFSqBcv9CkGw|ZY(yZxD75V*#xoN7Ot!F5So31Eus_AVi9(%wyGsLNz{aj2??-^ zvp|(c#gbEKyrd(flAC1Z%b`-MDG{n14g*fmLo|jcvV@qz4lHc=%!;0{5;j2 z>dJoaHk3=k$kj`H#+$zLE_zY-H0&YdI{W z%;|^Bwn0#jOKu2F3;=H80h2^qmGZ7Bt*@==5csZ6MVmA?)lI_*H(Kjp#&r50)y;YF zO<8DP6J>l(qt5Piv`zIfP)E+pL#P=2Sc}SQ^79eH3DilLp=+viC)Lel!xI7q^#o4J z3Nov1vt3hxOv{gK!d_+)U+^OR@_XFrY1(spyRV6pAp>I~;T)Z1!*4l_UedyQMe(a5 zwKd6VhETOOtwZB{ zb@>d&ut|j-Q~M5m#B2Y$>Db2!7GuhEx?XXmC(oUpJS-7cRTgUW%uEuE*wfwFGu-Ko zb4M9m9J{l5j?Jg z+T;#bTNq+r0S`@qj|sk2B)V&w1NG%0G3{GNK%Pnj5)Pb;`6DOAmg$9Y;PP2t^Rf_#6)wj?U!3uL_}L7_X9Oh?|-T^rmJ^AvNFc_Dm8$# zNTtd(sZ=ox&bL;Cv#5}uRkm5xM`8|vmU0!*BykS(o$B0`!^$FS5g$LVE)M+anlH6)ze`!Bpv3lI7O|N zm4KCz07Mq+Pm7B1{A)l(bl3ztuA>r5W@7c%#MqB;g%a9zAQ;K2zUn@a@SDWOINa9* zW~_l>zw3+DTP`UaMhfl%@X<~E66O~1 zSGaTdXYO+dTxd0%o5jyrX$vg4lh>4We;Tnh&JGW?wNSMNwKcc~xz*|`RZm=3@c^+% z<^4tQL6-F>V|tr?;H>88ftk|3vfe4xhOHWur6@|&V!^bcXengsz`888P~3GZWpkCsXdRz}{FdIiyhb(P2x? zE_t-dRG~twaO$cn=fv`$$L4R{{oo&cAt&QnfIB`td z_Y%0i4{~uF0tueHmlJsU@HmUFL0JhI1d@S=Ga`ZLib`n|aO1n(m{&gVfQN(?bm*Ry zB3eN)q&1u`Tit6Aa0sItlF}QiKeK`l@C?rGN;o9xoDvL=isC*phxoRGQ~k&TQ`=a- zYexU^+F7B0Y7~$;7i7u@is8{R#-E4s25@R?6O#EAX=u`K@3_4hQjpj0NH-h|3>?5mJ9Hg7gEDRNc=YB2xeRF2R$`g;(?2=q)m zlX)I)Tbdz@He&G_oa!IYV%hYFZgdc31E`~~0$17=L?q)ylFgikCFYS2Q=WL zWuzB0krqVoB5Fx-$d%K@M;r8+yE`y6zS_+Dy0dy+n#-(rHPcsp^r}#WKmK0>%Hdqq zhtGW5GY_2n$=#Qq5>KoSzaI}4znle5Jd<28pvzZahg5k^Jji9K@E~%$q6mv$JPLHM zhdjc;_Z%Ys!W;Z%_$vs4JP4TyS8RvtyF#T?n5un$P9wpkc=jDSvV0&VWe&&;?{b8* zJmL&5XjZ4(7ly_wa?CGA!K9Q+4JpSm)Ex0ztaB z48L*-p2T?K;KKRYqy0gV^er)er8eKxW10QrOYgTBIQ ze!WURYhAIP(}Fp~O1gITf3Oj}pmTmRQ0)7v*tvQ;y7KwRl+voq1Lw6&qkj}!*L5)E zuX}&R|IsV|fv==Mswk^tjp{=ntOvC<6%Y&s5E-zcCFB!SDzL#zm&JfKcF_#cbdii9 znNWoU1_rreI#7oZVP?ekLC}JZDxw%c~S`<#Bwejexk ze($*J@A*Llc>rYqYXW2lmH;FKRt4G+CKmRn_r9Q>3f{;w$XRVK}-b$g$Txi zL=xTM0f`I80(#-ENCHZcUZqK|Npnq^BQSZ`gF}2CAb+4BUF3nJA~9tIe^=}tuzN<| z;knGw18_xuCU9DHohrRyR~oE9VD@$do;hSA&!LUI-CjYx>*h_zulD22RPMWzX#C=y zX#;W!v;@DRik>HX@H^i5UhHpZu0adEkA^(%rGkG;dWcU|06F!`0`tH2dqJFA-c)mB zpe>Q?0#x$PaB9a-C~rUOkMd&w)!|>Yv=HC5t>1OoW&gY2R;@famBStn)>;)LC(6mR;ev46*DzkH<@;Ry`>D}6(?Y6jW4z1yAo}(?_mkId+5=*Hs9SDy z;OmHahFf}KlA;pp5~Y*fvD6m-+v%hWc=$#|XodX<_`Qmtb7-jRJfQwBH2f0(l%jq5 z$myRGYuihx5&j2$)-0DsYmY(ymvuTuZ5hqYbL(|$ToR*KGx{j;XL8-!1@@nPi4Rif z*nO1W$%RuSE@6HuYsajb8azmisy0WRbC>qc^buY(+p4=U9=~nIBS=|MenK~orA5@W zs3r~E^lHWVfeYUsIcs^NruZsmabG1qTB8fy%$gM@oL$B>T+0$!drK*dgH+Q> zjt|6A#2n#@-*A_%qq78$_J9y=Psp3< z6gK*m_*7@IjZd#042kR4LUCD8C=vf}`@yV2=GnXKn7-NHg4s1AYlU5-y%6p{`BX>e zlMfv;y;mu&Iiz8*Kc00G7_7?VZxC{7@dgM3rzV0!8ZI_=M_FCqkw>{sZlj4VFvmG+J-te5k zGFqiRa~`d4{e3d;YEP%p2Rx1Bx+oEn_Z->Dxu2XXdqdZrNShM4zKC7#TgOQMHiyHv zAVQIQC-Uu^emWV7pDNo0JIed8_@|7{$TeFdr5;UFJ7xQ|ExlDRH2*sOYs+Ks@=rQC zd#?<%o8RH=6M@Tbel$6|`U=-h+0ErAR`ruHG_M^>oxN{u-UK>C2Yw@UnwD-R&^1_f ztg;Mc(qS=U*#RZFuLZAJeX)waIV44PY5CK^B5LpakW*~>kr z7dtKLK%X-Y`z(c)--F2HkM5p@bf@GKyocUlSTEXqqi_CK@s}qJy3my`ywsS!J>n^B z2Ta;)p3XKUMP*XXH-<~{WS{VHcqhm0J_~Ai8M+ic4yH@g$CIFDv*tD95?N9ZKWUPy z@vF>#Jot`;bG$Bn#zJ`Dl7Zjx<_O1H0~-k80WJ5}?`KAU;tO9QrXTU_IUYk_f8_koBL6_kFx|IGi0i zlVUKzQ$Tp~Uf#R*{&6)51R@9%9(n8x{4$XBAM*gq?UXrcT|b7!%-zL{-zGdn=M!!j zXs-DefAXdAM8X|NmS;c^np-DR<0n@pd@U%#gH;+$o0&VQd_T6oO-kFB@g`JLZlL^ za*;x$N~KgxNQUV_jzGAqTS{*Wk4{d1li)O(HLT~Pf%}`%Z`QbG^YV9&%Fe?{Oh!nh z0H;1-tf{6kCM}9fAY@!?I5RJtwf+W6^Oz2=6?EZ#Rr=&!oG7M zPpo(_xD4msKuQ%V84^qUSO%8N#HcJF?pq9#NTu2^Z>?X3L?y5PmoMQL_s3k zoa6pMRqs71)7MY|EV}x7I{GlY-~fJvbmqP*3%)TKIf4iwgv;Q-v{2Q43@fiL%q6y! zvm$8yuy!GeAktURr@t`cD(Nb<*Xo2@XsO+4yMJV~ZNXjZ_yS5o=-^*{|4dG{hT zW?YhPD(ur7bIety?U|PE?S_GEaV)Qx@Y68%gUh&N&peaUoH>}!8D{Oa^uu30H*Qc{ z9&&mEQf;&N5SXE}h(<7sL}NydN~lX+6-uH7MI`-rq);gk3r#SsR!43z$(JC>TnVLS zRugR5nWeNLk;+XXXFim=spnJ_$qsz6D%s@;wmN$fAx%1B4PE03k*x)U(~UKz5h_!EvEC1E6O?_sRf#=eunsljWb%^KH*IC3|qm6f_Z z)BQcY9?s!(J_U9Ft31J8^<;Z+Q^xu#^0adxyR)ahz}t0-!08N78S-d(cE;3qRx92#-YxW6NQY zay4ooQeiD8Sw9lSWiVoNlA3ALGIWDD7lUnlYwO+AH*KrvHxV9f@fbXJ|$aM)(9+WXi~4*ov#N7nw*=;h(jv;@WD@BJSV0x%8h- zh=<^R>Jm+zZ2nItT(l;v1M+J2?()P+!%O38YM+1v!WbbSv4$p8ePExK5JWgIn6O1p zys4$_WNPz7jR^sQk}UF(YdKzP4}gY^D0@I zn^T$#lWl4H?5_W_30mS-mU+%g_wT3wumA0@+RBEkHc=2l^l%a{`*ZTNqhOi8C_?a} z-z3JAhUFlY$)cunS28_DYMgwgyJpCf!cN2F8x4k=aSFRNTG+$jVrYz0hk+S0XpIqr zF}5-lNu`p_j&Hfgqle#Icg-YPv`l?Il`iEebPH25{Yk}Ns}z~X3q9c(3JgPs;Z1#98^CZbc6mKrB4m>C~wYDdcAI%tflL3|GT4Wk!6sd-w=*6po%zccc zAQ9+p4?r#I8Z(UM>&86o`x)G19RbH4>}HBNILn!?*&5c((x{A7LKA!krGQoSgi^Yg z^~X$^)#Ua8O2Qe6OSO_{H9Z$6(%h*fytvFyLSZYdM*Ho35yxjyG8x2cJif0_kcf&K z9D?BI#x&Lu8BZIdA%O>A*Aa=i)p&s>hB*z#m_n&TqE*u{S*o3cn-k%}Ga+efo~!Dl z#}_2p;%#^H_sc`f!{}e}j`F1#aUg;BL-$Dymasde>Ie#DQNWnj8R_$fv{|A?(ju+* z|Iw_A?rKb+0};zQvZCt~D7H{-mU9^6GDp*q$8BfC5QN5yC1Zhi!#ecWM_Ss0DzVZ+ zWu=SBts4Vr0WOxw(&5zRA-bi8mQ(-Yt^)lgVJYH$%>9jslNKUaGg=JpC?!{VZz+5s9RktWQ&oEG4QjzE6%szrPUB$N|$Q5D|eZ+)UTA3c(?QkU>2K@$KXQaUFQRo7bLq|7H{HFOpg z#m6Q-e3WN0)SB#nJw6Yhe-vE64l#2pV6D9_qIc1ko|ianDt$g#TAr4$^JI`hFkI71 zNZnS?kT5w4ph(J^B&vh-uO6GIss$YZZdR%7IWG8x&_?KO(l#!#zx@1c|k$g1PD{F4kpDXt6+>Q<0x|J7iXS7_F%XM%kAM5|-0EOI26s`-D|gHGEgR?SM&sft)nuQB=lQ&CTN#%vq~tM<&Rr z!ksCF6BN2gd4)&IsJpVBr+kekSAF14J3%K8=T$W`Vg#cEoD1+$%M`my2ixij`I3;-2esk_o*K1hrM6e&AJ^^*2kS6dBi-Ab9jQ`T&#$;S1YbQm#M>py7+So1bCRaHF`Q2=4^&|9u4rc z%?+4QQDZbAs6*%w^5P*C+{b5zhXsA#$ERK!JQhq$z`2?BI{g_jGu_uldrmVav}CX2 zEa18HV(XL=&3@_sRm;NgbhGt#WB;408;cli4k~G^E

    }`i8pN_)Z|`7N@P2e0t^w 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 +%% @copyright 2007 Mochi Media, Inc. + +%% Changes specific to SockJS: support for handling \xFFFE and \xFFFF +%% characters. The usual xmerl_ucs:to_utf8 doesn't work for those (in +%% fact these characters aren't valid unicode characters). But we can +%% support them, why not: +%% +%% diff --git a/src/mochijson2_fork.erl b/src/mochijson2_fork.erl +%% index ddd62c7..8c26fc6 100644 +%% --- a/src/mochijson2_fork.erl +%% +++ b/src/mochijson2_fork.erl +%% @@ -458,7 +458,14 @@ tokenize_string(B, S=#decoder{offset=O}, Acc) -> +%% Acc1 = lists:reverse(xmerl_ucs:to_utf8(CodePoint), Acc), +%% tokenize_string(B, ?ADV_COL(S, 12), Acc1); +%% true -> +%% - Acc1 = lists:reverse(xmerl_ucs:to_utf8(C), Acc), +%% + R = if C < 16#FFFE -> +%% + xmerl_ucs:to_utf8(C); +%% + true -> +%% + [16#E0 + (C bsr 12), +%% + 128+((C bsr 6) band 16#3F), +%% + 128+(C band 16#3F)] +%% + end, +%% + Acc1 = lists:reverse(R, Acc), +%% tokenize_string(B, ?ADV_COL(S, 6), Acc1) +%% end; +%% <<_:O/binary, C1, _/binary>> when C1 < 128 -> +%% + +%% @doc Yet another JSON (RFC 4627) library for Erlang. mochijson2 works +%% with binaries as strings, arrays as lists (without an {array, _}) +%% wrapper and it only knows how to decode UTF-8 (and ASCII). +%% +%% JSON terms are decoded as follows (javascript -> erlang): +%%

    +%%
      +%%
    • Strings in JSON decode to UTF-8 binaries in Erlang
    • +%%
    • Objects decode to {struct, PropList}
    • +%%
    • Numbers decode to integer or float
    • +%%
    • true, false, null decode to their respective terms.
    • +%%
    +%% The encoder will accept the same format that the decoder will produce, +%% but will also allow additional cases for leniency: +%%
      +%%
    • atoms other than true, false, null will be considered UTF-8 +%% strings (even as a proplist key) +%%
    • +%%
    • {json, IoList} will insert IoList directly into the output +%% with no validation +%%
    • +%%
    • {array, Array} will be encoded as Array +%% (legacy mochijson style) +%%
    • +%%
    • A non-empty raw proplist will be encoded as an object as long +%% as the first pair does not have an atom key of json, struct, +%% or array +%%
    • +%%
    + +-module(mochijson2_fork). +-author('bob@mochimedia.com'). +-export([encoder/1, encode/1]). +-export([decoder/1, decode/1, decode/2]). + +%% This is a macro to placate syntax highlighters.. +-define(Q, $\"). +-define(ADV_COL(S, N), S#decoder{offset=N+S#decoder.offset, + column=N+S#decoder.column}). +-define(INC_COL(S), S#decoder{offset=1+S#decoder.offset, + column=1+S#decoder.column}). +-define(INC_LINE(S), S#decoder{offset=1+S#decoder.offset, + column=1, + line=1+S#decoder.line}). +-define(INC_CHAR(S, C), + case C of + $\n -> + S#decoder{column=1, + line=1+S#decoder.line, + offset=1+S#decoder.offset}; + _ -> + S#decoder{column=1+S#decoder.column, + offset=1+S#decoder.offset} + end). +-define(IS_WHITESPACE(C), + (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)). + +%% -type(decoder_option() :: any()). +%% -type(handler_option() :: any()). + +%% -type(json_string() :: atom | binary()). +%% -type(json_number() :: integer() | float()). +%% -type(json_array() :: [json_term()]). +%% -type(json_object() :: {struct, [{json_string(), json_term()}]}). +%% -type(json_eep18_object() :: {[{json_string(), json_term()}]}). +%% -type(json_iolist() :: {json, iolist()}). +%% -type(json_term() :: json_string() | json_number() | json_array() | +%% json_object() | json_eep18_object() | json_iolist()). + +-record(encoder, {handler=null, + utf8=false}). + +-record(decoder, {object_hook=null, + offset=0, + line=1, + column=1, + state=null}). + +%% -type(utf8_option() :: boolean()). +%% -type(encoder_option() :: handler_option() | utf8_option()). +%% -spec encoder([encoder_option()]) -> function(). +%% @doc Create an encoder/1 with the given options. +%% Emit unicode as utf8 (default - false) +encoder(Options) -> + State = parse_encoder_options(Options, #encoder{}), + fun (O) -> json_encode(O, State) end. + +%% -spec encode(json_term()) -> iolist(). +%% @doc Encode the given as JSON to an iolist. +encode(Any) -> + json_encode(Any, #encoder{}). + +%% -spec decoder([decoder_option()]) -> function(). +%% @doc Create a decoder/1 with the given options. +decoder(Options) -> + State = parse_decoder_options(Options, #decoder{}), + fun (O) -> json_decode(O, State) end. + +%% -spec decode(iolist(), [{format, proplist | eep18 | struct}]) -> json_term(). +%% @doc Decode the given iolist to Erlang terms using the given object format +%% for decoding, where proplist returns JSON objects as [{binary(), json_term()}] +%% proplists, eep18 returns JSON objects as {[binary(), json_term()]}, and struct +%% returns them as-is. +decode(S, Options) -> + json_decode(S, parse_decoder_options(Options, #decoder{})). + +%% -spec decode(iolist()) -> json_term(). +%% @doc Decode the given iolist to Erlang terms. +decode(S) -> + json_decode(S, #decoder{}). + +%% Internal API + +parse_encoder_options([], State) -> + State; +parse_encoder_options([{handler, Handler} | Rest], State) -> + parse_encoder_options(Rest, State#encoder{handler=Handler}); +parse_encoder_options([{utf8, Switch} | Rest], State) -> + parse_encoder_options(Rest, State#encoder{utf8=Switch}). + +parse_decoder_options([], State) -> + State; +parse_decoder_options([{object_hook, Hook} | Rest], State) -> + parse_decoder_options(Rest, State#decoder{object_hook=Hook}); +parse_decoder_options([{format, Format} | Rest], State) + when Format =:= struct orelse Format =:= eep18 orelse Format =:= proplist -> + parse_decoder_options(Rest, State#decoder{object_hook=Format}). + +json_encode(true, _State) -> + <<"true">>; +json_encode(false, _State) -> + <<"false">>; +json_encode(null, _State) -> + <<"null">>; +json_encode(I, _State) when is_integer(I) -> + integer_to_list(I); +json_encode(F, _State) when is_float(F) -> + mochinum_fork:digits(F); +json_encode(S, State) when is_binary(S); is_atom(S) -> + json_encode_string(S, State); +json_encode([{K, _}|_] = Props, State) when (K =/= struct andalso + K =/= array andalso + K =/= json) -> + json_encode_proplist(Props, State); +json_encode({struct, Props}, State) when is_list(Props) -> + json_encode_proplist(Props, State); +json_encode({Props}, State) when is_list(Props) -> + json_encode_proplist(Props, State); +json_encode({}, State) -> + json_encode_proplist([], State); +json_encode(Array, State) when is_list(Array) -> + json_encode_array(Array, State); +json_encode({array, Array}, State) when is_list(Array) -> + json_encode_array(Array, State); +json_encode({json, IoList}, _State) -> + IoList; +json_encode(Bad, #encoder{handler=null}) -> + exit({json_encode, {bad_term, Bad}}); +json_encode(Bad, State=#encoder{handler=Handler}) -> + json_encode(Handler(Bad), State). + +json_encode_array([], _State) -> + <<"[]">>; +json_encode_array(L, State) -> + F = fun (O, Acc) -> + [$,, json_encode(O, State) | Acc] + end, + [$, | Acc1] = lists:foldl(F, "[", L), + lists:reverse([$\] | Acc1]). + +json_encode_proplist([], _State) -> + <<"{}">>; +json_encode_proplist(Props, State) -> + F = fun ({K, V}, Acc) -> + KS = json_encode_string(K, State), + VS = json_encode(V, State), + [$,, VS, $:, KS | Acc] + end, + [$, | Acc1] = lists:foldl(F, "{", Props), + lists:reverse([$\} | Acc1]). + +json_encode_string(A, State) when is_atom(A) -> + L = atom_to_list(A), + case json_string_is_safe(L) of + true -> + [?Q, L, ?Q]; + false -> + json_encode_string_unicode(xmerl_ucs:from_utf8(L), State, [?Q]) + end; +json_encode_string(B, State) when is_binary(B) -> + case json_bin_is_safe(B) of + true -> + [?Q, B, ?Q]; + false -> + json_encode_string_unicode(xmerl_ucs:from_utf8(B), State, [?Q]) + end; +json_encode_string(I, _State) when is_integer(I) -> + [?Q, integer_to_list(I), ?Q]; +json_encode_string(L, State) when is_list(L) -> + case json_string_is_safe(L) of + true -> + [?Q, L, ?Q]; + false -> + json_encode_string_unicode(L, State, [?Q]) + end. + +json_string_is_safe([]) -> + true; +json_string_is_safe([C | Rest]) -> + case C of + ?Q -> + false; + $\\ -> + false; + $\b -> + false; + $\f -> + false; + $\n -> + false; + $\r -> + false; + $\t -> + false; + C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF -> + false; + C when C < 16#7f -> + json_string_is_safe(Rest); + _ -> + false + end. + +json_bin_is_safe(<<>>) -> + true; +json_bin_is_safe(<>) -> + case C of + ?Q -> + false; + $\\ -> + false; + $\b -> + false; + $\f -> + false; + $\n -> + false; + $\r -> + false; + $\t -> + false; + C when C >= 0, C < $\s; C >= 16#7f -> + false; + C when C < 16#7f -> + json_bin_is_safe(Rest) + end. + +json_encode_string_unicode([], _State, Acc) -> + lists:reverse([$\" | Acc]); +json_encode_string_unicode([C | Cs], State, Acc) -> + Acc1 = case C of + ?Q -> + [?Q, $\\ | Acc]; + %% Escaping solidus is only useful when trying to protect + %% against "" injection attacks which are only + %% possible when JSON is inserted into a HTML document + %% in-line. mochijson2 does not protect you from this, so + %% if you do insert directly into HTML then you need to + %% uncomment the following case or escape the output of encode. + %% + %% $/ -> + %% [$/, $\\ | Acc]; + %% + $\\ -> + [$\\, $\\ | Acc]; + $\b -> + [$b, $\\ | Acc]; + $\f -> + [$f, $\\ | Acc]; + $\n -> + [$n, $\\ | Acc]; + $\r -> + [$r, $\\ | Acc]; + $\t -> + [$t, $\\ | Acc]; + C when C >= 0, C < $\s -> + [unihex(C) | Acc]; + C when C >= 16#7f, C =< 16#10FFFF, State#encoder.utf8 -> + [xmerl_ucs:to_utf8(C) | Acc]; + C when C >= 16#7f, C =< 16#10FFFF, not State#encoder.utf8 -> + [unihex(C) | Acc]; + C when C < 16#7f -> + [C | Acc]; + _ -> + exit({json_encode, {bad_char, C}}) + end, + json_encode_string_unicode(Cs, State, Acc1). + +hexdigit(C) when C >= 0, C =< 9 -> + C + $0; +hexdigit(C) when C =< 15 -> + C + $a - 10. + +unihex(C) when C < 16#10000 -> + <> = <>, + Digits = [hexdigit(D) || D <- [D3, D2, D1, D0]], + [$\\, $u | Digits]; +unihex(C) when C =< 16#10FFFF -> + N = C - 16#10000, + S1 = 16#d800 bor ((N bsr 10) band 16#3ff), + S2 = 16#dc00 bor (N band 16#3ff), + [unihex(S1), unihex(S2)]. + +json_decode(L, S) when is_list(L) -> + json_decode(iolist_to_binary(L), S); +json_decode(B, S) -> + {Res, S1} = decode1(B, S), + {eof, _} = tokenize(B, S1#decoder{state=trim}), + Res. + +decode1(B, S=#decoder{state=null}) -> + case tokenize(B, S#decoder{state=any}) of + {{const, C}, S1} -> + {C, S1}; + {start_array, S1} -> + decode_array(B, S1); + {start_object, S1} -> + decode_object(B, S1) + end. + +make_object(V, #decoder{object_hook=N}) when N =:= null orelse N =:= struct -> + V; +make_object({struct, P}, #decoder{object_hook=eep18}) -> + {P}; +make_object({struct, P}, #decoder{object_hook=proplist}) -> + P; +make_object(V, #decoder{object_hook=Hook}) -> + Hook(V). + +decode_object(B, S) -> + decode_object(B, S#decoder{state=key}, []). + +decode_object(B, S=#decoder{state=key}, Acc) -> + case tokenize(B, S) of + {end_object, S1} -> + V = make_object({struct, lists:reverse(Acc)}, S1), + {V, S1#decoder{state=null}}; + {{const, K}, S1} -> + {colon, S2} = tokenize(B, S1), + {V, S3} = decode1(B, S2#decoder{state=null}), + decode_object(B, S3#decoder{state=comma}, [{K, V} | Acc]) + end; +decode_object(B, S=#decoder{state=comma}, Acc) -> + case tokenize(B, S) of + {end_object, S1} -> + V = make_object({struct, lists:reverse(Acc)}, S1), + {V, S1#decoder{state=null}}; + {comma, S1} -> + decode_object(B, S1#decoder{state=key}, Acc) + end. + +decode_array(B, S) -> + decode_array(B, S#decoder{state=any}, []). + +decode_array(B, S=#decoder{state=any}, Acc) -> + case tokenize(B, S) of + {end_array, S1} -> + {lists:reverse(Acc), S1#decoder{state=null}}; + {start_array, S1} -> + {Array, S2} = decode_array(B, S1), + decode_array(B, S2#decoder{state=comma}, [Array | Acc]); + {start_object, S1} -> + {Array, S2} = decode_object(B, S1), + decode_array(B, S2#decoder{state=comma}, [Array | Acc]); + {{const, Const}, S1} -> + decode_array(B, S1#decoder{state=comma}, [Const | Acc]) + end; +decode_array(B, S=#decoder{state=comma}, Acc) -> + case tokenize(B, S) of + {end_array, S1} -> + {lists:reverse(Acc), S1#decoder{state=null}}; + {comma, S1} -> + decode_array(B, S1#decoder{state=any}, Acc) + end. + +tokenize_string(B, S=#decoder{offset=O}) -> + case tokenize_string_fast(B, O) of + {escape, O1} -> + Length = O1 - O, + S1 = ?ADV_COL(S, Length), + <<_:O/binary, Head:Length/binary, _/binary>> = B, + tokenize_string(B, S1, lists:reverse(binary_to_list(Head))); + O1 -> + Length = O1 - O, + <<_:O/binary, String:Length/binary, ?Q, _/binary>> = B, + {{const, String}, ?ADV_COL(S, Length + 1)} + end. + +tokenize_string_fast(B, O) -> + case B of + <<_:O/binary, ?Q, _/binary>> -> + O; + <<_:O/binary, $\\, _/binary>> -> + {escape, O}; + <<_:O/binary, C1, _/binary>> when C1 < 128 -> + tokenize_string_fast(B, 1 + O); + <<_:O/binary, C1, C2, _/binary>> when C1 >= 194, C1 =< 223, + C2 >= 128, C2 =< 191 -> + tokenize_string_fast(B, 2 + O); + <<_:O/binary, C1, C2, C3, _/binary>> when C1 >= 224, C1 =< 239, + C2 >= 128, C2 =< 191, + C3 >= 128, C3 =< 191 -> + tokenize_string_fast(B, 3 + O); + <<_:O/binary, C1, C2, C3, C4, _/binary>> when C1 >= 240, C1 =< 244, + C2 >= 128, C2 =< 191, + C3 >= 128, C3 =< 191, + C4 >= 128, C4 =< 191 -> + tokenize_string_fast(B, 4 + O); + _ -> + throw(invalid_utf8) + end. + +tokenize_string(B, S=#decoder{offset=O}, Acc) -> + case B of + <<_:O/binary, ?Q, _/binary>> -> + {{const, iolist_to_binary(lists:reverse(Acc))}, ?INC_COL(S)}; + <<_:O/binary, "\\\"", _/binary>> -> + tokenize_string(B, ?ADV_COL(S, 2), [$\" | Acc]); + <<_:O/binary, "\\\\", _/binary>> -> + tokenize_string(B, ?ADV_COL(S, 2), [$\\ | Acc]); + <<_:O/binary, "\\/", _/binary>> -> + tokenize_string(B, ?ADV_COL(S, 2), [$/ | Acc]); + <<_:O/binary, "\\b", _/binary>> -> + tokenize_string(B, ?ADV_COL(S, 2), [$\b | Acc]); + <<_:O/binary, "\\f", _/binary>> -> + tokenize_string(B, ?ADV_COL(S, 2), [$\f | Acc]); + <<_:O/binary, "\\n", _/binary>> -> + tokenize_string(B, ?ADV_COL(S, 2), [$\n | Acc]); + <<_:O/binary, "\\r", _/binary>> -> + tokenize_string(B, ?ADV_COL(S, 2), [$\r | Acc]); + <<_:O/binary, "\\t", _/binary>> -> + tokenize_string(B, ?ADV_COL(S, 2), [$\t | Acc]); + <<_:O/binary, "\\u", C3, C2, C1, C0, Rest/binary>> -> + C = erlang:list_to_integer([C3, C2, C1, C0], 16), + if C > 16#D7FF, C < 16#DC00 -> + %% coalesce UTF-16 surrogate pair + <<"\\u", D3, D2, D1, D0, _/binary>> = Rest, + D = erlang:list_to_integer([D3,D2,D1,D0], 16), + [CodePoint] = xmerl_ucs:from_utf16be(<>), + Acc1 = lists:reverse(xmerl_ucs:to_utf8(CodePoint), Acc), + tokenize_string(B, ?ADV_COL(S, 12), Acc1); + true -> + R = if C < 16#FFFE -> + xmerl_ucs:to_utf8(C); + true -> + [16#E0 + (C bsr 12), + 128+((C bsr 6) band 16#3F), + 128+(C band 16#3F)] + end, + Acc1 = lists:reverse(R, Acc), + tokenize_string(B, ?ADV_COL(S, 6), Acc1) + end; + <<_:O/binary, C1, _/binary>> when C1 < 128 -> + tokenize_string(B, ?INC_CHAR(S, C1), [C1 | Acc]); + <<_:O/binary, C1, C2, _/binary>> when C1 >= 194, C1 =< 223, + C2 >= 128, C2 =< 191 -> + tokenize_string(B, ?ADV_COL(S, 2), [C2, C1 | Acc]); + <<_:O/binary, C1, C2, C3, _/binary>> when C1 >= 224, C1 =< 239, + C2 >= 128, C2 =< 191, + C3 >= 128, C3 =< 191 -> + tokenize_string(B, ?ADV_COL(S, 3), [C3, C2, C1 | Acc]); + <<_:O/binary, C1, C2, C3, C4, _/binary>> when C1 >= 240, C1 =< 244, + C2 >= 128, C2 =< 191, + C3 >= 128, C3 =< 191, + C4 >= 128, C4 =< 191 -> + tokenize_string(B, ?ADV_COL(S, 4), [C4, C3, C2, C1 | Acc]); + _ -> + throw(invalid_utf8) + end. + +tokenize_number(B, S) -> + case tokenize_number(B, sign, S, []) of + {{int, Int}, S1} -> + {{const, list_to_integer(Int)}, S1}; + {{float, Float}, S1} -> + {{const, list_to_float(Float)}, S1} + end. + +tokenize_number(B, sign, S=#decoder{offset=O}, []) -> + case B of + <<_:O/binary, $-, _/binary>> -> + tokenize_number(B, int, ?INC_COL(S), [$-]); + _ -> + tokenize_number(B, int, S, []) + end; +tokenize_number(B, int, S=#decoder{offset=O}, Acc) -> + case B of + <<_:O/binary, $0, _/binary>> -> + tokenize_number(B, frac, ?INC_COL(S), [$0 | Acc]); + <<_:O/binary, C, _/binary>> when C >= $1 andalso C =< $9 -> + tokenize_number(B, int1, ?INC_COL(S), [C | Acc]) + end; +tokenize_number(B, int1, S=#decoder{offset=O}, Acc) -> + case B of + <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> + tokenize_number(B, int1, ?INC_COL(S), [C | Acc]); + _ -> + tokenize_number(B, frac, S, Acc) + end; +tokenize_number(B, frac, S=#decoder{offset=O}, Acc) -> + case B of + <<_:O/binary, $., C, _/binary>> when C >= $0, C =< $9 -> + tokenize_number(B, frac1, ?ADV_COL(S, 2), [C, $. | Acc]); + <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E -> + tokenize_number(B, esign, ?INC_COL(S), [$e, $0, $. | Acc]); + _ -> + {{int, lists:reverse(Acc)}, S} + end; +tokenize_number(B, frac1, S=#decoder{offset=O}, Acc) -> + case B of + <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> + tokenize_number(B, frac1, ?INC_COL(S), [C | Acc]); + <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E -> + tokenize_number(B, esign, ?INC_COL(S), [$e | Acc]); + _ -> + {{float, lists:reverse(Acc)}, S} + end; +tokenize_number(B, esign, S=#decoder{offset=O}, Acc) -> + case B of + <<_:O/binary, C, _/binary>> when C =:= $- orelse C=:= $+ -> + tokenize_number(B, eint, ?INC_COL(S), [C | Acc]); + _ -> + tokenize_number(B, eint, S, Acc) + end; +tokenize_number(B, eint, S=#decoder{offset=O}, Acc) -> + case B of + <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> + tokenize_number(B, eint1, ?INC_COL(S), [C | Acc]) + end; +tokenize_number(B, eint1, S=#decoder{offset=O}, Acc) -> + case B of + <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> + tokenize_number(B, eint1, ?INC_COL(S), [C | Acc]); + _ -> + {{float, lists:reverse(Acc)}, S} + end. + +tokenize(B, S=#decoder{offset=O}) -> + case B of + <<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) -> + tokenize(B, ?INC_CHAR(S, C)); + <<_:O/binary, "{", _/binary>> -> + {start_object, ?INC_COL(S)}; + <<_:O/binary, "}", _/binary>> -> + {end_object, ?INC_COL(S)}; + <<_:O/binary, "[", _/binary>> -> + {start_array, ?INC_COL(S)}; + <<_:O/binary, "]", _/binary>> -> + {end_array, ?INC_COL(S)}; + <<_:O/binary, ",", _/binary>> -> + {comma, ?INC_COL(S)}; + <<_:O/binary, ":", _/binary>> -> + {colon, ?INC_COL(S)}; + <<_:O/binary, "null", _/binary>> -> + {{const, null}, ?ADV_COL(S, 4)}; + <<_:O/binary, "true", _/binary>> -> + {{const, true}, ?ADV_COL(S, 4)}; + <<_:O/binary, "false", _/binary>> -> + {{const, false}, ?ADV_COL(S, 5)}; + <<_:O/binary, "\"", _/binary>> -> + tokenize_string(B, ?INC_COL(S)); + <<_:O/binary, C, _/binary>> when (C >= $0 andalso C =< $9) + orelse C =:= $- -> + tokenize_number(B, S); + <<_:O/binary>> -> + trim = S#decoder.state, + {eof, S} + end. +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + + +%% testing constructs borrowed from the Yaws JSON implementation. + +%% Create an object from a list of Key/Value pairs. + +obj_new() -> + {struct, []}. + +is_obj({struct, Props}) -> + F = fun ({K, _}) when is_binary(K) -> true end, + lists:all(F, Props). + +obj_from_list(Props) -> + Obj = {struct, Props}, + ?assert(is_obj(Obj)), + Obj. + +%% Test for equivalence of Erlang terms. +%% Due to arbitrary order of construction, equivalent objects might +%% compare unequal as erlang terms, so we need to carefully recurse +%% through aggregates (tuples and objects). + +equiv({struct, Props1}, {struct, Props2}) -> + equiv_object(Props1, Props2); +equiv(L1, L2) when is_list(L1), is_list(L2) -> + equiv_list(L1, L2); +equiv(N1, N2) when is_number(N1), is_number(N2) -> N1 == N2; +equiv(B1, B2) when is_binary(B1), is_binary(B2) -> B1 == B2; +equiv(A, A) when A =:= true orelse A =:= false orelse A =:= null -> true. + +%% Object representation and traversal order is unknown. +%% Use the sledgehammer and sort property lists. + +equiv_object(Props1, Props2) -> + L1 = lists:keysort(1, Props1), + L2 = lists:keysort(1, Props2), + Pairs = lists:zip(L1, L2), + true = lists:all(fun({{K1, V1}, {K2, V2}}) -> + equiv(K1, K2) and equiv(V1, V2) + end, Pairs). + +%% Recursively compare tuple elements for equivalence. + +equiv_list([], []) -> + true; +equiv_list([V1 | L1], [V2 | L2]) -> + equiv(V1, V2) andalso equiv_list(L1, L2). + +decode_test() -> + [1199344435545.0, 1] = decode(<<"[1199344435545.0,1]">>), + <<16#F0,16#9D,16#9C,16#95>> = decode([34,"\\ud835","\\udf15",34]). + +e2j_vec_test() -> + test_one(e2j_test_vec(utf8), 1). + +test_one([], _N) -> + %% io:format("~p tests passed~n", [N-1]), + ok; +test_one([{E, J} | Rest], N) -> + %% io:format("[~p] ~p ~p~n", [N, E, J]), + true = equiv(E, decode(J)), + true = equiv(E, decode(encode(E))), + test_one(Rest, 1+N). + +e2j_test_vec(utf8) -> + [ + {1, "1"}, + {3.1416, "3.14160"}, %% text representation may truncate, trail zeroes + {-1, "-1"}, + {-3.1416, "-3.14160"}, + {12.0e10, "1.20000e+11"}, + {1.234E+10, "1.23400e+10"}, + {-1.234E-10, "-1.23400e-10"}, + {10.0, "1.0e+01"}, + {123.456, "1.23456E+2"}, + {10.0, "1e1"}, + {<<"foo">>, "\"foo\""}, + {<<"foo", 5, "bar">>, "\"foo\\u0005bar\""}, + {<<"">>, "\"\""}, + {<<"\n\n\n">>, "\"\\n\\n\\n\""}, + {<<"\" \b\f\r\n\t\"">>, "\"\\\" \\b\\f\\r\\n\\t\\\"\""}, + {obj_new(), "{}"}, + {obj_from_list([{<<"foo">>, <<"bar">>}]), "{\"foo\":\"bar\"}"}, + {obj_from_list([{<<"foo">>, <<"bar">>}, {<<"baz">>, 123}]), + "{\"foo\":\"bar\",\"baz\":123}"}, + {[], "[]"}, + {[[]], "[[]]"}, + {[1, <<"foo">>], "[1,\"foo\"]"}, + + %% json array in a json object + {obj_from_list([{<<"foo">>, [123]}]), + "{\"foo\":[123]}"}, + + %% json object in a json object + {obj_from_list([{<<"foo">>, obj_from_list([{<<"bar">>, true}])}]), + "{\"foo\":{\"bar\":true}}"}, + + %% fold evaluation order + {obj_from_list([{<<"foo">>, []}, + {<<"bar">>, obj_from_list([{<<"baz">>, true}])}, + {<<"alice">>, <<"bob">>}]), + "{\"foo\":[],\"bar\":{\"baz\":true},\"alice\":\"bob\"}"}, + + %% json object in a json array + {[-123, <<"foo">>, obj_from_list([{<<"bar">>, []}]), null], + "[-123,\"foo\",{\"bar\":[]},null]"} + ]. + +%% test utf8 encoding +encoder_utf8_test() -> + %% safe conversion case (default) + [34,"\\u0001","\\u0442","\\u0435","\\u0441","\\u0442",34] = + encode(<<1,"\321\202\320\265\321\201\321\202">>), + + %% raw utf8 output (optional) + Enc = mochijson2:encoder([{utf8, true}]), + [34,"\\u0001",[209,130],[208,181],[209,129],[209,130],34] = + Enc(<<1,"\321\202\320\265\321\201\321\202">>). + +input_validation_test() -> + Good = [ + {16#00A3, <>}, %% pound + {16#20AC, <>}, %% euro + {16#10196, <>} %% denarius + ], + lists:foreach(fun({CodePoint, UTF8}) -> + Expect = list_to_binary(xmerl_ucs:to_utf8(CodePoint)), + Expect = decode(UTF8) + end, Good), + + Bad = [ + %% 2nd, 3rd, or 4th byte of a multi-byte sequence w/o leading byte + <>, + %% missing continuations, last byte in each should be 80-BF + <>, + <>, + <>, + %% we don't support code points > 10FFFF per RFC 3629 + <>, + %% escape characters trigger a different code path + <> + ], + lists:foreach( + fun(X) -> + ok = try decode(X) catch invalid_utf8 -> ok end, + %% could be {ucs,{bad_utf8_character_code}} or + %% {json_encode,{bad_char,_}} + {'EXIT', _} = (catch encode(X)) + end, Bad). + +inline_json_test() -> + ?assertEqual(<<"\"iodata iodata\"">>, + iolist_to_binary( + encode({json, [<<"\"iodata">>, " iodata\""]}))), + ?assertEqual({struct, [{<<"key">>, <<"iodata iodata">>}]}, + decode( + encode({struct, + [{key, {json, [<<"\"iodata">>, " iodata\""]}}]}))), + ok. + +big_unicode_test() -> + UTF8Seq = list_to_binary(xmerl_ucs:to_utf8(16#0001d120)), + ?assertEqual( + <<"\"\\ud834\\udd20\"">>, + iolist_to_binary(encode(UTF8Seq))), + ?assertEqual( + UTF8Seq, + decode(iolist_to_binary(encode(UTF8Seq)))), + ok. + +custom_decoder_test() -> + ?assertEqual( + {struct, [{<<"key">>, <<"value">>}]}, + (decoder([]))("{\"key\": \"value\"}")), + F = fun ({struct, [{<<"key">>, <<"value">>}]}) -> win end, + ?assertEqual( + win, + (decoder([{object_hook, F}]))("{\"key\": \"value\"}")), + ok. + +atom_test() -> + %% JSON native atoms + [begin + ?assertEqual(A, decode(atom_to_list(A))), + ?assertEqual(iolist_to_binary(atom_to_list(A)), + iolist_to_binary(encode(A))) + end || A <- [true, false, null]], + %% Atom to string + ?assertEqual( + <<"\"foo\"">>, + iolist_to_binary(encode(foo))), + ?assertEqual( + <<"\"\\ud834\\udd20\"">>, + iolist_to_binary(encode(list_to_atom(xmerl_ucs:to_utf8(16#0001d120))))), + ok. + +key_encode_test() -> + %% Some forms are accepted as keys that would not be strings in other + %% cases + ?assertEqual( + <<"{\"foo\":1}">>, + iolist_to_binary(encode({struct, [{foo, 1}]}))), + ?assertEqual( + <<"{\"foo\":1}">>, + iolist_to_binary(encode({struct, [{<<"foo">>, 1}]}))), + ?assertEqual( + <<"{\"foo\":1}">>, + iolist_to_binary(encode({struct, [{"foo", 1}]}))), + ?assertEqual( + <<"{\"foo\":1}">>, + iolist_to_binary(encode([{foo, 1}]))), + ?assertEqual( + <<"{\"foo\":1}">>, + iolist_to_binary(encode([{<<"foo">>, 1}]))), + ?assertEqual( + <<"{\"foo\":1}">>, + iolist_to_binary(encode([{"foo", 1}]))), + ?assertEqual( + <<"{\"\\ud834\\udd20\":1}">>, + iolist_to_binary( + encode({struct, [{[16#0001d120], 1}]}))), + ?assertEqual( + <<"{\"1\":1}">>, + iolist_to_binary(encode({struct, [{1, 1}]}))), + ok. + +unsafe_chars_test() -> + Chars = "\"\\\b\f\n\r\t", + [begin + ?assertEqual(false, json_string_is_safe([C])), + ?assertEqual(false, json_bin_is_safe(<>)), + ?assertEqual(<>, decode(encode(<>))) + end || C <- Chars], + ?assertEqual( + false, + json_string_is_safe([16#0001d120])), + ?assertEqual( + false, + json_bin_is_safe(list_to_binary(xmerl_ucs:to_utf8(16#0001d120)))), + ?assertEqual( + [16#0001d120], + xmerl_ucs:from_utf8( + binary_to_list( + decode(encode(list_to_atom(xmerl_ucs:to_utf8(16#0001d120))))))), + ?assertEqual( + false, + json_string_is_safe([16#110000])), + ?assertEqual( + false, + json_bin_is_safe(list_to_binary(xmerl_ucs:to_utf8([16#110000])))), + %% solidus can be escaped but isn't unsafe by default + ?assertEqual( + <<"/">>, + decode(<<"\"\\/\"">>)), + ok. + +int_test() -> + ?assertEqual(0, decode("0")), + ?assertEqual(1, decode("1")), + ?assertEqual(11, decode("11")), + ok. + +large_int_test() -> + ?assertEqual(<<"-2147483649214748364921474836492147483649">>, + iolist_to_binary(encode(-2147483649214748364921474836492147483649))), + ?assertEqual(<<"2147483649214748364921474836492147483649">>, + iolist_to_binary(encode(2147483649214748364921474836492147483649))), + ok. + +float_test() -> + ?assertEqual(<<"-2147483649.0">>, iolist_to_binary(encode(-2147483649.0))), + ?assertEqual(<<"2147483648.0">>, iolist_to_binary(encode(2147483648.0))), + ok. + +handler_test() -> + ?assertEqual( + {'EXIT',{json_encode,{bad_term,{x,y}}}}, + catch encode({x,y})), + F = fun ({x,y}) -> [] end, + ?assertEqual( + <<"[]">>, + iolist_to_binary((encoder([{handler, F}]))({x, y}))), + ok. + +encode_empty_test_() -> + [{A, ?_assertEqual(<<"{}">>, iolist_to_binary(encode(B)))} + || {A, B} <- [{"eep18 {}", {}}, + {"eep18 {[]}", {[]}}, + {"{struct, []}", {struct, []}}]]. + +encode_test_() -> + P = [{<<"k">>, <<"v">>}], + JSON = iolist_to_binary(encode({struct, P})), + [{atom_to_list(F), + ?_assertEqual(JSON, iolist_to_binary(encode(decode(JSON, [{format, F}]))))} + || F <- [struct, eep18, proplist]]. + +format_test_() -> + P = [{<<"k">>, <<"v">>}], + JSON = iolist_to_binary(encode({struct, P})), + [{atom_to_list(F), + ?_assertEqual(A, decode(JSON, [{format, F}]))} + || {F, A} <- [{struct, {struct, P}}, + {eep18, {P}}, + {proplist, P}]]. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/mochinum_fork.erl b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/mochinum_fork.erl new file mode 100644 index 0000000..ea22766 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/mochinum_fork.erl @@ -0,0 +1,354 @@ +%% @copyright 2007 Mochi Media, Inc. +%% @author Bob Ippolito + +%% @doc Useful numeric algorithms for floats that cover some deficiencies +%% in the math module. More interesting is digits/1, which implements +%% the algorithm from: +%% http://www.cs.indiana.edu/~burger/fp/index.html +%% See also "Printing Floating-Point Numbers Quickly and Accurately" +%% in Proceedings of the SIGPLAN '96 Conference on Programming Language +%% Design and Implementation. + +-module(mochinum_fork). +-author("Bob Ippolito "). +-export([digits/1, frexp/1, int_pow/2, int_ceil/1]). + +%% IEEE 754 Float exponent bias +-define(FLOAT_BIAS, 1022). +-define(MIN_EXP, -1074). +-define(BIG_POW, 4503599627370496). + +%% External API + +%% @spec digits(number()) -> string() +%% @doc Returns a string that accurately represents the given integer or float +%% using a conservative amount of digits. Great for generating +%% human-readable output, or compact ASCII serializations for floats. +digits(N) when is_integer(N) -> + integer_to_list(N); +digits(0.0) -> + "0.0"; +digits(Float) -> + {Frac1, Exp1} = frexp_int(Float), + [Place0 | Digits0] = digits1(Float, Exp1, Frac1), + {Place, Digits} = transform_digits(Place0, Digits0), + R = insert_decimal(Place, Digits), + case Float < 0 of + true -> + [$- | R]; + _ -> + R + end. + +%% @spec frexp(F::float()) -> {Frac::float(), Exp::float()} +%% @doc Return the fractional and exponent part of an IEEE 754 double, +%% equivalent to the libc function of the same name. +%% F = Frac * pow(2, Exp). +frexp(F) -> + frexp1(unpack(F)). + +%% @spec int_pow(X::integer(), N::integer()) -> Y::integer() +%% @doc Moderately efficient way to exponentiate integers. +%% int_pow(10, 2) = 100. +int_pow(_X, 0) -> + 1; +int_pow(X, N) when N > 0 -> + int_pow(X, N, 1). + +%% @spec int_ceil(F::float()) -> integer() +%% @doc Return the ceiling of F as an integer. The ceiling is defined as +%% F when F == trunc(F); +%% trunc(F) when F < 0; +%% trunc(F) + 1 when F > 0. +int_ceil(X) -> + T = trunc(X), + case (X - T) of + Pos when Pos > 0 -> T + 1; + _ -> T + end. + + +%% Internal API + +int_pow(X, N, R) when N < 2 -> + R * X; +int_pow(X, N, R) -> + int_pow(X * X, N bsr 1, case N band 1 of 1 -> R * X; 0 -> R end). + +insert_decimal(0, S) -> + "0." ++ S; +insert_decimal(Place, S) when Place > 0 -> + L = length(S), + case Place - L of + 0 -> + S ++ ".0"; + N when N < 0 -> + {S0, S1} = lists:split(L + N, S), + S0 ++ "." ++ S1; + N when N < 6 -> + %% More places than digits + S ++ lists:duplicate(N, $0) ++ ".0"; + _ -> + insert_decimal_exp(Place, S) + end; +insert_decimal(Place, S) when Place > -6 -> + "0." ++ lists:duplicate(abs(Place), $0) ++ S; +insert_decimal(Place, S) -> + insert_decimal_exp(Place, S). + +insert_decimal_exp(Place, S) -> + [C | S0] = S, + S1 = case S0 of + [] -> + "0"; + _ -> + S0 + end, + Exp = case Place < 0 of + true -> + "e-"; + false -> + "e+" + end, + [C] ++ "." ++ S1 ++ Exp ++ integer_to_list(abs(Place - 1)). + + +digits1(Float, Exp, Frac) -> + Round = ((Frac band 1) =:= 0), + case Exp >= 0 of + true -> + BExp = 1 bsl Exp, + case (Frac =/= ?BIG_POW) of + true -> + scale((Frac * BExp * 2), 2, BExp, BExp, + Round, Round, Float); + false -> + scale((Frac * BExp * 4), 4, (BExp * 2), BExp, + Round, Round, Float) + end; + false -> + case (Exp =:= ?MIN_EXP) orelse (Frac =/= ?BIG_POW) of + true -> + scale((Frac * 2), 1 bsl (1 - Exp), 1, 1, + Round, Round, Float); + false -> + scale((Frac * 4), 1 bsl (2 - Exp), 2, 1, + Round, Round, Float) + end + end. + +scale(R, S, MPlus, MMinus, LowOk, HighOk, Float) -> + Est = int_ceil(math:log10(abs(Float)) - 1.0e-10), + %% Note that the scheme implementation uses a 326 element look-up table + %% for int_pow(10, N) where we do not. + case Est >= 0 of + true -> + fixup(R, S * int_pow(10, Est), MPlus, MMinus, Est, + LowOk, HighOk); + false -> + Scale = int_pow(10, -Est), + fixup(R * Scale, S, MPlus * Scale, MMinus * Scale, Est, + LowOk, HighOk) + end. + +fixup(R, S, MPlus, MMinus, K, LowOk, HighOk) -> + TooLow = case HighOk of + true -> + (R + MPlus) >= S; + false -> + (R + MPlus) > S + end, + case TooLow of + true -> + [(K + 1) | generate(R, S, MPlus, MMinus, LowOk, HighOk)]; + false -> + [K | generate(R * 10, S, MPlus * 10, MMinus * 10, LowOk, HighOk)] + end. + +generate(R0, S, MPlus, MMinus, LowOk, HighOk) -> + D = R0 div S, + R = R0 rem S, + TC1 = case LowOk of + true -> + R =< MMinus; + false -> + R < MMinus + end, + TC2 = case HighOk of + true -> + (R + MPlus) >= S; + false -> + (R + MPlus) > S + end, + case TC1 of + false -> + case TC2 of + false -> + [D | generate(R * 10, S, MPlus * 10, MMinus * 10, + LowOk, HighOk)]; + true -> + [D + 1] + end; + true -> + case TC2 of + false -> + [D]; + true -> + case R * 2 < S of + true -> + [D]; + false -> + [D + 1] + end + end + end. + +unpack(Float) -> + <> = <>, + {Sign, Exp, Frac}. + +frexp1({_Sign, 0, 0}) -> + {0.0, 0}; +frexp1({Sign, 0, Frac}) -> + Exp = log2floor(Frac), + <> = <>, + {Frac1, -(?FLOAT_BIAS) - 52 + Exp}; +frexp1({Sign, Exp, Frac}) -> + <> = <>, + {Frac1, Exp - ?FLOAT_BIAS}. + +log2floor(Int) -> + log2floor(Int, 0). + +log2floor(0, N) -> + N; +log2floor(Int, N) -> + log2floor(Int bsr 1, 1 + N). + + +transform_digits(Place, [0 | Rest]) -> + transform_digits(Place, Rest); +transform_digits(Place, Digits) -> + {Place, [$0 + D || D <- Digits]}. + + +frexp_int(F) -> + case unpack(F) of + {_Sign, 0, Frac} -> + {Frac, ?MIN_EXP}; + {_Sign, Exp, Frac} -> + {Frac + (1 bsl 52), Exp - 53 - ?FLOAT_BIAS} + end. + +%% +%% Tests +%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +int_ceil_test() -> + ?assertEqual(1, int_ceil(0.0001)), + ?assertEqual(0, int_ceil(0.0)), + ?assertEqual(1, int_ceil(0.99)), + ?assertEqual(1, int_ceil(1.0)), + ?assertEqual(-1, int_ceil(-1.5)), + ?assertEqual(-2, int_ceil(-2.0)), + ok. + +int_pow_test() -> + ?assertEqual(1, int_pow(1, 1)), + ?assertEqual(1, int_pow(1, 0)), + ?assertEqual(1, int_pow(10, 0)), + ?assertEqual(10, int_pow(10, 1)), + ?assertEqual(100, int_pow(10, 2)), + ?assertEqual(1000, int_pow(10, 3)), + ok. + +digits_test() -> + ?assertEqual("0", + digits(0)), + ?assertEqual("0.0", + digits(0.0)), + ?assertEqual("1.0", + digits(1.0)), + ?assertEqual("-1.0", + digits(-1.0)), + ?assertEqual("0.1", + digits(0.1)), + ?assertEqual("0.01", + digits(0.01)), + ?assertEqual("0.001", + digits(0.001)), + ?assertEqual("1.0e+6", + digits(1000000.0)), + ?assertEqual("0.5", + digits(0.5)), + ?assertEqual("4503599627370496.0", + digits(4503599627370496.0)), + %% small denormalized number + %% 4.94065645841246544177e-324 =:= 5.0e-324 + <> = <<0,0,0,0,0,0,0,1>>, + ?assertEqual("5.0e-324", + digits(SmallDenorm)), + ?assertEqual(SmallDenorm, + list_to_float(digits(SmallDenorm))), + %% large denormalized number + %% 2.22507385850720088902e-308 + <> = <<0,15,255,255,255,255,255,255>>, + ?assertEqual("2.225073858507201e-308", + digits(BigDenorm)), + ?assertEqual(BigDenorm, + list_to_float(digits(BigDenorm))), + %% small normalized number + %% 2.22507385850720138309e-308 + <> = <<0,16,0,0,0,0,0,0>>, + ?assertEqual("2.2250738585072014e-308", + digits(SmallNorm)), + ?assertEqual(SmallNorm, + list_to_float(digits(SmallNorm))), + %% large normalized number + %% 1.79769313486231570815e+308 + <> = <<127,239,255,255,255,255,255,255>>, + ?assertEqual("1.7976931348623157e+308", + digits(LargeNorm)), + ?assertEqual(LargeNorm, + list_to_float(digits(LargeNorm))), + %% issue #10 - mochinum:frexp(math:pow(2, -1074)). + ?assertEqual("5.0e-324", + digits(math:pow(2, -1074))), + ok. + +frexp_test() -> + %% zero + ?assertEqual({0.0, 0}, frexp(0.0)), + %% one + ?assertEqual({0.5, 1}, frexp(1.0)), + %% negative one + ?assertEqual({-0.5, 1}, frexp(-1.0)), + %% small denormalized number + %% 4.94065645841246544177e-324 + <> = <<0,0,0,0,0,0,0,1>>, + ?assertEqual({0.5, -1073}, frexp(SmallDenorm)), + %% large denormalized number + %% 2.22507385850720088902e-308 + <> = <<0,15,255,255,255,255,255,255>>, + ?assertEqual( + {0.99999999999999978, -1022}, + frexp(BigDenorm)), + %% small normalized number + %% 2.22507385850720138309e-308 + <> = <<0,16,0,0,0,0,0,0>>, + ?assertEqual({0.5, -1021}, frexp(SmallNorm)), + %% large normalized number + %% 1.79769313486231570815e+308 + <> = <<127,239,255,255,255,255,255,255>>, + ?assertEqual( + {0.99999999999999989, 1024}, + frexp(LargeNorm)), + %% issue #10 - mochinum:frexp(math:pow(2, -1074)). + ?assertEqual( + {0.5, -1073}, + frexp(math:pow(2, -1074))), + ok. + +-endif. diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/pmod_pt.erl b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/pmod_pt.erl new file mode 100644 index 0000000..db21974 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/pmod_pt.erl @@ -0,0 +1,461 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(pmod_pt). +-export([parse_transform/2, + format_error/1]). + +%% Expand function definition forms of parameterized module. +%% The code is based on the code in sys_expand_pmod which used to be +%% included in the compiler, but details are different because +%% sys_pre_expand has not been run. In particular: +%% +%% * Record definitions are still present and must be handled. +%% +%% * (Syntatic) local calls may actually be calls to an imported +%% funtion or a BIF. It is a local call if and only if there +%% is a definition for the function in the module. +%% +%% * When we introduce the module parameters and 'THIS' in each +%% function, we must artificially use it to avoid a warning for +%% unused variables. +%% +%% * On the other hand, we don't have to worry about module_info/0,1 +%% because they have not been added yet. + +-record(pmod, {parameters, + defined + }). + +parse_transform(Forms0, _Options) -> + put(?MODULE, []), + Forms = transform(Forms0), + case erase(?MODULE) of + [] -> + Forms; + [_|_]=Errors -> + File = get_file(Forms), + {error,[{File,Errors}],[]} + end. + +format_error(extends_self) -> + "cannot extend from self"; +format_error(define_instance) -> + "defining instance function not allowed in parameterized module". + +add_error(Line, Error) -> + put(?MODULE, get(?MODULE) ++ [{Line,?MODULE,Error}]). + +get_file([{attribute,_,file,{File,_}}|_]) -> File; +get_file([_|T]) -> get_file(T). + +transform(Forms0) -> + Def = collect_defined(Forms0), + {Base,ModAs,Forms1} = attribs(Forms0, [], undefined, []), + {Mod,Ps0} = case ModAs of + {M0,P0} -> {M0,P0}; + M0 -> {M0,undefined} + end, + Forms2 = case Ps0 of + undefined -> + Forms1; + _ -> + pmod_expand(Forms1, Mod, Base, Ps0, Def) + end, + + %% Add new functions. + NewFs0 = maybe_extend(Base, Mod, Ps0), + NewExps = collect_defined(NewFs0), + Forms3 = add_attributes(Forms2, [{attribute,0,export,NewExps}]), + add_new_funcs(Forms3, NewFs0). + +pmod_expand(Forms0, Mod, Base, Ps0, Def) -> + Ps = if is_atom(Base) -> + ['BASE' | Ps0]; + true -> + Ps0 + end, + St0 = #pmod{parameters=Ps,defined=gb_sets:from_list(Def)}, + {Forms1,_} = forms(Forms0, St0), + Forms2 = update_exps(Forms1), + Forms3 = update_forms(Forms2), + NewFs0 = add_instance(Mod, Ps, []), + NewFs = ensure_new(Base, Ps0, NewFs0), + Forms = add_new_funcs(Forms3, NewFs), + NewExps = collect_defined(NewFs), + add_attributes(Forms, [{attribute,0,export,NewExps}]). + +add_attributes([{attribute,_,module,_}=F|Fs], Attrs) -> + [F|Attrs++Fs]; +add_attributes([F|Fs], Attrs) -> + [F|add_attributes(Fs, Attrs)]. + +add_new_funcs([{eof,_}|_]=Fs, NewFs) -> + NewFs ++ Fs; +add_new_funcs([F|Fs], Es) -> + [F|add_new_funcs(Fs, Es)]. + +maybe_extend([], _, _) -> + %% No 'extends' attribute. + []; +maybe_extend(Base, _Mod, undefined) -> + %% There is a an 'extends' attribute; the module is not parameterized. + Name = '$handle_undefined_function', + Args = [{var,0,'Func'},{var,0,'Args'}], + Body = [make_apply({atom,0,Base}, {var,0,'Func'}, {var,0,'Args'})], + F = {function,0,Name,2,[{clause,0,Args,[],Body}]}, + [F]; +maybe_extend(Base, Mod, Ps) -> + %% There is a an 'extends' attribute; the module is parameterized. + Name = '$handle_undefined_function', + Args = [{var,0,'Func'},{var,0,'Args'}], + DontCares = [{var,0,'_'} || _ <- Ps], + TuplePs = {tuple,0,[{atom,0,Mod},{var,0,'BaseVars'}|DontCares]}, + G = [{call,0,{atom,0,is_atom}, + [{call,0,{atom,0,element}, + [{integer,0,1},{var,0,'BaseVars'}]}]}], + FixedArgs = make_lists_rev([{var,0,'Rs'}, + {cons,0,{var,0,'BaseVars'},{nil,0}}]), + Body = [{'case',0,make_lists_rev([{var,0,'Args'}]), + [{clause,0,[{cons,0,TuplePs,{var,0,'Rs'}}],[G], + [make_apply({atom,0,Base}, {var,0,'Func'}, FixedArgs)]}, + {clause,0,[{var,0,'_'}],[], + [make_apply({atom,0,Base}, {var,0,'Func'}, {var,0,'Args'})]} + ]}], + F = {function,0,Name,2,[{clause,0,Args,[],Body}]}, + [F]. + +make_apply(M, F, A) -> + {call,0,{remote,0,{atom,0,erlang},{atom,0,apply}},[M,F,A]}. + +make_lists_rev(As) -> + {call,0,{remote,0,{atom,0,lists},{atom,0,reverse}},As}. + +ensure_new(Base, Ps, Fs) -> + case has_new(Fs) of + true -> + Fs; + false -> + add_new(Base, Ps, Fs) + end. + +has_new([{function,_L,new,_A,_Cs} | _Fs]) -> + true; +has_new([_ | Fs]) -> + has_new(Fs); +has_new([]) -> + false. + +add_new(Base, Ps, Fs) -> + Vs = [{var,0,V} || V <- Ps], + As = if is_atom(Base) -> + [{call,0,{remote,0,{atom,0,Base},{atom,0,new}},Vs} | Vs]; + true -> + Vs + end, + Body = [{call,0,{atom,0,instance},As}], + add_func(new, Vs, Body, Fs). + +add_instance(Mod, Ps, Fs) -> + Vs = [{var,0,V} || V <- Ps], + AbsMod = [{tuple,0,[{atom,0,Mod}|Vs]}], + add_func(instance, Vs, AbsMod, Fs). + +add_func(Name, Args, Body, Fs) -> + A = length(Args), + F = {function,0,Name,A,[{clause,0,Args,[],Body}]}, + [F|Fs]. + +collect_defined(Fs) -> + [{N,A} || {function,_,N,A,_} <- Fs]. + +attribs([{attribute,Line,module,{Mod,_}=ModAs}|T], Base, _, Acc) -> + attribs(T, Base, ModAs, [{attribute,Line,module,Mod}|Acc]); +attribs([{attribute,_,module,Mod}=H|T], Base, _, Acc) -> + attribs(T, Base, Mod, [H|Acc]); +attribs([{attribute,Line,extends,Base}|T], Base0, Ps, Acc) when is_atom(Base) -> + Mod = case Ps of + {Mod0,_} -> Mod0; + Mod0 -> Mod0 + end, + case Mod of + Base -> + add_error(Line, extends_self), + attribs(T, Base0, Ps, Acc); + _ -> + attribs(T, Base, Ps, Acc) + end; +attribs([H|T], Base, Ps, Acc) -> + attribs(T, Base, Ps, [H|Acc]); +attribs([], Base, Ps, Acc) -> + {Base,Ps,lists:reverse(Acc)}. + +%% This is extremely simplistic for now; all functions get an extra +%% parameter, whether they need it or not, except for static functions. + +update_function_name({F,A}) when F =/= new -> + {F,A+1}; +update_function_name(E) -> + E. + +update_forms([{function,L,N,A,Cs}|Fs]) when N =/= new -> + [{function,L,N,A+1,Cs}|update_forms(Fs)]; +update_forms([F|Fs]) -> + [F|update_forms(Fs)]; +update_forms([]) -> + []. + +update_exps([{attribute,Line,export,Es0}|T]) -> + Es = [update_function_name(E) || E <- Es0], + [{attribute,Line,export,Es}|update_exps(T)]; +update_exps([H|T]) -> + [H|update_exps(T)]; +update_exps([]) -> + []. + +%% Process the program forms. + +forms([F0|Fs0],St0) -> + {F1,St1} = form(F0,St0), + {Fs1,St2} = forms(Fs0,St1), + {[F1|Fs1],St2}; +forms([], St0) -> + {[], St0}. + +%% Only function definitions are of interest here. State is not updated. +form({function,Line,instance,_Arity,_Clauses}=F,St) -> + add_error(Line, define_instance), + {F,St}; +form({function,Line,Name0,Arity0,Clauses0},St) when Name0 =/= new -> + {Name,Arity,Clauses} = function(Name0, Arity0, Clauses0, St), + {{function,Line,Name,Arity,Clauses},St}; +%% Pass anything else through +form(F,St) -> {F,St}. + +function(Name, Arity, Clauses0, St) -> + Clauses1 = clauses(Clauses0,St), + {Name,Arity,Clauses1}. + +clauses([C|Cs],#pmod{parameters=Ps}=St) -> + {clause,L,H,G,B0} = clause(C,St), + T = {tuple,L,[{var,L,V} || V <- ['_'|Ps]]}, + B = [{match,L,{var,L,'_'},{var,L,V}} || V <- ['THIS'|Ps]] ++ B0, + [{clause,L,H++[{match,L,T,{var,L,'THIS'}}],G,B}|clauses(Cs,St)]; +clauses([],_St) -> []. + +clause({clause,Line,H,G,B0},St) -> + %% We never update H and G, so we will just copy them. + B1 = exprs(B0,St), + {clause,Line,H,G,B1}. + +pattern_grp([{bin_element,L1,E1,S1,T1} | Fs],St) -> + S2 = case S1 of + default -> + default; + _ -> + expr(S1,St) + end, + T2 = case T1 of + default -> + default; + _ -> + bit_types(T1) + end, + [{bin_element,L1,expr(E1,St),S2,T2} | pattern_grp(Fs,St)]; +pattern_grp([],_St) -> + []. + +bit_types([]) -> + []; +bit_types([Atom | Rest]) when is_atom(Atom) -> + [Atom | bit_types(Rest)]; +bit_types([{Atom, Integer} | Rest]) when is_atom(Atom), is_integer(Integer) -> + [{Atom, Integer} | bit_types(Rest)]. + +exprs([E0|Es],St) -> + E1 = expr(E0,St), + [E1|exprs(Es,St)]; +exprs([],_St) -> []. + +expr({var,_L,_V}=Var,_St) -> + Var; +expr({integer,_Line,_I}=Integer,_St) -> Integer; +expr({float,_Line,_F}=Float,_St) -> Float; +expr({atom,_Line,_A}=Atom,_St) -> Atom; +expr({string,_Line,_S}=String,_St) -> String; +expr({char,_Line,_C}=Char,_St) -> Char; +expr({nil,_Line}=Nil,_St) -> Nil; +expr({cons,Line,H0,T0},St) -> + H1 = expr(H0,St), + T1 = expr(T0,St), + {cons,Line,H1,T1}; +expr({lc,Line,E0,Qs0},St) -> + Qs1 = lc_bc_quals(Qs0,St), + E1 = expr(E0,St), + {lc,Line,E1,Qs1}; +expr({bc,Line,E0,Qs0},St) -> + Qs1 = lc_bc_quals(Qs0,St), + E1 = expr(E0,St), + {bc,Line,E1,Qs1}; +expr({tuple,Line,Es0},St) -> + Es1 = expr_list(Es0,St), + {tuple,Line,Es1}; +expr({record,Line,Name,Is0},St) -> + Is = record_fields(Is0,St), + {record,Line,Name,Is}; +expr({record,Line,E0,Name,Is0},St) -> + E = expr(E0,St), + Is = record_fields(Is0,St), + {record,Line,E,Name,Is}; +expr({record_field,Line,E0,Name,Key},St) -> + E = expr(E0,St), + {record_field,Line,E,Name,Key}; +expr({block,Line,Es0},St) -> + Es1 = exprs(Es0,St), + {block,Line,Es1}; +expr({'if',Line,Cs0},St) -> + Cs1 = icr_clauses(Cs0,St), + {'if',Line,Cs1}; +expr({'case',Line,E0,Cs0},St) -> + E1 = expr(E0,St), + Cs1 = icr_clauses(Cs0,St), + {'case',Line,E1,Cs1}; +expr({'receive',Line,Cs0},St) -> + Cs1 = icr_clauses(Cs0,St), + {'receive',Line,Cs1}; +expr({'receive',Line,Cs0,To0,ToEs0},St) -> + To1 = expr(To0,St), + ToEs1 = exprs(ToEs0,St), + Cs1 = icr_clauses(Cs0,St), + {'receive',Line,Cs1,To1,ToEs1}; +expr({'try',Line,Es0,Scs0,Ccs0,As0},St) -> + Es1 = exprs(Es0,St), + Scs1 = icr_clauses(Scs0,St), + Ccs1 = icr_clauses(Ccs0,St), + As1 = exprs(As0,St), + {'try',Line,Es1,Scs1,Ccs1,As1}; +expr({'fun',_,{function,_,_,_}}=ExtFun,_St) -> + ExtFun; +expr({'fun',Line,Body},St) -> + case Body of + {clauses,Cs0} -> + Cs1 = fun_clauses(Cs0,St), + {'fun',Line,{clauses,Cs1}}; + {function,F,A} = Function -> + {F1,A1} = update_function_name({F,A}), + if A1 =:= A -> + {'fun',Line,Function}; + true -> + %% Must rewrite local fun-name to a fun that does a + %% call with the extra THIS parameter. + As = make_vars(A, Line), + As1 = As ++ [{var,Line,'THIS'}], + Call = {call,Line,{atom,Line,F1},As1}, + Cs = [{clause,Line,As,[],[Call]}], + {'fun',Line,{clauses,Cs}} + end; + {function,_M,_F,_A} = Fun4 -> %This is an error in lint! + {'fun',Line,Fun4} + end; +expr({call,Lc,{atom,_,instance}=Name,As0},St) -> + %% All local functions 'instance(...)' are static by definition, + %% so they do not take a 'THIS' argument when called + As1 = expr_list(As0,St), + {call,Lc,Name,As1}; +expr({call,Lc,{atom,_,new}=Name,As0},St) -> + %% All local functions 'new(...)' are static by definition, + %% so they do not take a 'THIS' argument when called + As1 = expr_list(As0,St), + {call,Lc,Name,As1}; +expr({call,Lc,{atom,_Lf,F}=Atom,As0}, #pmod{defined=Def}=St) -> + As1 = expr_list(As0,St), + case gb_sets:is_member({F,length(As0)}, Def) of + false -> + %% BIF or imported function. + {call,Lc,Atom,As1}; + true -> + %% Local function call - needs THIS parameter. + {call,Lc,Atom,As1 ++ [{var,0,'THIS'}]} + end; +expr({call,Line,F0,As0},St) -> + %% Other function call + F1 = expr(F0,St), + As1 = expr_list(As0,St), + {call,Line,F1,As1}; +expr({'catch',Line,E0},St) -> + E1 = expr(E0,St), + {'catch',Line,E1}; +expr({match,Line,P,E0},St) -> + E1 = expr(E0,St), + {match,Line,P,E1}; +expr({bin,Line,Fs},St) -> + Fs2 = pattern_grp(Fs,St), + {bin,Line,Fs2}; +expr({op,Line,Op,A0},St) -> + A1 = expr(A0,St), + {op,Line,Op,A1}; +expr({op,Line,Op,L0,R0},St) -> + L1 = expr(L0,St), + R1 = expr(R0,St), + {op,Line,Op,L1,R1}; +%% The following are not allowed to occur anywhere! +expr({remote,Line,M0,F0},St) -> + M1 = expr(M0,St), + F1 = expr(F0,St), + {remote,Line,M1,F1}. + +expr_list([E0|Es],St) -> + E1 = expr(E0,St), + [E1|expr_list(Es,St)]; +expr_list([],_St) -> []. + +record_fields([{record_field,L,K,E0}|T],St) -> + E = expr(E0,St), + [{record_field,L,K,E}|record_fields(T,St)]; +record_fields([],_) -> []. + +icr_clauses([C0|Cs],St) -> + C1 = clause(C0,St), + [C1|icr_clauses(Cs,St)]; +icr_clauses([],_St) -> []. + +lc_bc_quals([{generate,Line,P,E0}|Qs],St) -> + E1 = expr(E0,St), + [{generate,Line,P,E1}|lc_bc_quals(Qs,St)]; +lc_bc_quals([{b_generate,Line,P,E0}|Qs],St) -> + E1 = expr(E0,St), + [{b_generate,Line,P,E1}|lc_bc_quals(Qs,St)]; +lc_bc_quals([E0|Qs],St) -> + E1 = expr(E0,St), + [E1|lc_bc_quals(Qs,St)]; +lc_bc_quals([],_St) -> []. + +fun_clauses([C0|Cs],St) -> + C1 = clause(C0,St), + [C1|fun_clauses(Cs,St)]; +fun_clauses([],_St) -> []. + +make_vars(N, L) -> + make_vars(1, N, L). + +make_vars(N, M, L) when N =< M -> + V = list_to_atom("X"++integer_to_list(N)), + [{var,L,V} | make_vars(N + 1, M, L)]; +make_vars(_, _, _) -> + []. diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs.app.src b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs.app.src new file mode 100644 index 0000000..4e21412 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs.app.src @@ -0,0 +1,12 @@ +{application, sockjs, + [ + {description, "SockJS"}, + {vsn, "0.3.4"}, + {modules, []}, + {registered, []}, + {applications, [ + kernel, + stdlib + ]}, + {mod, { sockjs_app, []}} + ]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs.erl b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs.erl new file mode 100644 index 0000000..98b1173 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs.erl @@ -0,0 +1,24 @@ +-module(sockjs). + +-export([send/2, close/1, close/3, info/1]). + +%% -type(conn() :: {sockjs_session, any()}). + +%% Send data over a connection. +%% -spec send(iodata(), conn()) -> ok. +send(Data, Conn = {sockjs_session, _}) -> + sockjs_session:send(Data, Conn). + +%% Initiate a close of a connection. +%% -spec close(conn()) -> ok. +close(Conn) -> + close(1000, "Normal closure", Conn). + +%% -spec close(non_neg_integer(), string(), conn()) -> ok. +close(Code, Reason, Conn = {sockjs_session, _}) -> + sockjs_session:close(Code, Reason, Conn). + +%% -spec info(conn()) -> [{atom(), any()}]. +info(Conn = {sockjs_session, _}) -> + sockjs_session:info(Conn). + diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_action.erl b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_action.erl new file mode 100644 index 0000000..4310963 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_action.erl @@ -0,0 +1,279 @@ +-module(sockjs_action). + +% none +-export([welcome_screen/3, options/3, iframe/3, info_test/3]). +% send +-export([xhr_polling/4, xhr_streaming/4, eventsource/4, htmlfile/4, jsonp/4]). +% recv +-export([xhr_send/4, jsonp_send/4]). +% misc +-export([websocket/3, rawwebsocket/3]). + +-include("sockjs_internal.hrl"). + +%% -------------------------------------------------------------------------- + +-define(IFRAME, " + + + + + + + + +

    Don't panic!

    +

    This is a SockJS hidden iframe. It's used for cross domain magic.

    + +"). + +-define(IFRAME_HTMLFILE, " + + + +

    Don't panic!

    + "). + +%% -------------------------------------------------------------------------- + +%% -spec welcome_screen(req(), headers(), service()) -> req(). +welcome_screen(Req, Headers, _Service) -> + H = [{"Content-Type", "text/plain; charset=UTF-8"}], + sockjs_http:reply(200, H ++ Headers, + "Welcome to SockJS!\n", Req). + +%% -spec options(req(), headers(), service()) -> req(). +options(Req, Headers, _Service) -> + sockjs_http:reply(204, Headers, "", Req). + +%% -spec iframe(req(), headers(), service()) -> req(). +iframe(Req, Headers, #service{sockjs_url = SockjsUrl}) -> + IFrame = io_lib:format(?IFRAME, [SockjsUrl]), + MD5 = "\"" ++ binary_to_list(base64:encode(erlang:md5(IFrame))) ++ "\"", + {H, Req2} = sockjs_http:header('If-None-Match', Req), + case H of + MD5 -> sockjs_http:reply(304, Headers, "", Req2); + _ -> sockjs_http:reply( + 200, [{"Content-Type", "text/html; charset=UTF-8"}, + {"ETag", MD5}] ++ Headers, IFrame, Req2) + end. + + +%% -spec info_test(req(), headers(), service()) -> req(). +info_test(Req, Headers, #service{websocket = Websocket, + cookie_needed = CookieNeeded}) -> + I = [{websocket, Websocket}, + {cookie_needed, CookieNeeded}, + {origins, [<<"*:*">>]}, + {entropy, sockjs_util:rand32()}], + D = sockjs_json:encode({I}), + H = [{"Content-Type", "application/json; charset=UTF-8"}], + sockjs_http:reply(200, H ++ Headers, D, Req). + +%% -------------------------------------------------------------------------- + +%% -spec xhr_polling(req(), headers(), service(), session()) -> req(). +xhr_polling(Req, Headers, Service, Session) -> + Req1 = chunk_start(Req, Headers), + reply_loop(Req1, Session, 1, fun fmt_xhr/1, Service). + +%% -spec xhr_streaming(req(), headers(), service(), session()) -> req(). +xhr_streaming(Req, Headers, Service = #service{response_limit = ResponseLimit}, + Session) -> + Req1 = chunk_start(Req, Headers), + %% IE requires 2KB prefix: + %% http://blogs.msdn.com/b/ieinternals/archive/2010/04/06/comet-streaming-in-internet-explorer-with-xmlhttprequest-and-xdomainrequest.aspx + Req2 = chunk(Req1, list_to_binary(string:copies("h", 2048)), + fun fmt_xhr/1), + reply_loop(Req2, Session, ResponseLimit, fun fmt_xhr/1, Service). + +%% -spec eventsource(req(), headers(), service(), session()) -> req(). +eventsource(Req, Headers, Service = #service{response_limit = ResponseLimit}, + SessionId) -> + Req1 = chunk_start(Req, Headers, "text/event-stream; charset=UTF-8"), + Req2 = chunk(Req1, <<$\r, $\n>>), + reply_loop(Req2, SessionId, ResponseLimit, fun fmt_eventsource/1, Service). + + +%% -spec htmlfile(req(), headers(), service(), session()) -> req(). +htmlfile(Req, Headers, Service = #service{response_limit = ResponseLimit}, + SessionId) -> + S = fun (Req1, CB) -> + Req2 = chunk_start(Req1, Headers, "text/html; charset=UTF-8"), + IFrame = iolist_to_binary(io_lib:format(?IFRAME_HTMLFILE, [CB])), + %% Safari needs at least 1024 bytes to parse the + %% website. Relevant: + %% http://code.google.com/p/browsersec/wiki/Part2#Survey_of_content_sniffing_behaviors + Padding = string:copies(" ", 1024 - size(IFrame)), + Req3 = chunk(Req2, [IFrame, Padding, <<"\r\n\r\n">>]), + reply_loop(Req3, SessionId, ResponseLimit, fun fmt_htmlfile/1, Service) + end, + verify_callback(Req, S). + +%% -spec jsonp(req(), headers(), service(), session()) -> req(). +jsonp(Req, Headers, Service, SessionId) -> + S = fun (Req1, CB) -> + Req2 = chunk_start(Req1, Headers), + reply_loop(Req2, SessionId, 1, + fun (Body) -> fmt_jsonp(Body, CB) end, Service) + end, + verify_callback(Req, S). + +verify_callback(Req, Success) -> + {CB, Req1} = sockjs_http:callback(Req), + case CB of + undefined -> + sockjs_http:reply(500, [], "\"callback\" parameter required", Req1); + _ -> + Success(Req1, CB) + end. + +%% -------------------------------------------------------------------------- + +%% -spec xhr_send(req(), headers(), service(), session()) -> req(). +xhr_send(Req, Headers, _Service, Session) -> + {Body, Req1} = sockjs_http:body(Req), + case handle_recv(Req1, Body, Session) of + {error, Req2} -> + Req2; + ok -> + H = [{"content-type", "text/plain; charset=UTF-8"}], + sockjs_http:reply(204, H ++ Headers, "", Req1) + end. + +%% -spec jsonp_send(req(), headers(), service(), session()) -> req(). +jsonp_send(Req, Headers, _Service, Session) -> + {Body, Req1} = sockjs_http:body_qs(Req), + case handle_recv(Req1, Body, Session) of + {error, Req2} -> + Req2; + ok -> + H = [{"content-type", "text/plain; charset=UTF-8"}], + sockjs_http:reply(200, H ++ Headers, "ok", Req1) + end. + +handle_recv(Req, Body, Session) -> + case Body of + _Any when Body =:= <<>> -> + {error, sockjs_http:reply(500, [], "Payload expected.", Req)}; + _Any -> + case sockjs_json:decode(Body) of + {ok, Decoded} when is_list(Decoded)-> + sockjs_session:received(Decoded, Session), + ok; + {error, _} -> + {error, sockjs_http:reply(500, [], + "Broken JSON encoding.", Req)} + end + end. + +%% -------------------------------------------------------------------------- + +-define(STILL_OPEN, {2010, "Another connection still open"}). + +chunk_start(Req, Headers) -> + chunk_start(Req, Headers, "application/javascript; charset=UTF-8"). +chunk_start(Req, Headers, ContentType) -> + sockjs_http:chunk_start(200, [{"Content-Type", ContentType}] ++ Headers, + Req). + +reply_loop(Req, SessionId, ResponseLimit, Fmt, Service) -> + Req0 = sockjs_http:hook_tcp_close(Req), + case sockjs_session:reply(SessionId) of + wait -> receive + %% In Cowboy we need to capture async + %% messages from the tcp connection - + %% ie: {active, once}. + {tcp_closed, _} -> + Req0; + %% In Cowboy we may in theory get real + %% http requests, this is bad. + {tcp, _S, Data} -> + error_logger:error_msg( + "Received unexpected data on a " + "long-polling http connection: ~p. " + "Connection aborted.~n", + [Data]), + Req1 = sockjs_http:abruptly_kill(Req), + Req1; + go -> + Req1 = sockjs_http:unhook_tcp_close(Req0), + reply_loop(Req1, SessionId, ResponseLimit, + Fmt, Service) + end; + session_in_use -> Frame = sockjs_util:encode_frame({close, ?STILL_OPEN}), + chunk_end(Req0, Frame, Fmt); + {close, Frame} -> Frame1 = sockjs_util:encode_frame(Frame), + chunk_end(Req0, Frame1, Fmt); + {ok, Frame} -> Frame1 = sockjs_util:encode_frame(Frame), + Frame2 = iolist_to_binary(Frame1), + Req2 = chunk(Req0, Frame2, Fmt), + reply_loop0(Req2, SessionId, + ResponseLimit - size(Frame2), + Fmt, Service) + end. + +reply_loop0(Req, _SessionId, ResponseLimit, _Fmt, _Service) when ResponseLimit =< 0 -> + chunk_end(Req); +reply_loop0(Req, SessionId, ResponseLimit, Fmt, Service) -> + reply_loop(Req, SessionId, ResponseLimit, Fmt, Service). + +chunk(Req, Body) -> + {_, Req1} = sockjs_http:chunk(Body, Req), + Req1. +chunk(Req, Body, Fmt) -> chunk(Req, Fmt(Body)). + +chunk_end(Req) -> sockjs_http:chunk_end(Req). +chunk_end(Req, Body, Fmt) -> Req1 = chunk(Req, Body, Fmt), + chunk_end(Req1). + +%% -spec fmt_xhr(iodata()) -> iodata(). +fmt_xhr(Body) -> [Body, "\n"]. + +%% -spec fmt_eventsource(iodata()) -> iodata(). +fmt_eventsource(Body) -> + Escaped = sockjs_util:url_escape(binary_to_list(iolist_to_binary(Body)), + "%\r\n\0"), %% $% must be first! + [<<"data: ">>, Escaped, <<"\r\n\r\n">>]. + +%% -spec fmt_htmlfile(iodata()) -> iodata(). +fmt_htmlfile(Body) -> + Double = sockjs_json:encode(iolist_to_binary(Body)), + [<<"\r\n">>]. + +%% -spec fmt_jsonp(iodata(), iodata()) -> iodata(). +fmt_jsonp(Body, Callback) -> + %% Yes, JSONed twice, there isn't a a better way, we must pass + %% a string back, and the script, will be evaled() by the + %% browser. + [Callback, "(", sockjs_json:encode(iolist_to_binary(Body)), ");\r\n"]. + +%% -------------------------------------------------------------------------- + +%% -spec websocket(req(), headers(), service()) -> req(). +websocket(Req, Headers, Service) -> + {_Any, Req1, {R1, R2}} = sockjs_handler:is_valid_ws(Service, Req), + case {R1, R2} of + {false, _} -> + sockjs_http:reply(400, Headers, + "Can \"Upgrade\" only to \"WebSocket\".", Req1); + {_, false} -> + sockjs_http:reply(400, Headers, + "\"Connection\" must be \"Upgrade\"", Req1); + {true, true} -> + sockjs_http:reply(400, Headers, + "This WebSocket request can't be handled.", Req1) + end. + +%% -spec rawwebsocket(req(), headers(), service()) -> req(). +rawwebsocket(Req, Headers, Service) -> + websocket(Req, Headers, Service). diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_app.erl b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_app.erl new file mode 100644 index 0000000..54aceb6 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_app.erl @@ -0,0 +1,14 @@ +-module(sockjs_app). + +-behaviour(application). + +-export([start/2, stop/1]). + +%% -spec start(_, _) -> {ok, pid()}. +start(_StartType, _StartArgs) -> + sockjs_session:init(), + sockjs_session_sup:start_link(). + +%% -spec stop(_) -> ok. +stop(_State) -> + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_cowboy_handler.erl b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_cowboy_handler.erl new file mode 100644 index 0000000..d2f05ae --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_cowboy_handler.erl @@ -0,0 +1,97 @@ +-module(sockjs_cowboy_handler). +-behaviour(cowboy_http_handler). +-behaviour(cowboy_http_websocket_handler). + +%% Cowboy http callbacks +-export([init/3, handle/2, terminate/2]). + +%% Cowboy ws callbacks +-export([websocket_init/3, websocket_handle/3, + websocket_info/3, websocket_terminate/3]). + +-include("sockjs_internal.hrl"). + +%% -------------------------------------------------------------------------- + +init({_Any, http}, Req, Service) -> + case sockjs_handler:is_valid_ws(Service, {cowboy, Req}) of + {true, {cowboy, _Req1}, _Reason} -> + {upgrade, protocol, cowboy_http_websocket}; + {false, {cowboy, Req1}, _Reason} -> + {ok, Req1, Service} + end. + +handle(Req, Service) -> + {cowboy, Req3} = sockjs_handler:handle_req(Service, {cowboy, Req}), + {ok, Req3, Service}. + +terminate(_Req, _Service) -> + ok. + +%% -------------------------------------------------------------------------- + +websocket_init(_TransportName, Req, + Service = #service{logger = Logger, + subproto_pref = SubProtocolPref}) -> + Req3 = case cowboy_http_req:header(<<"Sec-Websocket-Protocol">>, Req) of + {undefined, Req1} -> + Req1; + {SubProtocols, Req1} -> + SelectedSubProtocol = + choose_subprotocol_bin(SubProtocols, SubProtocolPref), + {ok, Req2} = cowboy_http_req:set_resp_header( + <<"Sec-Websocket-Protocol">>, + SelectedSubProtocol, Req1), + Req2 + end, + + Req4 = Logger(Service, {cowboy, Req3}, websocket), + + Service1 = Service#service{disconnect_delay = 5*60*1000}, + + {Info, Req5} = sockjs_handler:extract_info(Req4), + SessionPid = sockjs_session:maybe_create(undefined, Service1, Info), + {RawWebsocket, {cowboy, Req7}} = + case sockjs_handler:get_action(Service, Req5) of + {{match, WS}, Req6} when WS =:= websocket orelse + WS =:= rawwebsocket -> + {WS, Req6} + end, + self() ! go, + {ok, Req7, {RawWebsocket, SessionPid}}. + +websocket_handle({text, Data}, Req, {RawWebsocket, SessionPid} = S) -> + case sockjs_ws_handler:received(RawWebsocket, SessionPid, Data) of + ok -> {ok, Req, S}; + shutdown -> {shutdown, Req, S} + end; +websocket_handle(_Unknown, Req, S) -> + {shutdown, Req, S}. + +websocket_info(go, Req, {RawWebsocket, SessionPid} = S) -> + case sockjs_ws_handler:reply(RawWebsocket, SessionPid) of + wait -> {ok, Req, S}; + {ok, Data} -> self() ! go, + {reply, {text, Data}, Req, S}; + {close, <<>>} -> {shutdown, Req, S}; + {close, Data} -> self() ! shutdown, + {reply, {text, Data}, Req, S} + end; +websocket_info(shutdown, Req, S) -> + {shutdown, Req, S}. + +websocket_terminate(_Reason, _Req, {RawWebsocket, SessionPid}) -> + sockjs_ws_handler:close(RawWebsocket, SessionPid), + ok. + +%% -------------------------------------------------------------------------- + +choose_subprotocol_bin(SubProtocols, Pref) -> + choose_subprotocol(re:split(SubProtocols, ", *"), Pref). +choose_subprotocol(SubProtocols, undefined) -> + erlang:hd(lists:reverse(lists:sort(SubProtocols))); +choose_subprotocol(SubProtocols, Pref) -> + case lists:filter(fun (E) -> lists:member(E, SubProtocols) end, Pref) of + [Hd | _] -> Hd; + [] -> choose_subprotocol(SubProtocols, undefined) + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_filters.erl b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_filters.erl new file mode 100644 index 0000000..fba43cc --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_filters.erl @@ -0,0 +1,69 @@ +-module(sockjs_filters). + +-include("sockjs_internal.hrl"). + +-export([cache_for/2, h_sid/2, h_no_cache/2, xhr_cors/2, + xhr_options_post/2, xhr_options_get/2]). + +-define(YEAR, 365 * 24 * 60 * 60). + +%% -------------------------------------------------------------------------- + +%% -spec cache_for(req(), headers()) -> {headers(), req()}. +cache_for(Req, Headers) -> + Expires = calendar:gregorian_seconds_to_datetime( + calendar:datetime_to_gregorian_seconds( + calendar:now_to_datetime(now())) + ?YEAR), + H = [{"Cache-Control", "public, max-age=" ++ integer_to_list(?YEAR)}, + {"Expires", httpd_util:rfc1123_date(Expires)}], + {H ++ Headers, Req}. + +%% -spec h_sid(req(), headers()) -> {headers(), req()}. +h_sid(Req, Headers) -> + %% Some load balancers do sticky sessions, but only if there is + %% a JSESSIONID cookie. If this cookie isn't yet set, we shall + %% set it to a dumb value. It doesn't really matter what, as + %% session information is usually added by the load balancer. + {C, Req2} = sockjs_http:jsessionid(Req), + H = case C of + undefined -> [{"Set-Cookie", "JSESSIONID=dummy; path=/"}]; + Jsid -> [{"Set-Cookie", "JSESSIONID=" ++ Jsid ++ "; path=/"}] + end, + {H ++ Headers, Req2}. + +%% -spec h_no_cache(req(), headers()) -> {headers(), req()}. +h_no_cache(Req, Headers) -> + H = [{"Cache-Control", "no-store, no-cache, must-revalidate, max-age=0"}], + {H ++ Headers, Req}. + +%% -spec xhr_cors(req(), headers()) -> {headers(), req()}. +xhr_cors(Req, Headers) -> + {OriginH, Req1} = sockjs_http:header('Origin', Req), + Origin = case OriginH of + "null" -> "*"; + undefined -> "*"; + O -> O + end, + {HeadersH, Req2} = sockjs_http:header( + 'Access-Control-Request-Headers', Req1), + AllowHeaders = case HeadersH of + undefined -> []; + V -> [{"Access-Control-Allow-Headers", V}] + end, + H = [{"Access-Control-Allow-Origin", Origin}, + {"Access-Control-Allow-Credentials", "true"}], + {H ++ AllowHeaders ++ Headers, Req2}. + +%% -spec xhr_options_post(req(), headers()) -> {headers(), req()}. +xhr_options_post(Req, Headers) -> + xhr_options(Req, Headers, ["OPTIONS", "POST"]). + +%% -spec xhr_options_get(req(), headers()) -> {headers(), req()}. +xhr_options_get(Req, Headers) -> + xhr_options(Req, Headers, ["OPTIONS", "GET"]). + +%% -spec xhr_options(req(), headers(), list(string())) -> {headers(), req()}. +xhr_options(Req, Headers, Methods) -> + H = [{"Access-Control-Allow-Methods", string:join(Methods, ", ")}, + {"Access-Control-Max-Age", integer_to_list(?YEAR)}], + {H ++ Headers, Req}. diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_handler.erl b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_handler.erl new file mode 100644 index 0000000..81d4ef7 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_handler.erl @@ -0,0 +1,230 @@ +-module(sockjs_handler). + +-export([init_state/4]). +-export([is_valid_ws/2, get_action/2]). +-export([dispatch_req/2, handle_req/2]). +-export([extract_info/1]). + +-include("sockjs_internal.hrl"). + +-define(SOCKJS_URL, "http://cdn.sockjs.org/sockjs-0.2.js"). + +%% -------------------------------------------------------------------------- + +%% -spec init_state(binary(), callback(), any(), list(tuple())) -> service(). +init_state(Prefix, Callback, State, Options) -> + #service{prefix = binary_to_list(Prefix), + callback = Callback, + state = State, + sockjs_url = + proplists:get_value(sockjs_url, Options, ?SOCKJS_URL), + websocket = + proplists:get_value(websocket, Options, true), + cookie_needed = + proplists:get_value(cookie_needed, Options, false), + disconnect_delay = + proplists:get_value(disconnect_delay, Options, 5000), + heartbeat_delay = + proplists:get_value(heartbeat_delay, Options, 25000), + response_limit = + proplists:get_value(response_limit, Options, 128*1024), + logger = + proplists:get_value(logger, Options, fun default_logger/3), + subproto_pref = + proplists:get_value(subproto_pref, Options) + }. + +%% -------------------------------------------------------------------------- + +%% -spec is_valid_ws(service(), req()) -> {boolean(), req(), tuple()}. +is_valid_ws(Service, Req) -> + case get_action(Service, Req) of + {{match, WS}, Req1} when WS =:= websocket orelse + WS =:= rawwebsocket -> + valid_ws_request(Service, Req1); + {_Else, Req1} -> + {false, Req1, {}} + end. + +%% -spec valid_ws_request(service(), req()) -> {boolean(), req(), tuple()}. +valid_ws_request(_Service, Req) -> + {R1, Req1} = valid_ws_upgrade(Req), + {R2, Req2} = valid_ws_connection(Req1), + {R1 and R2, Req2, {R1, R2}}. + +valid_ws_upgrade(Req) -> + case sockjs_http:header('Upgrade', Req) of + {undefined, Req2} -> + {false, Req2}; + {V, Req2} -> + case string:to_lower(V) of + "websocket" -> + {true, Req2}; + _Else -> + {false, Req2} + end + end. + +valid_ws_connection(Req) -> + case sockjs_http:header('Connection', Req) of + {undefined, Req2} -> + {false, Req2}; + {V, Req2} -> + Vs = [string:strip(T) || + T <- string:tokens(string:to_lower(V), ",")], + {lists:member("upgrade", Vs), Req2} + end. + +%% -spec get_action(service(), req()) -> {nomatch | {match, atom()}, req()}. +get_action(Service, Req) -> + {Dispatch, Req1} = dispatch_req(Service, Req), + case Dispatch of + {match, {_, Action, _, _, _}} -> + {{match, Action}, Req1}; + _Else -> + {nomatch, Req1} + end. + +%% -------------------------------------------------------------------------- + +strip_prefix(LongPath, Prefix) -> + {A, B} = lists:split(length(Prefix), LongPath), + case Prefix of + A -> {ok, B}; + _Any -> {error, io_lib:format("Wrong prefix: ~p is not ~p", [A, Prefix])} + end. + + +%% -type(dispatch_result() :: +%% nomatch | +%% {match, {send | recv | none , atom(), +%% server(), session(), list(atom())}} | +%% {bad_method, list(atom())}). + +%% -spec dispatch_req(service(), req()) -> {dispatch_result(), req()}. +dispatch_req(#service{prefix = Prefix}, Req) -> + {Method, Req1} = sockjs_http:method(Req), + {LongPath, Req2} = sockjs_http:path(Req1), + {ok, PathRemainder} = strip_prefix(LongPath, Prefix), + {dispatch(Method, PathRemainder), Req2}. + +%% -spec dispatch(atom(), nonempty_string()) -> dispatch_result(). +dispatch(Method, Path) -> + lists:foldl( + fun ({Match, MethodFilters}, nomatch) -> + case Match(Path) of + nomatch -> + nomatch; + [Server, Session] -> + case lists:keyfind(Method, 1, MethodFilters) of + false -> + Methods = [ K || + {K, _, _, _} <- MethodFilters], + {bad_method, Methods}; + {_Method, Type, A, Filters} -> + {match, {Type, A, Server, Session, Filters}} + end + end; + (_, Result) -> + Result + end, nomatch, filters()). + +%% -------------------------------------------------------------------------- + +filters() -> + OptsFilters = [h_sid, xhr_cors, cache_for, xhr_options_post], + %% websocket does not actually go via handle_req/3 but we need + %% something in dispatch/2 + [{t("/websocket"), [{'GET', none, websocket, []}]}, + {t("/xhr_send"), [{'POST', recv, xhr_send, [h_sid, h_no_cache, xhr_cors]}, + {'OPTIONS', none, options, OptsFilters}]}, + {t("/xhr"), [{'POST', send, xhr_polling, [h_sid, h_no_cache, xhr_cors]}, + {'OPTIONS', none, options, OptsFilters}]}, + {t("/xhr_streaming"), [{'POST', send, xhr_streaming, [h_sid, h_no_cache, xhr_cors]}, + {'OPTIONS', none, options, OptsFilters}]}, + {t("/jsonp_send"), [{'POST', recv, jsonp_send, [h_sid, h_no_cache]}]}, + {t("/jsonp"), [{'GET', send, jsonp, [h_sid, h_no_cache]}]}, + {t("/eventsource"), [{'GET', send, eventsource, [h_sid, h_no_cache]}]}, + {t("/htmlfile"), [{'GET', send, htmlfile, [h_sid, h_no_cache]}]}, + {p("/websocket"), [{'GET', none, rawwebsocket, []}]}, + {p(""), [{'GET', none, welcome_screen, []}]}, + {p("/iframe[0-9-.a-z_]*.html"), [{'GET', none, iframe, [cache_for]}]}, + {p("/info"), [{'GET', none, info_test, [h_no_cache, xhr_cors]}, + {'OPTIONS', none, options, [h_sid, xhr_cors, cache_for, xhr_options_get]}]} + ]. + +p(S) -> fun (Path) -> re(Path, "^" ++ S ++ "[/]?\$") end. +t(S) -> fun (Path) -> re(Path, "^/([^/.]+)/([^/.]+)" ++ S ++ "[/]?\$") end. + +re(Path, S) -> + case re:run(Path, S, [{capture, all_but_first, list}]) of + nomatch -> nomatch; + {match, []} -> [dummy, dummy]; + {match, [Server, Session]} -> [Server, Session] + end. + +%% -------------------------------------------------------------------------- + +%% -spec handle_req(service(), req()) -> req(). +handle_req(Service = #service{logger = Logger}, Req) -> + Req0 = Logger(Service, Req, http), + + {Dispatch, Req1} = dispatch_req(Service, Req0), + handle(Dispatch, Service, Req1). + +handle(nomatch, _Service, Req) -> + sockjs_http:reply(404, [], "", Req); + +handle({bad_method, Methods}, _Service, Req) -> + MethodsStr = string:join([atom_to_list(M) || M <- Methods], + ", "), + H = [{"Allow", MethodsStr}], + sockjs_http:reply(405, H, "", Req); + +handle({match, {Type, Action, _Server, Session, Filters}}, Service, Req) -> + {Headers, Req2} = lists:foldl( + fun (Filter, {Headers0, Req1}) -> + sockjs_filters:Filter(Req1, Headers0) + end, {[], Req}, Filters), + case Type of + send -> + {Info, Req3} = extract_info(Req2), + _SPid = sockjs_session:maybe_create(Session, Service, Info), + sockjs_action:Action(Req3, Headers, Service, Session); + recv -> + try + sockjs_action:Action(Req2, Headers, Service, Session) + catch throw:no_session -> + {H, Req3} = sockjs_filters:h_sid(Req2, []), + sockjs_http:reply(404, H, "", Req3) + end; + none -> + sockjs_action:Action(Req2, Headers, Service) + end. + +%% -------------------------------------------------------------------------- + +%% -spec default_logger(service(), req(), websocket | http) -> req(). +default_logger(_Service, Req, _Type) -> + {LongPath, Req1} = sockjs_http:path(Req), + {Method, Req2} = sockjs_http:method(Req1), + io:format("~s ~s~n", [Method, LongPath]), + Req2. + +%% -spec extract_info(req()) -> {info(), req()}. +extract_info(Req) -> + {Peer, Req0} = sockjs_http:peername(Req), + {Sock, Req1} = sockjs_http:sockname(Req0), + {Path, Req2} = sockjs_http:path(Req1), + {Headers, Req3} = lists:foldl(fun (H, {Acc, R0}) -> + case sockjs_http:header(H, R0) of + {undefined, R1} -> {Acc, R1}; + {V, R1} -> {[{H, V} | Acc], R1} + end + end, {[], Req2}, + ['Referer', 'X-Client-Ip', 'X-Forwarded-For', + 'X-Cluster-Client-Ip', 'Via', 'X-Real-Ip']), + {[{peername, Peer}, + {sockname, Sock}, + {path, Path}, + {headers, Headers}], Req3}. diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_http.erl b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_http.erl new file mode 100644 index 0000000..837b64f --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_http.erl @@ -0,0 +1,137 @@ +-module(sockjs_http). + +-export([path/1, method/1, body/1, body_qs/1, header/2, jsessionid/1, + callback/1, peername/1, sockname/1]). +-export([reply/4, chunk_start/3, chunk/2, chunk_end/1]). +-export([hook_tcp_close/1, unhook_tcp_close/1, abruptly_kill/1]). +-include("sockjs_internal.hrl"). + +%% -------------------------------------------------------------------------- + +%% -spec path(req()) -> {string(), req()}. +path({cowboy, Req}) -> {Path, Req1} = cowboy_http_req:raw_path(Req), + {binary_to_list(Path), {cowboy, Req1}}. + +%% -spec method(req()) -> {atom(), req()}. +method({cowboy, Req}) -> {Method, Req1} = cowboy_http_req:method(Req), + case is_binary(Method) of + true -> {list_to_atom(binary_to_list(Method)), {cowboy, Req1}}; + false -> {Method, {cowboy, Req1}} + end. + +%% -spec body(req()) -> {binary(), req()}. +body({cowboy, Req}) -> {ok, Body, Req1} = cowboy_http_req:body(Req), + {Body, {cowboy, Req1}}. + +%% -spec body_qs(req()) -> {binary(), req()}. +body_qs(Req) -> + {H, Req1} = header('Content-Type', Req), + case H of + H when H =:= "text/plain" orelse H =:= "" -> + body(Req1); + _ -> + %% By default assume application/x-www-form-urlencoded + body_qs2(Req1) + end. +body_qs2({cowboy, Req}) -> + {BodyQS, Req1} = cowboy_http_req:body_qs(Req), + case proplists:get_value(<<"d">>, BodyQS) of + undefined -> + {<<>>, {cowboy, Req1}}; + V -> + {V, {cowboy, Req1}} + end. + +%% -spec header(atom(), req()) -> {nonempty_string() | undefined, req()}. +header(K, {cowboy, Req})-> + {H, Req2} = cowboy_http_req:header(K, Req), + {V, Req3} = case H of + undefined -> + cowboy_http_req:header(list_to_binary(atom_to_list(K)), Req2); + _ -> {H, Req2} + end, + case V of + undefined -> {undefined, {cowboy, Req3}}; + _ -> {binary_to_list(V), {cowboy, Req3}} + end. + +%% -spec jsessionid(req()) -> {nonempty_string() | undefined, req()}. +jsessionid({cowboy, Req}) -> + {C, Req2} = cowboy_http_req:cookie(<<"JSESSIONID">>, Req), + case C of + _ when is_binary(C) -> + {binary_to_list(C), {cowboy, Req2}}; + undefined -> + {undefined, {cowboy, Req2}} + end. + +%% -spec callback(req()) -> {nonempty_string() | undefined, req()}. +callback({cowboy, Req}) -> + {CB, Req1} = cowboy_http_req:qs_val(<<"c">>, Req), + case CB of + undefined -> {undefined, {cowboy, Req1}}; + _ -> {binary_to_list(CB), {cowboy, Req1}} + end. + +%% -spec peername(req()) -> {{inet:ip_address(), non_neg_integer()}, req()}. +peername({cowboy, Req}) -> + {P, Req1} = cowboy_http_req:peer(Req), + {P, {cowboy, Req1}}. + +%% -spec sockname(req()) -> {{inet:ip_address(), non_neg_integer()}, req()}. +sockname({cowboy, Req} = R) -> + {ok, _T, S} = cowboy_http_req:transport(Req), + %% Cowboy has peername(), but doesn't have sockname() equivalent. + {ok, Addr} = case S of + _ when is_port(S) -> + inet:sockname(S); + _ -> + {ok, {{0,0,0,0}, 0}} + end, + {Addr, R}. + +%% -------------------------------------------------------------------------- + +%% -spec reply(non_neg_integer(), headers(), iodata(), req()) -> req(). +reply(Code, Headers, Body, {cowboy, Req}) -> + Body1 = iolist_to_binary(Body), + {ok, Req1} = cowboy_http_req:reply(Code, enbinary(Headers), Body1, Req), + {cowboy, Req1}. + +%% -spec chunk_start(non_neg_integer(), headers(), req()) -> req(). +chunk_start(Code, Headers, {cowboy, Req}) -> + {ok, Req1} = cowboy_http_req:chunked_reply(Code, enbinary(Headers), Req), + {cowboy, Req1}. + +%% -spec chunk(iodata(), req()) -> {ok | error, req()}. +chunk(Chunk, {cowboy, Req} = R) -> + case cowboy_http_req:chunk(Chunk, Req) of + ok -> {ok, R}; + {error, _E} -> {error, R} + %% This shouldn't happen too often, usually we + %% should catch tco socket closure before. + end. + +%% -spec chunk_end(req()) -> req(). +chunk_end({cowboy, _Req} = R) -> R. + +enbinary(L) -> [{list_to_binary(K), list_to_binary(V)} || {K, V} <- L]. + + +%% -spec hook_tcp_close(req()) -> req(). +hook_tcp_close(R = {cowboy, Req}) -> + {ok, T, S} = cowboy_http_req:transport(Req), + T:setopts(S,[{active,once}]), + R. + +%% -spec unhook_tcp_close(req()) -> req(). +unhook_tcp_close(R = {cowboy, Req}) -> + {ok, T, S} = cowboy_http_req:transport(Req), + T:setopts(S,[{active,false}]), + R. + +%% -spec abruptly_kill(req()) -> req(). +abruptly_kill(R = {cowboy, Req}) -> + {ok, T, S} = cowboy_http_req:transport(Req), + T:close(S), + R. diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_internal.hrl b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_internal.hrl new file mode 100644 index 0000000..eed5597 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_internal.hrl @@ -0,0 +1,33 @@ + +%% -type(req() :: {cowboy, any()}). + +%% -type(user_session() :: nonempty_string()). +%% -type(emittable() :: init|closed|{recv, binary()}). +%% -type(callback() :: fun((user_session(), emittable(), any()) -> ok)). +%% -type(logger() :: fun((any(), req(), websocket|http) -> req())). + +-record(service, {prefix , %% nonempty_string(), + callback , %% callback() + state , %% any() + sockjs_url , %% nonempty_string() + cookie_needed , %% boolean() + websocket , %% boolean() + disconnect_delay , %% non_neg_integer() + heartbeat_delay , %% non_neg_integer() + response_limit , %% non_neg_integer() + logger , %% logger() + subproto_pref %% [binary()] + }). + +%% -type(service() :: #service{}). + +%% -type(headers() :: list({nonempty_string(), nonempty_string()})). +%% -type(server() :: nonempty_string()). +%% -type(session() :: nonempty_string()). + +%% -type(frame() :: {open, nil} | +%% {close, {non_neg_integer(), string()}} | +%% {data, list(iodata())} | +%% {heartbeat, nil} ). + +%% -type(info() :: [{atom(), any()}]). diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_json.erl b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_json.erl new file mode 100644 index 0000000..d3dae20 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_json.erl @@ -0,0 +1,17 @@ +-module(sockjs_json). + +-export([encode/1, decode/1]). + +%% -------------------------------------------------------------------------- + +%% -spec encode(any()) -> iodata(). +encode(Thing) -> + mochijson2_fork:encode(Thing). + +%% -spec decode(iodata()) -> {ok, any()} | {error, any()}. +decode(Encoded) -> + try mochijson2_fork:decode(Encoded) of + V -> {ok, V} + catch + _:E -> {error, E} + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_multiplex.erl b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_multiplex.erl new file mode 100644 index 0000000..b4ff03a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_multiplex.erl @@ -0,0 +1,79 @@ +-module(sockjs_multiplex). + +-behaviour(sockjs_service). + +-export([init_state/1]). +-export([sockjs_init/2, sockjs_handle/3, sockjs_terminate/2]). + +-record(service, {callback, state, vconn}). + +%% -------------------------------------------------------------------------- + +init_state(Services) -> + L = [{Topic, #service{callback = Callback, state = State}} || + {Topic, Callback, State} <- Services], + {orddict:from_list(L), orddict:new()}. + + + +sockjs_init(_Conn, {_Services, _Channels} = S) -> + {ok, S}. + +sockjs_handle(Conn, Data, {Services, Channels}) -> + [Type, Topic, Payload] = split($,, binary_to_list(Data), 3), + case orddict:find(Topic, Services) of + {ok, Service} -> + Channels1 = action(Conn, {Type, Topic, Payload}, Service, Channels), + {ok, {Services, Channels1}}; + _Else -> + {ok, {Services, Channels}} + end. + +sockjs_terminate(_Conn, {Services, Channels}) -> + _ = [ {emit(closed, Channel)} || + {_Topic, Channel} <- orddict:to_list(Channels) ], + {ok, {Services, orddict:new()}}. + + +action(Conn, {Type, Topic, Payload}, Service, Channels) -> + case {Type, orddict:is_key(Topic, Channels)} of + {"sub", false} -> + Channel = Service#service{ + vconn = sockjs_multiplex_channel:new( + Conn, Topic) + }, + orddict:store(Topic, emit(init, Channel), Channels); + {"uns", true} -> + Channel = orddict:fetch(Topic, Channels), + emit(closed, Channel), + orddict:erase(Topic, Channels); + {"msg", true} -> + Channel = orddict:fetch(Topic, Channels), + orddict:store(Topic, emit({recv, Payload}, Channel), Channels); + _Else -> + %% Ignore + Channels + end. + + +emit(What, Channel = #service{callback = Callback, + state = State, + vconn = VConn}) -> + case Callback(VConn, What, State) of + {ok, State1} -> Channel#service{state = State1}; + ok -> Channel + end. + + +%% -------------------------------------------------------------------------- + +split(Char, Str, Limit) -> + Acc = split(Char, Str, Limit, []), + lists:reverse(Acc). +split(_Char, _Str, 0, Acc) -> Acc; +split(Char, Str, Limit, Acc) -> + {L, R} = case string:chr(Str, Char) of + 0 -> {Str, ""}; + I -> {string:substr(Str, 1, I-1), string:substr(Str, I+1)} + end, + split(Char, R, Limit-1, [L | Acc]). diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_multiplex_channel.erl b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_multiplex_channel.erl new file mode 100644 index 0000000..5afcfa3 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_multiplex_channel.erl @@ -0,0 +1,18 @@ +-compile({parse_transform,pmod_pt}). + +-module(sockjs_multiplex_channel, [Conn, Topic]). + +-export([send/1, close/0, close/2, info/0]). + +send(Data) -> + Conn:send(iolist_to_binary(["msg", ",", Topic, ",", Data])). + +close() -> + close(1000, "Normal closure"). + +close(_Code, _Reason) -> + Conn:send(iolist_to_binary(["uns", ",", Topic])). + +info() -> + Conn:info() ++ [{topic, Topic}]. + diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_service.erl b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_service.erl new file mode 100644 index 0000000..df0d79b --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_service.erl @@ -0,0 +1,13 @@ +-module(sockjs_service). + +-export([behaviour_info/1]). + +behaviour_info(callbacks) -> + [ + {sockjs_init, 2}, + {sockjs_handle, 3}, + {sockjs_terminate, 2} + ]; + +behaviour_info(_Other) -> + undefined. diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_session.erl b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_session.erl new file mode 100644 index 0000000..7e4ae00 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_session.erl @@ -0,0 +1,306 @@ +-module(sockjs_session). + +-behaviour(gen_server). + +-export([init/0, start_link/3]). +-export([maybe_create/3, reply/1, reply/2, received/2]). +-export([send/2, close/3, info/1]). + + +-export([init/1, handle_call/3, handle_info/2, terminate/2, code_change/3, + handle_cast/2]). + +-include("sockjs_internal.hrl"). +%% -type(handle() :: {?MODULE, {pid(), info()}}). + +-record(session, {id , %% session(), + outbound_queue = queue:new() , %% queue() + response_pid , %% pid() + disconnect_tref , %% reference() + disconnect_delay = 5000 , %% non_neg_integer() + heartbeat_tref , %% reference() | triggered + heartbeat_delay = 25000 , %% non_neg_integer() + ready_state = connecting , %% connecting | open | closed + close_msg , %% {non_neg_integer(), string()} + callback, + state, + handle %% handle() + }). +-define(ETS, sockjs_table). + + +%% -type(session_or_undefined() :: session() | undefined). +%% -type(session_or_pid() :: session() | pid()). + +%% -------------------------------------------------------------------------- + +%% -spec init() -> ok. +init() -> + _ = ets:new(?ETS, [public, named_table]), + ok. + +%% -spec start_link(session_or_undefined(), service(), info()) -> {ok, pid()}. +start_link(SessionId, Service, Info) -> + gen_server:start_link(?MODULE, {SessionId, Service, Info}, []). + +%% -spec maybe_create(session_or_undefined(), service(), info()) -> pid(). +maybe_create(SessionId, Service, Info) -> + case ets:lookup(?ETS, SessionId) of + [] -> {ok, SPid} = sockjs_session_sup:start_child( + SessionId, Service, Info), + SPid; + [{_, SPid}] -> SPid + end. + + +%% -spec received(list(iodata()), session_or_pid()) -> ok. +received(Messages, SessionPid) when is_pid(SessionPid) -> + case gen_server:call(SessionPid, {received, Messages}, infinity) of + ok -> ok; + error -> throw(no_session) + %% TODO: should we respond 404 when session is closed? + end; +received(Messages, SessionId) -> + received(Messages, spid(SessionId)). + +%% -spec send(iodata(), handle()) -> ok. +send(Data, {?MODULE, {SPid, _}}) -> + gen_server:cast(SPid, {send, Data}), + ok. + +%% -spec close(non_neg_integer(), string(), handle()) -> ok. +close(Code, Reason, {?MODULE, {SPid, _}}) -> + gen_server:cast(SPid, {close, Code, Reason}), + ok. + +%% -spec info(handle()) -> info(). +info({?MODULE, {_SPid, Info}}) -> + Info. + +%% -spec reply(session_or_pid()) -> +%% wait | session_in_use | {ok | close, frame()}. +reply(Session) -> + reply(Session, true). + +%% -spec reply(session_or_pid(), boolean()) -> +%% wait | session_in_use | {ok | close, frame()}. +reply(SessionPid, Multiple) when is_pid(SessionPid) -> + gen_server:call(SessionPid, {reply, self(), Multiple}, infinity); +reply(SessionId, Multiple) -> + reply(spid(SessionId), Multiple). + +%% -------------------------------------------------------------------------- + +cancel_timer_safe(Timer, Atom) -> + case erlang:cancel_timer(Timer) of + false -> + receive Atom -> ok + after 0 -> ok end; + _ -> ok + end. + +spid(SessionId) -> + case ets:lookup(?ETS, SessionId) of + [] -> throw(no_session); + [{_, SPid}] -> SPid + end. + +%% Mark a process as waiting for data. +%% 1) The same process may ask for messages multiple times. +mark_waiting(Pid, State = #session{response_pid = Pid, + disconnect_tref = undefined}) -> + State; +%% 2) Noone else waiting - link and start heartbeat timeout. +mark_waiting(Pid, State = #session{response_pid = undefined, + disconnect_tref = DisconnectTRef, + heartbeat_delay = HeartbeatDelay}) + when DisconnectTRef =/= undefined -> + link(Pid), + cancel_timer_safe(DisconnectTRef, session_timeout), + TRef = erlang:send_after(HeartbeatDelay, self(), heartbeat_triggered), + State#session{response_pid = Pid, + disconnect_tref = undefined, + heartbeat_tref = TRef}. + +%% Prolong session lifetime. +%% 1) Maybe clear up response_pid if already awaiting. +unmark_waiting(RPid, State = #session{response_pid = RPid, + heartbeat_tref = HeartbeatTRef, + disconnect_tref = undefined, + disconnect_delay = DisconnectDelay}) -> + unlink(RPid), + _ = case HeartbeatTRef of + undefined -> ok; + triggered -> ok; + _Else -> cancel_timer_safe(HeartbeatTRef, heartbeat_triggered) + end, + TRef = erlang:send_after(DisconnectDelay, self(), session_timeout), + State#session{response_pid = undefined, + heartbeat_tref = undefined, + disconnect_tref = TRef}; + +%% 2) prolong disconnect timer if no connection is waiting +unmark_waiting(_Pid, State = #session{response_pid = undefined, + disconnect_tref = DisconnectTRef, + disconnect_delay = DisconnectDelay}) + when DisconnectTRef =/= undefined -> + cancel_timer_safe(DisconnectTRef, session_timeout), + TRef = erlang:send_after(DisconnectDelay, self(), session_timeout), + State#session{disconnect_tref = TRef}; + +%% 3) Event from someone else? Ignore. +unmark_waiting(RPid, State = #session{response_pid = Pid, + disconnect_tref = undefined}) + when Pid =/= undefined andalso Pid =/= RPid -> + State. + +%% -spec emit(emittable(), #session{}) -> #session{}. +emit(What, State = #session{callback = Callback, + state = UserState, + handle = Handle}) -> + R = case Callback of + _ when is_function(Callback) -> + Callback(Handle, What, UserState); + _ when is_atom(Callback) -> + case What of + init -> Callback:sockjs_init(Handle, UserState); + {recv, Data} -> Callback:sockjs_handle(Handle, Data, UserState); + closed -> Callback:sockjs_terminate(Handle, UserState) + end + end, + case R of + {ok, UserState1} -> State#session{state = UserState1}; + ok -> State + end. + +%% -------------------------------------------------------------------------- + +%% -spec init({session_or_undefined(), service(), info()}) -> {ok, #session{}}. +init({SessionId, #service{callback = Callback, + state = UserState, + disconnect_delay = DisconnectDelay, + heartbeat_delay = HeartbeatDelay}, Info}) -> + case SessionId of + undefined -> ok; + _Else -> ets:insert(?ETS, {SessionId, self()}) + end, + process_flag(trap_exit, true), + TRef = erlang:send_after(DisconnectDelay, self(), session_timeout), + {ok, #session{id = SessionId, + callback = Callback, + state = UserState, + response_pid = undefined, + disconnect_tref = TRef, + disconnect_delay = DisconnectDelay, + heartbeat_tref = undefined, + heartbeat_delay = HeartbeatDelay, + handle = {?MODULE, {self(), Info}}}}. + + +handle_call({reply, Pid, _Multiple}, _From, State = #session{ + response_pid = undefined, + ready_state = connecting}) -> + State0 = emit(init, State), + State1 = unmark_waiting(Pid, State0), + {reply, {ok, {open, nil}}, + State1#session{ready_state = open}}; + +handle_call({reply, Pid, _Multiple}, _From, State = #session{ + ready_state = closed, + close_msg = CloseMsg}) -> + State1 = unmark_waiting(Pid, State), + {reply, {close, {close, CloseMsg}}, State1}; + + +handle_call({reply, Pid, _Multiple}, _From, State = #session{ + response_pid = RPid}) + when RPid =/= Pid andalso RPid =/= undefined -> + %% don't use unmark_waiting(), this shouldn't touch the session lifetime + {reply, session_in_use, State}; + +handle_call({reply, Pid, Multiple}, _From, State = #session{ + ready_state = open, + response_pid = RPid, + heartbeat_tref = HeartbeatTRef, + outbound_queue = Q}) + when RPid == undefined orelse RPid == Pid -> + {Messages, Q1} = case Multiple of + true -> {queue:to_list(Q), queue:new()}; + false -> case queue:out(Q) of + {{value, Msg}, Q2} -> {[Msg], Q2}; + {empty, Q2} -> {[], Q2} + end + end, + case {Messages, HeartbeatTRef} of + {[], triggered} -> State1 = unmark_waiting(Pid, State), + {reply, {ok, {heartbeat, nil}}, State1}; + {[], _TRef} -> State1 = mark_waiting(Pid, State), + {reply, wait, State1}; + _More -> State1 = unmark_waiting(Pid, State), + {reply, {ok, {data, Messages}}, + State1#session{outbound_queue = Q1}} + end; + +handle_call({received, Messages}, _From, State = #session{ready_state = open}) -> + State2 = lists:foldl(fun(Msg, State1) -> + emit({recv, iolist_to_binary(Msg)}, State1) + end, State, Messages), + {reply, ok, State2}; + +handle_call({received, _Data}, _From, State = #session{ready_state = _Any}) -> + {reply, error, State}; + +handle_call(Request, _From, State) -> + {stop, {odd_request, Request}, State}. + + +handle_cast({send, Data}, State = #session{outbound_queue = Q, + response_pid = RPid}) -> + case RPid of + undefined -> ok; + _Else -> RPid ! go + end, + {noreply, State#session{outbound_queue = queue:in(Data, Q)}}; + +handle_cast({close, Status, Reason}, State = #session{response_pid = RPid}) -> + case RPid of + undefined -> ok; + _Else -> RPid ! go + end, + {noreply, State#session{ready_state = closed, + close_msg = {Status, Reason}}}; + +handle_cast(Cast, State) -> + {stop, {odd_cast, Cast}, State}. + + +handle_info({'EXIT', Pid, _Reason}, + State = #session{response_pid = Pid}) -> + %% It is illegal for a connection to go away when receiving, we + %% may lose some messages that are in transit. Kill current + %% session. + {stop, normal, State#session{response_pid = undefined}}; + +handle_info(force_shutdown, State) -> + %% Websockets may want to force closure sometimes + {stop, normal, State}; + +handle_info(session_timeout, State = #session{response_pid = undefined}) -> + {stop, normal, State}; + +handle_info(heartbeat_triggered, State = #session{response_pid = RPid}) when RPid =/= undefined -> + RPid ! go, + {noreply, State#session{heartbeat_tref = triggered}}; + +handle_info(Info, State) -> + {stop, {odd_info, Info}, State}. + + +terminate(_, State = #session{id = SessionId}) -> + ets:delete(?ETS, SessionId), + _ = emit(closed, State), + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_session_sup.erl b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_session_sup.erl new file mode 100644 index 0000000..71c7ff4 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_session_sup.erl @@ -0,0 +1,20 @@ +-module(sockjs_session_sup). + +-behaviour(supervisor). + +-export([start_link/0, start_child/3]). +-export([init/1]). + +%% -------------------------------------------------------------------------- + +%% -spec start_link() -> ignore | {'ok', pid()} | {'error', any()}. +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +init([]) -> + {ok, {{simple_one_for_one, 10, 10}, + [{undefined, {sockjs_session, start_link, []}, + transient, 5000, worker, [sockjs_session]}]}}. + +start_child(SessionId, Service, Info) -> + supervisor:start_child(?MODULE, [SessionId, Service, Info]). diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_util.erl b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_util.erl new file mode 100644 index 0000000..9b9969d --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_util.erl @@ -0,0 +1,48 @@ +-module(sockjs_util). + +-export([rand32/0]). +-export([encode_frame/1]). +-export([url_escape/2]). + +-include("sockjs_internal.hrl"). + +%% -------------------------------------------------------------------------- + +%% -spec rand32() -> non_neg_integer(). +rand32() -> + case get(random_seeded) of + undefined -> + {MegaSecs, Secs, MicroSecs} = now(), + _ = random:seed(MegaSecs, Secs, MicroSecs), + put(random_seeded, true); + _Else -> + ok + end, + random:uniform(erlang:trunc(math:pow(2,32)))-1. + + +%% -spec encode_frame(frame()) -> iodata(). +encode_frame({open, nil}) -> + <<"o">>; +encode_frame({close, {Code, Reason}}) -> + [<<"c">>, + sockjs_json:encode([Code, list_to_binary(Reason)])]; +encode_frame({data, L}) -> + [<<"a">>, + sockjs_json:encode([iolist_to_binary(D) || D <- L])]; +encode_frame({heartbeat, nil}) -> + <<"h">>. + + +%% -spec url_escape(string(), string()) -> iolist(). +url_escape(Str, Chars) -> + [case lists:member(Char, Chars) of + true -> hex(Char); + false -> Char + end || Char <- Str]. + +hex(C) -> + <> = <>, + High = integer_to_list(High0), + Low = integer_to_list(Low0), + "%" ++ High ++ Low. diff --git a/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_ws_handler.erl b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_ws_handler.erl new file mode 100644 index 0000000..c011c89 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/sockjs-erlang-wrapper/sockjs-erlang-git/src/sockjs_ws_handler.erl @@ -0,0 +1,58 @@ +-module(sockjs_ws_handler). + +-export([received/3, reply/2, close/2]). + +-include("sockjs_internal.hrl"). + +%% -------------------------------------------------------------------------- + +%% -spec received(websocket|rawwebsocket, pid(), binary()) -> ok | shutdown. +%% Ignore empty +received(_RawWebsocket, _SessionPid, <<>>) -> + ok; +received(websocket, SessionPid, Data) -> + case sockjs_json:decode(Data) of + {ok, Msg} when is_binary(Msg) -> + session_received([Msg], SessionPid); + {ok, Messages} when is_list(Messages) -> + session_received(Messages, SessionPid); + _Else -> + shutdown + end; + +received(rawwebsocket, SessionPid, Data) -> + session_received([Data], SessionPid). + +session_received(Messages, SessionPid) -> + try sockjs_session:received(Messages, SessionPid) of + ok -> ok + catch + no_session -> shutdown + end. + +%% -spec reply(websocket|rawwebsocket, pid()) -> {close|open, binary()} | wait. +reply(websocket, SessionPid) -> + case sockjs_session:reply(SessionPid) of + {W, Frame} when W =:= ok orelse W =:= close-> + Frame1 = sockjs_util:encode_frame(Frame), + {W, iolist_to_binary(Frame1)}; + wait -> + wait + end; +reply(rawwebsocket, SessionPid) -> + case sockjs_session:reply(SessionPid, false) of + {W, Frame} when W =:= ok orelse W =:= close-> + case Frame of + {open, nil} -> reply(rawwebsocket, SessionPid); + {close, {_Code, _Reason}} -> {close, <<>>}; + {data, [Msg]} -> {ok, iolist_to_binary(Msg)}; + {heartbeat, nil} -> reply(rawwebsocket, SessionPid) + end; + wait -> + wait + end. + +%% -spec close(websocket|rawwebsocket, pid()) -> ok. +close(_RawWebsocket, SessionPid) -> + SessionPid ! force_shutdown, + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/umbrella.mk b/rabbitmq-server-3.3.5/plugins-src/umbrella.mk new file mode 100644 index 0000000..5764ff3 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/umbrella.mk @@ -0,0 +1,55 @@ +# The default goal +dist: + +UMBRELLA_BASE_DIR:=.. + +include $(UMBRELLA_BASE_DIR)/common.mk + +# We start at the initial package (i.e. the one in the current directory) +PACKAGE_DIR:=$(call canonical_path,.) + +# Produce all of the releasable artifacts of this package +.PHONY: dist +dist: $(PACKAGE_DIR)+dist + +# Produce a source tarball for this package +.PHONY: srcdist +srcdist: $(PACKAGE_DIR)+srcdist + +# Clean the package and all its dependencies +.PHONY: clean +clean: $(PACKAGE_DIR)+clean-with-deps + +# Clean just the initial package +.PHONY: clean-local +clean-local: $(PACKAGE_DIR)+clean + +# Run erlang with the package, its tests, and all its dependencies +# available. +.PHONY: run +run: $(PACKAGE_DIR)+run + +# Run the broker with the package, its tests, and all its dependencies +# available. +.PHONY: run-in-broker +run-in-broker: $(PACKAGE_DIR)+run-in-broker + +# Runs the package's tests +.PHONY: test +test: $(PACKAGE_DIR)+test + +# Test the package with code coverage recording on. Note that +# coverage only covers the in-broker tests. +.PHONY: coverage +coverage: $(PACKAGE_DIR)+coverage + +# Runs the package's tests +.PHONY: check-xref +check-xref: $(PACKAGE_DIR)+check-xref + +# Do the initial package +include $(UMBRELLA_BASE_DIR)/do-package.mk + +# We always need the coverage package to support the coverage goal +PACKAGE_DIR:=$(COVERAGE_PATH) +$(eval $(call do_package,$(COVERAGE_PATH))) diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/.srcdist_done b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/.srcdist_done new file mode 100644 index 0000000..e69de29 diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/10-remove-crypto-dependency.patch b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/10-remove-crypto-dependency.patch new file mode 100644 index 0000000..7cabbd4 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/10-remove-crypto-dependency.patch @@ -0,0 +1,78 @@ +diff --git a/src/webmachine.app.src b/src/webmachine.app.src +index eb949a2..2c46c3f 100644 +--- a/src/webmachine.app.src ++++ b/src/webmachine.app.src +@@ -7,7 +7,6 @@ + {registered, []}, + {applications, [kernel, + stdlib, +- crypto, + mochiweb]}, + {mod, {webmachine_app, []}}, + {env, []} +diff --git a/src/webmachine.erl b/src/webmachine.erl +index 47f1ce2..2e5be1b 100644 +--- a/src/webmachine.erl ++++ b/src/webmachine.erl +@@ -28,7 +28,6 @@ + %% @doc Start the webmachine server. + start() -> + webmachine_deps:ensure(), +- application:start(crypto), + application:start(webmachine). + + %% @spec stop() -> ok +diff --git a/src/webmachine_decision_core.erl b/src/webmachine_decision_core.erl +index 194c48d..3379388 100644 +--- a/src/webmachine_decision_core.erl ++++ b/src/webmachine_decision_core.erl +@@ -722,32 +722,17 @@ variances() -> + end, + Accept ++ AcceptEncoding ++ AcceptCharset ++ resource_call(variances). + +--ifndef(old_hash). + md5(Bin) -> +- crypto:hash(md5, Bin). ++ erlang:md5(Bin). + + md5_init() -> +- crypto:hash_init(md5). ++ erlang:md5_init(). + + md5_update(Ctx, Bin) -> +- crypto:hash_update(Ctx, Bin). ++ erlang:md5_update(Ctx, Bin). + + md5_final(Ctx) -> +- crypto:hash_final(Ctx). +--else. +-md5(Bin) -> +- crypto:md5(Bin). +- +-md5_init() -> +- crypto:md5_init(). +- +-md5_update(Ctx, Bin) -> +- crypto:md5_update(Ctx, Bin). +- +-md5_final(Ctx) -> +- crypto:md5_final(Ctx). +--endif. +- ++ erlang:md5_final(Ctx). + + compute_body_md5() -> + case wrcall({req_body, 52428800}) of +diff --git a/src/webmachine_request.erl b/src/webmachine_request.erl +index 2a5ff7a..ee459a3 100644 +--- a/src/webmachine_request.erl ++++ b/src/webmachine_request.erl +@@ -624,7 +624,7 @@ parts_to_body(BodyList, Size, Req) when is_list(BodyList) -> + {CT, _} -> + CT + end, +- Boundary = mochihex:to_hex(crypto:rand_bytes(8)), ++ Boundary = mochihex:to_hex(mochiweb_util:rand_bytes(8)), + HeaderList = [{"Content-Type", + ["multipart/byteranges; ", + "boundary=", Boundary]}], diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/LICENSE-Apache-Basho b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/LICENSE-Apache-Basho new file mode 100644 index 0000000..e454a52 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/LICENSE-Apache-Basho @@ -0,0 +1,178 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/Makefile b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/Makefile new file mode 100644 index 0000000..482105a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/Makefile @@ -0,0 +1 @@ +include ../umbrella.mk diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/hash.mk b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/hash.mk new file mode 100644 index 0000000..d5fc525 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/hash.mk @@ -0,0 +1 @@ +UPSTREAM_SHORT_HASH:=e9359c7 diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/license_info b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/license_info new file mode 100644 index 0000000..c00fb92 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/license_info @@ -0,0 +1,3 @@ +Webmachine is Copyright (c) Basho Technologies and is covered by the +Apache License 2.0. It was downloaded from http://webmachine.basho.com/ + diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/package.mk b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/package.mk new file mode 100644 index 0000000..65770e7 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/package.mk @@ -0,0 +1,19 @@ +APP_NAME:=webmachine +DEPS:=mochiweb-wrapper + +UPSTREAM_GIT:=https://github.com/rabbitmq/webmachine.git +UPSTREAM_REVISION:=e9359c7092b228f671417abe68319913f1aebe46 +RETAIN_ORIGINAL_VERSION:=true + +WRAPPER_PATCHES:=10-remove-crypto-dependency.patch + +ORIGINAL_APP_FILE=$(CLONE_DIR)/src/$(APP_NAME).app.src +DO_NOT_GENERATE_APP_FILE=true + +define package_rules + +# This rule is run *before* the one in do_package.mk +$(PLUGINS_SRC_DIST_DIR)/$(PACKAGE_DIR)/.srcdist_done:: + cp $(CLONE_DIR)/LICENSE $(PACKAGE_DIR)/LICENSE-Apache-Basho + +endef diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/.done b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/.done new file mode 100644 index 0000000..e69de29 diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/.travis.yml b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/.travis.yml new file mode 100644 index 0000000..e603470 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/.travis.yml @@ -0,0 +1,9 @@ +language: erlang +notifications: + webhooks: http://basho-engbot.herokuapp.com/travis?key=66724b424957d598311ba00bb2d137fcae4eae21 + email: eng@basho.com +otp_release: + - R15B01 + - R15B + - R14B04 + - R14B03 diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/Emakefile b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/Emakefile new file mode 100644 index 0000000..7ee92ef --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/Emakefile @@ -0,0 +1,6 @@ +% -*- mode: erlang -*- +{["src/*"], + [{i, "include"}, + {outdir, "ebin"}, + debug_info] +}. diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/LICENSE b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/LICENSE new file mode 100644 index 0000000..e454a52 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/LICENSE @@ -0,0 +1,178 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/Makefile b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/Makefile new file mode 100644 index 0000000..e9de516 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/Makefile @@ -0,0 +1,24 @@ +ERL ?= erl +APP := webmachine + +.PHONY: deps + +all: deps + @(./rebar compile) + +deps: + @(./rebar get-deps) + +clean: + @(./rebar clean) + +distclean: clean + @(./rebar delete-deps) + +edoc: + @$(ERL) -noshell -run edoc_run application '$(APP)' '"."' '[{preprocess, true},{includes, ["."]}]' + +test: all + @(./rebar skip_deps=true eunit) + + diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/README.org b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/README.org new file mode 100644 index 0000000..73b985d --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/README.org @@ -0,0 +1,66 @@ +* webmachine +** Overview + +[[http://travis-ci.org/basho/webmachine][Travis-CI]] :: [[https://secure.travis-ci.org/basho/webmachine.png]] + +Webmachine is an application layer that adds HTTP semantic awareness +on top of the excellent bit-pushing and HTTP syntax-management +provided by mochiweb, and provides a simple and clean way to connect +that to your application's behavior. + +More information is available [[http://webmachine.basho.com/][here]]. + +** Quick Start +A shell script is provided in the =webmachine= repository to help +users quickly and easily create a new =webmachine= application. + +#+BEGIN_SRC shell +git clone git://github.com/basho/webmachine.git +cd webmachine +./scripts/new_webmachine.sh mydemo +#+END_SRC + +A destination path can also be passed to the =new_webmachine.sh= +script. + +#+BEGIN_SRC shell +./scripts/new_webmachine.sh mydemo ~/webmachine_applications +#+END_SRC + +Once a new application has been created it can be built and started. + +#+BEGIN_SRC shell +cd mydemo +make +./start.sh +#+END_SRC + +The application will be available at [[http://localhost:8000]]. + +To learn more continue reading [[http://webmachine.basho.com/][here]]. + +** Contributing + We encourage contributions to =webmachine= from the community. + + 1) Fork the =webmachine= repository on [[https://github.com/basho/webmachine][Github]]. + 2) Clone your fork or add the remote if you already have a clone of + the repository. +#+BEGIN_SRC shell +git clone git@github.com:yourusername/webmachine.git +# or +git remote add mine git@github.com:yourusername/webmachine.git +#+END_SRC + 3) Create a topic branch for your change. +#+BEGIN_SRC shell +git checkout -b some-topic-branch +#+END_SRC + 4) Make your change and commit. Use a clear and descriptive commit + message, spanning multiple lines if detailed explanation is + needed. + 5) Push to your fork of the repository and then send a pull-request + through Github. +#+BEGIN_SRC shell +git push mine some-topic-branch +#+END_SRC + 6) A Basho engineer or community maintainer will review your patch + and merge it into the main repository or send you feedback. diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/THANKS b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/THANKS new file mode 100644 index 0000000..e3a10ba --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/THANKS @@ -0,0 +1,21 @@ +The following people have contributed to Webmachine: + +Andy Gross +Justin Sheehy +John Muellerleile +Robert Ahrens +Jeremy Latt +Bryan Fink +Ryan Tilder +Taavi Talvik +Marc Worrell +Seth Falcon +Tuncer Ayaz +Martin Scholl +Paul Mineiro +Dave Smith +Arjan Scherpenisse +Benjamin Black +Anthony Molinaro +Phil Pirozhkov +Rusty Klophaus \ No newline at end of file diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/Makefile b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/Makefile new file mode 100644 index 0000000..e134e6a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/Makefile @@ -0,0 +1,19 @@ +ERL ?= erl +APP := webmachine_demo + +.PHONY: deps + +all: deps + @../rebar compile + +deps: + @../rebar get-deps + +clean: + @../rebar clean + +distclean: clean + @../rebar delete-deps + +docs: + @erl -noshell -run edoc_run application '$(APP)' '"."' '[]' diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/README b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/README new file mode 100644 index 0000000..cc5ab25 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/README @@ -0,0 +1,43 @@ +Project Skeleton for the webmachine_demo app. + +You should find in this directory: + +README : this file +Makefile : simple make commands +rebar : the Rebar build tool for Erlang applications +rebar.config : configuration for Rebar +start.sh : simple startup script for running webmachine_demo +/ebin + /webmachine_demo.app : the Erlang app specification +/src + /webmachine_demo_app.erl : base module for the Erlang application + /webmachine_demo_sup.erl : OTP supervisor for the application + /webmachine_demo_resource.erl : a simple example Webmachine resource +/priv + /dispatch.conf : the Webmachine URL-dispatching table + /www : a convenient place to put your static web content + +You probably want to do one of a couple of things at this point: + +0. Build the skeleton application: + $ make + - or - + $ ./rebar compile + +1. Start up the skeleton application: + $ ./start.sh + +2. Test the basic application: + Visit http://localhost:8000/demo + +3. Change the basic application: + edit src/webmachine_demo_resource.erl + +4. Test the filesystem resource: + $ mkdir /tmp/fs + $ echo "Hello World." > /tmp/fs/demo.txt + Visit http://localhost:8000/fs/demo.txt + +5. Add some new resources: + edit src/YOUR_NEW_RESOURCE.erl + edit priv/dispatch.conf diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/priv/dispatch.conf b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/priv/dispatch.conf new file mode 100644 index 0000000..587ea31 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/priv/dispatch.conf @@ -0,0 +1,3 @@ +%%-*- mode: erlang -*- +{["demo", '*'], webmachine_demo_resource, []}. +{["fs", '*'], webmachine_demo_fs_resource, [{root, "/tmp/fs"}]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/rebar.config b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/rebar.config new file mode 100644 index 0000000..d245b80 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/rebar.config @@ -0,0 +1,3 @@ +%%-*- mode: erlang -*- + +{deps, [{webmachine, "1.10.*", {git, "git://github.com/basho/webmachine", "HEAD"}}]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo.app.src b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo.app.src new file mode 100644 index 0000000..a18addd --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo.app.src @@ -0,0 +1,17 @@ +%%-*- mode: erlang -*- +{application, webmachine_demo, + [ + {description, "demo"}, + {vsn, "1"}, + {modules, []}, + {registered, []}, + {applications, [ + kernel, + stdlib, + crypto, + mochiweb, + webmachine + ]}, + {mod, { webmachine_demo_app, []}}, + {env, []} + ]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo.erl b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo.erl new file mode 100644 index 0000000..1431fb9 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo.erl @@ -0,0 +1,42 @@ +-module(webmachine_demo). +-author('Andy Gross '). +-author('Justin Sheehy '). +-export([start/0, start_link/0, stop/0]). + +ensure_started(App) -> + case application:start(App) of + ok -> + ok; + {error, {already_started, App}} -> + ok + end. + +%% @spec start_link() -> {ok,Pid::pid()} +%% @doc Starts the app for inclusion in a supervisor tree +start_link() -> + ensure_started(crypto), + ensure_started(mochiweb), + application:set_env(webmachine, webmachine_logger_module, + webmachine_logger), + ensure_started(webmachine), + webmachine_demo_sup:start_link(). + +%% @spec start() -> ok +%% @doc Start the webmachine_demo server. +start() -> + ensure_started(inets), + ensure_started(crypto), + ensure_started(mochiweb), + application:set_env(webmachine, webmachine_logger_module, + webmachine_logger), + ensure_started(webmachine), + application:start(webmachine_demo). + +%% @spec stop() -> ok +%% @doc Stop the webmachine_demo server. +stop() -> + Res = application:stop(webmachine_demo), + application:stop(webmachine), + application:stop(mochiweb), + application:stop(crypto), + Res. diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo_app.erl b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo_app.erl new file mode 100644 index 0000000..f36f22e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo_app.erl @@ -0,0 +1,20 @@ +%% @author Andy Gross +%% @author Justin Sheehy + +%% @doc Callbacks for the webmachine_demo application. + +-module(webmachine_demo_app). + +-behaviour(application). +-export([start/2,stop/1]). + + +%% @spec start(_Type, _StartArgs) -> ServerRet +%% @doc application start callback for webmachine_demo. +start(_Type, _StartArgs) -> + webmachine_demo_sup:start_link(). + +%% @spec stop(_State) -> ServerRet +%% @doc application stop callback for webmachine_demo. +stop(_State) -> + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo_fs_resource.erl b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo_fs_resource.erl new file mode 100644 index 0000000..9488268 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo_fs_resource.erl @@ -0,0 +1,159 @@ +%% @author Bryan Fink +%% @author Andy Gross +%% @author Justin Sheehy +%% @copyright 2008-2009 Basho Technologies, Inc. + +-module(webmachine_demo_fs_resource). +-export([init/1]). +-export([allowed_methods/2, + resource_exists/2, + last_modified/2, + content_types_provided/2, + content_types_accepted/2, + delete_resource/2, + post_is_create/2, + create_path/2, + provide_content/2, + accept_content/2, + generate_etag/2]). + +-record(context, {root,response_body=undefined,metadata=[]}). + +-include_lib("kernel/include/file.hrl"). +-include_lib("webmachine/include/webmachine.hrl"). + +init(ConfigProps) -> + {root, Root} = proplists:lookup(root, ConfigProps), + {ok, #context{root=Root}}. + +allowed_methods(ReqData, Context) -> + {['HEAD', 'GET', 'PUT', 'DELETE', 'POST'], ReqData, Context}. + +file_path(_Context, []) -> + false; +file_path(Context, Name) -> + RelName = case hd(Name) of + "/" -> tl(Name); + _ -> Name + end, + filename:join([Context#context.root, RelName]). + +file_exists(Context, Name) -> + NamePath = file_path(Context, Name), + case filelib:is_regular(NamePath) of + true -> + {true, NamePath}; + false -> + false + end. + +resource_exists(ReqData, Context) -> + Path = wrq:disp_path(ReqData), + case file_exists(Context, Path) of + {true, _} -> + {true, ReqData, Context}; + _ -> + case Path of + "p" -> {true, ReqData, Context}; + _ -> {false, ReqData, Context} + end + end. + +maybe_fetch_object(Context, Path) -> + % if returns {true, NewContext} then NewContext has response_body + case Context#context.response_body of + undefined -> + case file_exists(Context, Path) of + {true, FullPath} -> + {ok, Value} = file:read_file(FullPath), + {true, Context#context{response_body=Value}}; + false -> + {false, Context} + end; + _Body -> + {true, Context} + end. + +content_types_provided(ReqData, Context) -> + CT = webmachine_util:guess_mime(wrq:disp_path(ReqData)), + {[{CT, provide_content}], ReqData, + Context#context{metadata=[{'content-type', CT}|Context#context.metadata]}}. + +content_types_accepted(ReqData, Context) -> + CT = case wrq:get_req_header("content-type", ReqData) of + undefined -> "application/octet-stream"; + X -> X + end, + {MT, _Params} = webmachine_util:media_type_to_detail(CT), + {[{MT, accept_content}], ReqData, + Context#context{metadata=[{'content-type', MT}|Context#context.metadata]}}. + +accept_content(ReqData, Context) -> + Path = wrq:disp_path(ReqData), + FP = file_path(Context, Path), + ok = filelib:ensure_dir(FP), + ReqData1 = case file_exists(Context, Path) of + {true, _} -> + ReqData; + _ -> + LOC = "http://" ++ + wrq:get_req_header("host", ReqData) ++ + "/fs/" ++ Path, + wrq:set_resp_header("Location", LOC, ReqData) + end, + Value = wrq:req_body(ReqData1), + case file:write_file(FP, Value) of + ok -> + {true, wrq:set_resp_body(Value, ReqData1), Context}; + Err -> + {{error, Err}, ReqData1, Context} + end. + +post_is_create(ReqData, Context) -> + {true, ReqData, Context}. + +create_path(ReqData, Context) -> + case wrq:get_req_header("slug", ReqData) of + undefined -> {undefined, ReqData, Context}; + Slug -> + case file_exists(Context, Slug) of + {true, _} -> {undefined, ReqData, Context}; + _ -> {Slug, ReqData, Context} + end + end. + +delete_resource(ReqData, Context) -> + case file:delete(file_path( + Context, wrq:disp_path(ReqData))) of + ok -> {true, ReqData, Context}; + _ -> {false, ReqData, Context} + end. + +provide_content(ReqData, Context) -> + case maybe_fetch_object(Context, wrq:disp_path(ReqData)) of + {true, NewContext} -> + Body = NewContext#context.response_body, + {Body, ReqData, Context}; + {false, NewContext} -> + {error, ReqData, NewContext} + end. + +last_modified(ReqData, Context) -> + {true, FullPath} = file_exists(Context, + wrq:disp_path(ReqData)), + LMod = filelib:last_modified(FullPath), + {LMod, ReqData, Context#context{metadata=[{'last-modified', + httpd_util:rfc1123_date(LMod)}|Context#context.metadata]}}. + +hash_body(Body) -> mochihex:to_hex(binary_to_list(crypto:sha(Body))). + +generate_etag(ReqData, Context) -> + case maybe_fetch_object(Context, wrq:disp_path(ReqData)) of + {true, BodyContext} -> + ETag = hash_body(BodyContext#context.response_body), + {ETag, ReqData, + BodyContext#context{metadata=[{etag,ETag}| + BodyContext#context.metadata]}}; + _ -> + {undefined, ReqData, Context} + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo_resource.erl b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo_resource.erl new file mode 100644 index 0000000..797ab78 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo_resource.erl @@ -0,0 +1,51 @@ +%% @author Justin Sheehy +%% @copyright 2007-2009 Basho Technologies, Inc. All Rights Reserved. +%% @doc Example webmachine_resource. + +-module(webmachine_demo_resource). +-author('Justin Sheehy '). +-export([init/1, to_html/2, to_text/2, content_types_provided/2, + is_authorized/2, generate_etag/2, expires/2, last_modified/2]). + +-include_lib("webmachine/include/webmachine.hrl"). + +init([]) -> {ok, undefined}. + +content_types_provided(ReqData, Context) -> + {[{"text/html", to_html},{"text/plain",to_text}], ReqData, Context}. + +to_text(ReqData, Context) -> + Path = wrq:disp_path(ReqData), + Body = io_lib:format("Hello ~s from webmachine.~n", [Path]), + {Body, ReqData, Context}. + +to_html(ReqData, Context) -> + {Body, _RD, Ctx2} = to_text(ReqData, Context), + HBody = io_lib:format("~s~n", + [erlang:iolist_to_binary(Body)]), + {HBody, ReqData, Ctx2}. + +is_authorized(ReqData, Context) -> + case wrq:disp_path(ReqData) of + "authdemo" -> + case wrq:get_req_header("authorization", ReqData) of + "Basic "++Base64 -> + Str = base64:mime_decode_to_string(Base64), + case string:tokens(Str, ":") of + ["authdemo", "demo1"] -> + {true, ReqData, Context}; + _ -> + {"Basic realm=webmachine", ReqData, Context} + end; + _ -> + {"Basic realm=webmachine", ReqData, Context} + end; + _ -> {true, ReqData, Context} + end. + +expires(ReqData, Context) -> {{{2021,1,1},{0,0,0}}, ReqData, Context}. + +last_modified(ReqData, Context) -> + {calendar:now_to_universal_time(os:timestamp()), ReqData, Context}. + +generate_etag(ReqData, Context) -> {wrq:raw_path(ReqData), ReqData, Context}. diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo_sup.erl b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo_sup.erl new file mode 100644 index 0000000..ea4532c --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/src/webmachine_demo_sup.erl @@ -0,0 +1,56 @@ +%% @author author +%% @copyright YYYY author. + +%% @doc Supervisor for the webmachine_demo application. + +-module(webmachine_demo_sup). + +-behaviour(supervisor). + +%% External exports +-export([start_link/0, upgrade/0]). + +%% supervisor callbacks +-export([init/1]). + +%% @spec start_link() -> ServerRet +%% @doc API for starting the supervisor. +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%% @spec upgrade() -> ok +%% @doc Add processes if necessary. +upgrade() -> + {ok, {_, Specs}} = init([]), + + Old = sets:from_list( + [Name || {Name, _, _, _} <- supervisor:which_children(?MODULE)]), + New = sets:from_list([Name || {Name, _, _, _, _, _} <- Specs]), + Kill = sets:subtract(Old, New), + + sets:fold(fun (Id, ok) -> + supervisor:terminate_child(?MODULE, Id), + supervisor:delete_child(?MODULE, Id), + ok + end, ok, Kill), + + [supervisor:start_child(?MODULE, Spec) || Spec <- Specs], + ok. + +%% @spec init([]) -> SupervisorTree +%% @doc supervisor callback. +init([]) -> + Ip = case os:getenv("WEBMACHINE_IP") of false -> "0.0.0.0"; Any -> Any end, + {ok, Dispatch} = file:consult(filename:join( + [filename:dirname(code:which(?MODULE)), + "..", "priv", "dispatch.conf"])), + WebConfig = [ + {ip, Ip}, + {port, 8000}, + {log_dir, "priv/log"}, + {dispatch, Dispatch}], + Web = {webmachine_mochiweb, + {webmachine_mochiweb, start, [WebConfig]}, + permanent, 5000, worker, dynamic}, + Processes = [Web], + {ok, { {one_for_one, 10, 10}, Processes} }. diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/start.sh b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/start.sh new file mode 100755 index 0000000..8a234ab --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/demo/start.sh @@ -0,0 +1,3 @@ +#!/bin/sh +cd `dirname $0` +exec erl -pa $PWD/ebin $PWD/deps/*/ebin -boot start_sasl -s reloader -s webmachine_demo diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/docs/http-headers-status-v3.png b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/docs/http-headers-status-v3.png new file mode 100644 index 0000000000000000000000000000000000000000..231316066863c9f786ab5f251c37cd0a17eb3940 GIT binary patch literal 412634 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�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
    5aa6Xx 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%0DmpBq7QjI}GjyIj+2wiWH9$xL zSZIw5n;08_nbu0&TH4#P@MV#|{Ag2SBW%FydDIo|r=(8twZpGn=gcqdl|E2YfKlf- zBpnM{(62v=BptkSLDN1==s*q9A0>VOYbQ?EZm8E=&Fh(Z6UifZnEm!(iV`-2a@PHt zUxoR1{KBiSCn`0sG8X;c3zmn?wdGG)w-(N3`+&UBQtZmXltLdOo6^JN3PDZ|CA>t5 z$!^izY592Sfnq$5;cBzA7oQ3U-$(NYyz)ifPxmhO)zr&2!iIOSc^vU8esLBtRlFL(=!REb_e||t z6xe6_;!0hOpxvcC6$x{sm*S~f$G-bUdfkY7dl`p3l$7uHDR8mHz+6f1?Ch)P`1{i* z{EPFXh}D0$4_I;-D_hA0lhhvI;wn9=o%9x9AN)PtM{Rb;?C* zoAe0lVkp|(oaf^H6Mm5-=zEn**op)gW_FOYYqrrn z3iZ*9lX_%nPhpeC*_WRa8#qqka$l7`Qeh@#D~eO%nrIma>aTYNl-yPkJXZ#1ET}ri z7x)nQnMI}%jz2Wp7E@SR_%yI*k7t~fBI|CqTY@3h6Qel3kp1UGPyd3n5jZ`ft$$8K zd?GuBdDI0q)uu#Ux7WRbL6yQtQS{PiPc-eLoYk`G3iqbzF+u0aQDlBogYcSN7=3G7 zhV!ditLW?6O5$IJ8U%TBhiWU*IIRUmBwo-P%LimTjBD~zZ9w~OU2WwKv-x6bJo2RK z5bX<_2*nbCbVhUg0Q!L@!>C|_)jFm@M~^~oRlkz{iG zU-GP-!cwX&sX# zLH#Q(1X$Y;*I?@dk38QUz0FToS8)|hRlfE(BukT2U^QWGuK(W3I%x0eod#wbml?i| zCOxk0gHNhtd4@F`N%SJ4EBDLxAA}$oIP*O%KSGqeDw%=Ea0(EALRYacNgO?S+pQlvh6!cG)8xY;x~rOV4ZHpUyz^P2)YKw9s;(B9FN-SlbkMU$*JdZJXdJ4 zG<#Xk#93T!t&tK)vDu4gEeSP2v5ek(aq&o^arn89Ib{)3)e6QlnPtaE(u7E%Z#1JX z-hWNsdYH>L07FkIk8$jmQ)#L2X&YJZt$(x8XqGX9hQ|*;iN!6Jb?V#duCkRSsx$>5 z+{1@2YE6aEl2(V08qDc)Iu%gtUP2yf9f-vRg9~LVHDe0qR}CgtzU6?9*yWd?i3FAd z=4u&M$%?;-2ZyJP>gGWJ0Cx7xENRyl8I)YsReWZIfQ4)jPU*wr1K+}~NJb#UhsuQf z9v$R5_j%>Jk%UKuyQfk6(yuSv2XL`)Z9Jp|&u8UvPF>h@@yT~n8pb01%hTN%jkf*Q zxtxeP0aGFkz7xG?4=nEIKL#)NtYu03GW&T$`tz)bUOUKAl_n(B6kmZE>+j;NhqmOUp;rxBL=jd@jlFT{Vg1>*W< + {pong, ReqData, State}. + + diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/include/webmachine_logger.hrl b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/include/webmachine_logger.hrl new file mode 100644 index 0000000..c07068a --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/include/webmachine_logger.hrl @@ -0,0 +1,16 @@ +-record(wm_log_data, + {resource_module :: atom(), + start_time :: tuple(), + method :: atom(), + headers, + peer, + path :: string(), + version, + response_code, + response_length, + end_time :: tuple(), + finish_time :: tuple(), + notes}). +-type wm_log_data() :: #wm_log_data{}. + +-define(EVENT_LOGGER, webmachine_log_event). diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/include/wm_reqdata.hrl b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/include/wm_reqdata.hrl new file mode 100644 index 0000000..e82bcbc --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/include/wm_reqdata.hrl @@ -0,0 +1,8 @@ +-record(wm_reqdata, {method, scheme, version, peer, wm_state, + disp_path, path, raw_path, path_info, path_tokens, + app_root,response_code,max_recv_body, max_recv_hunk, + req_cookie, req_qs, req_headers, req_body, + resp_redirect, resp_headers, resp_body, resp_range, + host_tokens, port, notes + }). + diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/include/wm_reqstate.hrl b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/include/wm_reqstate.hrl new file mode 100644 index 0000000..a72a574 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/include/wm_reqstate.hrl @@ -0,0 +1,10 @@ +-record(wm_reqstate, {socket=undefined, + metadata=orddict:new(), + range=undefined, + peer=undefined, + reqdata=undefined, + bodyfetch=undefined, + reqbody=undefined, + log_data=undefined + }). + diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/include/wm_resource.hrl b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/include/wm_resource.hrl new file mode 100644 index 0000000..588405d --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/include/wm_resource.hrl @@ -0,0 +1 @@ +-record(wm_resource, {module, modstate, modexports, trace}). diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/Makefile b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/Makefile new file mode 100644 index 0000000..812f23b --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/Makefile @@ -0,0 +1,19 @@ +ERL ?= erl +APP := {{appid}} + +.PHONY: deps + +all: deps + @./rebar compile + +deps: + @./rebar get-deps + +clean: + @./rebar clean + +distclean: clean + @./rebar delete-deps + +docs: + @erl -noshell -run edoc_run application '$(APP)' '"."' '[]' diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/README b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/README new file mode 100644 index 0000000..36f5591 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/README @@ -0,0 +1,35 @@ +Project Skeleton for the {{appid}} app. + +You should find in this directory: + +README : this file +Makefile : simple make commands +rebar : the Rebar build tool for Erlang applications +rebar.config : configuration for Rebar +start.sh : simple startup script for running {{appid}} +/ebin + /{{appid}}.app : the Erlang app specification +/src + /{{appid}}_app.erl : base module for the Erlang application + /{{appid}}_sup.erl : OTP supervisor for the application + /{{appid}}_resource.erl : a simple example Webmachine resource +/priv + /dispatch.conf : the Webmachine URL-dispatching table + /www : a convenient place to put your static web content + +You probably want to do one of a couple of things at this point: + +0. Build the skeleton application: + $ make + - or - + $ ./rebar compile + +1. Start up the skeleton application: + $ ./start.sh + +2. Change the basic application: + edit src/{{appid}}_resource.erl + +3. Add some new resources: + edit src/YOUR_NEW_RESOURCE.erl + edit priv/dispatch.conf diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/priv/dispatch.conf b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/priv/dispatch.conf new file mode 100644 index 0000000..fdc5f0e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/priv/dispatch.conf @@ -0,0 +1,2 @@ +%%-*- mode: erlang -*- +{[], {{appid}}_resource, []}. diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/rebar.config b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/rebar.config new file mode 100644 index 0000000..d245b80 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/rebar.config @@ -0,0 +1,3 @@ +%%-*- mode: erlang -*- + +{deps, [{webmachine, "1.10.*", {git, "git://github.com/basho/webmachine", "HEAD"}}]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/src/wmskel.app.src b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/src/wmskel.app.src new file mode 100644 index 0000000..449abf6 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/src/wmskel.app.src @@ -0,0 +1,18 @@ +%%-*- mode: erlang -*- +{application, {{appid}}, + [ + {description, "{{appid}}"}, + {vsn, "1"}, + {modules, []}, + {registered, []}, + {applications, [ + kernel, + stdlib, + inets, + crypto, + mochiweb, + webmachine + ]}, + {mod, { {{appid}}_app, []}}, + {env, []} + ]}. diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/src/wmskel.erl b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/src/wmskel.erl new file mode 100644 index 0000000..2ef035e --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/src/wmskel.erl @@ -0,0 +1,48 @@ +%% @author author +%% @copyright YYYY author. + +%% @doc {{appid}} startup code + +-module({{appid}}). +-author('author '). +-export([start/0, start_link/0, stop/0]). + +ensure_started(App) -> + case application:start(App) of + ok -> + ok; + {error, {already_started, App}} -> + ok + end. + +%% @spec start_link() -> {ok,Pid::pid()} +%% @doc Starts the app for inclusion in a supervisor tree +start_link() -> + ensure_started(inets), + ensure_started(crypto), + ensure_started(mochiweb), + application:set_env(webmachine, webmachine_logger_module, + webmachine_logger), + ensure_started(webmachine), + {{appid}}_sup:start_link(). + +%% @spec start() -> ok +%% @doc Start the {{appid}} server. +start() -> + ensure_started(inets), + ensure_started(crypto), + ensure_started(mochiweb), + application:set_env(webmachine, webmachine_logger_module, + webmachine_logger), + ensure_started(webmachine), + application:start({{appid}}). + +%% @spec stop() -> ok +%% @doc Stop the {{appid}} server. +stop() -> + Res = application:stop({{appid}}), + application:stop(webmachine), + application:stop(mochiweb), + application:stop(crypto), + application:stop(inets), + Res. diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/src/wmskel_app.erl b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/src/wmskel_app.erl new file mode 100644 index 0000000..61f6b73 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/src/wmskel_app.erl @@ -0,0 +1,21 @@ +%% @author author +%% @copyright YYYY author. + +%% @doc Callbacks for the {{appid}} application. + +-module({{appid}}_app). +-author('author '). + +-behaviour(application). +-export([start/2,stop/1]). + + +%% @spec start(_Type, _StartArgs) -> ServerRet +%% @doc application start callback for {{appid}}. +start(_Type, _StartArgs) -> + {{appid}}_sup:start_link(). + +%% @spec stop(_State) -> ServerRet +%% @doc application stop callback for {{appid}}. +stop(_State) -> + ok. diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/src/wmskel_resource.erl b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/src/wmskel_resource.erl new file mode 100644 index 0000000..5112803 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/src/wmskel_resource.erl @@ -0,0 +1,13 @@ +%% @author author +%% @copyright YYYY author. +%% @doc Example webmachine_resource. + +-module({{appid}}_resource). +-export([init/1, to_html/2]). + +-include_lib("webmachine/include/webmachine.hrl"). + +init([]) -> {ok, undefined}. + +to_html(ReqData, State) -> + {"Hello, new world", ReqData, State}. diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/src/wmskel_sup.erl b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/src/wmskel_sup.erl new file mode 100644 index 0000000..53e11f4 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/src/wmskel_sup.erl @@ -0,0 +1,72 @@ +%% @author author +%% @copyright YYYY author. + +%% @doc Supervisor for the {{appid}} application. + +-module({{appid}}_sup). +-author('author '). + +-behaviour(supervisor). + +%% External exports +-export([start_link/0, upgrade/0]). + +%% supervisor callbacks +-export([init/1]). + +%% @spec start_link() -> ServerRet +%% @doc API for starting the supervisor. +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%% @spec upgrade() -> ok +%% @doc Add processes if necessary. +upgrade() -> + {ok, {_, Specs}} = init([]), + + Old = sets:from_list( + [Name || {Name, _, _, _} <- supervisor:which_children(?MODULE)]), + New = sets:from_list([Name || {Name, _, _, _, _, _} <- Specs]), + Kill = sets:subtract(Old, New), + + sets:fold(fun (Id, ok) -> + supervisor:terminate_child(?MODULE, Id), + supervisor:delete_child(?MODULE, Id), + ok + end, ok, Kill), + + [supervisor:start_child(?MODULE, Spec) || Spec <- Specs], + ok. + +%% @spec init([]) -> SupervisorTree +%% @doc supervisor callback. +init([]) -> + Ip = case os:getenv("WEBMACHINE_IP") of false -> "0.0.0.0"; Any -> Any end, + {ok, App} = application:get_application(?MODULE), + {ok, Dispatch} = file:consult(filename:join([priv_dir(App), + "dispatch.conf"])), + Port = case os:getenv("WEBMACHINE_PORT") of + false -> 8000; + AnyPort -> AnyPort + end, + WebConfig = [ + {ip, Ip}, + {port, Port}, + {log_dir, "priv/log"}, + {dispatch, Dispatch}], + Web = {webmachine_mochiweb, + {webmachine_mochiweb, start, [WebConfig]}, + permanent, 5000, worker, [mochiweb_socket_server]}, + Processes = [Web], + {ok, { {one_for_one, 10, 10}, Processes} }. + +%% +%% @doc return the priv dir +priv_dir(Mod) -> + case code:priv_dir(Mod) of + {error, bad_name} -> + Ebin = filename:dirname(code:which(Mod)), + filename:join(filename:dirname(Ebin), "priv"); + PrivDir -> + PrivDir + end. diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/start.sh b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/start.sh new file mode 100644 index 0000000..0a7ff56 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/start.sh @@ -0,0 +1,3 @@ +#!/bin/sh +cd `dirname $0` +exec erl -pa $PWD/ebin $PWD/deps/*/ebin -boot start_sasl -s reloader -s {{appid}} diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/wmskel.template b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/wmskel.template new file mode 100644 index 0000000..c150ac8 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/templates/wmskel.template @@ -0,0 +1,35 @@ +%%-*- mode: erlang -*- +%% Basic Webmachine application skeleton + +%% Variables: +%% appid: name of the application to build +%% default = "wmskel" +%% webmachine: path to webmachine from this template +%% default = "../.." +%% prefix: path where the application should be created +%% default = "." +{variables, [{appid, "wmskel"}, + {webmachine, "../.."}, + {prefix, "."}]}. + +%% main project files +{template, "README", "{{prefix}}/README"}. +{template, "Makefile", "{{prefix}}/Makefile"}. +{template, "rebar.config", "{{prefix}}/rebar.config"}. +{file, "{{webmachine}}/rebar", "{{prefix}}/rebar"}. +{chmod, 8#744, "{{prefix}}/rebar"}. +{template, "start.sh", "{{prefix}}/start.sh"}. +{chmod, 8#744, "{{prefix}}/start.sh"}. + +{template, "src/wmskel.app.src", "{{prefix}}/src/{{appid}}.app.src"}. + +{template, "src/wmskel.erl", "{{prefix}}/src/{{appid}}.erl"}. +{template, "src/wmskel_app.erl", "{{prefix}}/src/{{appid}}_app.erl"}. +{template, "src/wmskel_sup.erl", "{{prefix}}/src/{{appid}}_sup.erl"}. +{template, "src/wmskel_resource.erl", "{{prefix}}/src/{{appid}}_resource.erl"}. + +{template, "priv/dispatch.conf", "{{prefix}}/priv/dispatch.conf"}. +{dir, "{{prefix}}/priv/www"}. + +%% dependencies +{dir, "{{prefix}}/deps"}. diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/trace/http-headers-status-v3.png b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/trace/http-headers-status-v3.png new file mode 100644 index 0000000000000000000000000000000000000000..231316066863c9f786ab5f251c37cd0a17eb3940 GIT binary patch literal 412634 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�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
    5aa6Xx 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%0DmpBq7QjI}GjyIj+2wiWH9$xL zSZIw5n;08_nbu0&TH4#P@MV#|{Ag2SBW%FydDIo|r=(8twZpGn=gcqdl|E2YfKlf- zBpnM{(62v=BptkSLDN1==s*q9A0>VOYbQ?EZm8E=&Fh(Z6UifZnEm!(iV`-2a@PHt zUxoR1{KBiSCn`0sG8X;c3zmn?wdGG)w-(N3`+&UBQtZmXltLdOo6^JN3PDZ|CA>t5 z$!^izY592Sfnq$5;cBzA7oQ3U-$(NYyz)ifPxmhO)zr&2!iIOSc^vU8esLBtRlFL(=!REb_e||t z6xe6_;!0hOpxvcC6$x{sm*S~f$G-bUdfkY7dl`p3l$7uHDR8mHz+6f1?Ch)P`1{i* z{EPFXh}D0$4_I;-D_hA0lhhvI;wn9=o%9x9AN)PtM{Rb;?C* zoAe0lVkp|(oaf^H6Mm5-=zEn**op)gW_FOYYqrrn z3iZ*9lX_%nPhpeC*_WRa8#qqka$l7`Qeh@#D~eO%nrIma>aTYNl-yPkJXZ#1ET}ri z7x)nQnMI}%jz2Wp7E@SR_%yI*k7t~fBI|CqTY@3h6Qel3kp1UGPyd3n5jZ`ft$$8K zd?GuBdDI0q)uu#Ux7WRbL6yQtQS{PiPc-eLoYk`G3iqbzF+u0aQDlBogYcSN7=3G7 zhV!ditLW?6O5$IJ8U%TBhiWU*IIRUmBwo-P%LimTjBD~zZ9w~OU2WwKv-x6bJo2RK z5bX<_2*nbCbVhUg0Q!L@!>C|_)jFm@M~^~oRlkz{iG zU-GP-!cwX&sX# zLH#Q(1X$Y;*I?@dk38QUz0FToS8)|hRlfE(BukT2U^QWGuK(W3I%x0eod#wbml?i| zCOxk0gHNhtd4@F`N%SJ4EBDLxAA}$oIP*O%KSGqeDw%=Ea0(EALRYacNgO?S+pQlvh6!cG)8xY;x~rOV4ZHpUyz^P2)YKw9s;(B9FN-SlbkMU$*JdZJXdJ4 zG<#Xk#93T!t&tK)vDu4gEeSP2v5ek(aq&o^arn89Ib{)3)e6QlnPtaE(u7E%Z#1JX z-hWNsdYH>L07FkIk8$jmQ)#L2X&YJZt$(x8XqGX9hQ|*;iN!6Jb?V#duCkRSsx$>5 z+{1@2YE6aEl2(V08qDc)Iu%gtUP2yf9f-vRg9~LVHDe0qR}CgtzU6?9*yWd?i3FAd z=4u&M$%?;-2ZyJP>gGWJ0Cx7xENRyl8I)YsReWZIfQ4)jPU*wr1K+}~NJb#UhsuQf z9v$R5_j%>Jk%UKuyQfk6(yuSv2XL`)Z9Jp|&u8UvPF>h@@yT~n8pb01%hTN%jkf*Q zxtxeP0aGFkz7xG?4=nEIKL#)NtYu03GW&T$`tz)bUOUKAl_n(B6kmZE>+j;NhqmOUp;rxBL=jd@jlFT{Vg1>*W<'; + } + return prev; +}; + +function drawTrace() { + drawDecision(trace[0]); + for (var i = 1; i < trace.length; i++) { + drawPath(trace[i].path); + drawDecision(trace[i]); + } + + drawPath(response.path); + drawResponse(); +}; + +function drawResponse() { + if (response.type == 'normal') { + var context = canvas.getContext('2d'); + context.strokeStyle=HIGHLIGHT; + context.lineWidth=4; + + context.beginPath(); + context.rect(response.x-(response.width/2), + response.y-19, + response.width, + 38); + context.stroke(); + } else { + var context = canvas.getContext('2d'); + context.strokeStyle='#ff0000'; + context.lineWidth=4; + + context.beginPath(); + context.arc(response.x, response.y, 19, + 0, 2*3.14159, false); + context.stroke(); + + } +}; + +function drawDecision(dec) { + var context = canvas.getContext('2d'); + + if (dec.previewCalls == '') + context.strokeStyle=REGULAR; + else + context.strokeStyle=HIGHLIGHT; + context.lineWidth=4; + + context.beginPath(); + context.moveTo(dec.x, dec.y-19); + context.lineTo(dec.x+19, dec.y); + context.lineTo(dec.x, dec.y+19); + context.lineTo(dec.x-19, dec.y); + context.closePath(); + context.stroke(); +}; + +function drawPath(path) { + var context = canvas.getContext('2d'); + context.strokeStyle=REGULAR; + context.lineWidth=4; + + context.beginPath(); + context.moveTo(path[0].x1, path[0].y1); + for (var p = 0; p < path.length; p++) { + context.lineTo(path[p].x2, path[p].y2); + } + context.stroke(); +}; + +function getSeg(p1, p2, last) { + var seg = { + x1:cols[p1[0]], + y1:rows[p1.slice(1)] + }; + if (ends[p2]) { + seg.x2 = cols[ends[p2].col]; + seg.y2 = rows[ends[p2].row]; + } else { + seg.x2 = cols[p2[0]]; + seg.y2 = rows[p2.slice(1)]; + } + + if (seg.x1 == seg.x2) { + if (seg.y1 < seg.y2) { + seg.y1 = seg.y1+19; + if (last) seg.y2 = seg.y2-19; + } else { + seg.y1 = seg.y1-19; + if (last) seg.y2 = seg.y2+19; + } + } else { + //assume seg.y1 == seg.y2 + if (seg.x1 < seg.x2) { + seg.x1 = seg.x1+19; + if (last) seg.x2 = seg.x2-(ends[p2] ? (ends[p2].width/2) : 19); + } else { + seg.x1 = seg.x1-19; + if (last) seg.x2 = seg.x2+(ends[p2] ? (ends[p2].width/2) : 19); + } + } + return seg; +}; + +function traceDecision(name) { + for (var i = trace.length-1; i >= 0; i--) + if (trace[i].d == name) return trace[i]; +}; + +var detailPanels = {}; +function initDetailPanels() { + var windowWidth = document.getElementById('sizetest').clientWidth; + var infoPanel = document.getElementById('infopanel'); + var panelWidth = windowWidth-infoPanel.offsetLeft; + + var panels = { + 'request': document.getElementById('requestdetail'), + 'response': document.getElementById('responsedetail'), + 'decision': document.getElementById('decisiondetail') + }; + + var tabs = { + 'request': document.getElementById('requesttab'), + 'response': document.getElementById('responsetab'), + 'decision': document.getElementById('decisiontab') + }; + + var decisionId = document.getElementById('decisionid'); + var decisionCalls = document.getElementById('decisioncalls'); + var callInput = document.getElementById('callinput'); + var callOutput = document.getElementById('calloutput'); + + var lastUsedPanelWidth = windowWidth-infoPanel.offsetLeft; + + var setPanelWidth = function(width) { + infoPanel.style.left = (windowWidth-width)+'px'; + canvas.style.marginRight = (width+20)+'px'; + panelWidth = width; + }; + setPanelWidth(panelWidth); + + var ensureVisible = function() { + if (windowWidth-infoPanel.offsetLeft < 10) + setPanelWidth(lastUsedPanelWidth); + }; + + var decChoices = ''; + for (var i = 0; i < trace.length; i++) { + decChoices += ''; + } + decisionId.innerHTML = decChoices; + decisionId.selectedIndex = -1; + + decisionId.onchange = function() { + detailPanels.setDecision(traceDecision(decisionId.value)); + } + + detailPanels.setDecision = function(dec) { + decisionId.value = dec.d; + + var calls = []; + for (var i = 0; i < dec.calls.length; i++) { + calls.push(''); + } + decisionCalls.innerHTML = calls.join(''); + decisionCalls.selectedIndex = 0; + + decisionCalls.onchange(); + }; + + detailPanels.show = function(name) { + for (p in panels) { + if (p == name) { + panels[p].style.display = 'block'; + tabs[p].className = 'selectedtab'; + } + else { + panels[p].style.display = 'none'; + tabs[p].className = ''; + } + } + ensureVisible(); + }; + + detailPanels.hide = function() { + setPanelWidth(0); + } + + decisionCalls.onchange = function() { + var val = decisionCalls.value; + if (val) { + var dec = traceDecision(val.substring(0, val.indexOf('-'))); + var call = dec.calls[parseInt(val.substring(val.indexOf('-')+1, val.length))]; + + if (call.output != "wmtrace_not_exported") { + callInput.style.color='#000000'; + callInput.innerHTML = call.input; + if (call.output != null) { + callOutput.style.color = '#000000'; + callOutput.innerHTML = call.output; + } else { + callOutput.style.color = '#ff0000'; + callOutput.textContent = 'Error: '+call.module+':'+call['function']+' never returned'; + } + } else { + callInput.style.color='#999999'; + callInput.textContent = call.module+':'+call['function']+' was not exported'; + callOutput.textContent = ''; + } + } else { + callInput.textContent = ''; + callOutput.textContent = ''; + } + }; + + var headersList = function(headers) { + var h = ''; + for (n in headers) h += '
  • '+n+': '+headers[n]; + return h; + }; + + document.getElementById('requestmethod').innerHTML = request.method; + document.getElementById('requestpath').innerHTML = request.path; + document.getElementById('requestheaders').innerHTML = headersList(request.headers); + document.getElementById('requestbody').innerHTML = request.body; + + document.getElementById('responsecode').innerHTML = response.code; + document.getElementById('responseheaders').innerHTML = headersList(response.headers); + document.getElementById('responsebody').innerHTML = response.body; + + + var infoControls = document.getElementById('infocontrols'); + var md = false; + var dragged = false; + var msoff = 0; + infoControls.onmousedown = function(ev) { + md = true; + dragged = false; + msoff = ev.clientX-infoPanel.offsetLeft; + }; + + infoControls.onclick = function(ev) { + if (dragged) { + lastUsedPanelWidth = panelWidth; + } + else if (panelWidth < 10) { + switch(ev.target.id) { + case 'requesttab': detailPanels.show('request'); break; + case 'responsetab': detailPanels.show('response'); break; + case 'decisiontab': detailPanels.show('decision'); break; + default: ensureVisible(); + } + } else { + var name = 'none'; + switch(ev.target.id) { + case 'requesttab': name = 'request'; break; + case 'responsetab': name = 'response'; break; + case 'decisiontab': name = 'decision'; break; + } + + if (panels[name] && panels[name].style.display != 'block') + detailPanels.show(name); + else + detailPanels.hide(); + } + + return false; + }; + + document.onmousemove = function(ev) { + if (md) { + dragged = true; + panelWidth = windowWidth-(ev.clientX-msoff); + if (panelWidth < 0) { + panelWidth = 0; + infoPanel.style.left = windowWidth+"px"; + } + else if (panelWidth > windowWidth-21) { + panelWidth = windowWidth-21; + infoPanel.style.left = '21px'; + } + else + infoPanel.style.left = (ev.clientX-msoff)+"px"; + + canvas.style.marginRight = panelWidth+20+"px"; + return false; + } + }; + + document.onmouseup = function() { md = false; }; + + window.onresize = function() { + windowWidth = document.getElementById('sizetest').clientWidth; + infoPanel.style.left = windowWidth-panelWidth+'px'; + }; +}; + +window.onload = function() { + canvas = document.getElementById('v3map'); + + initDetailPanels(); + + var scale = 0.25; + var coy = canvas.offsetTop; + function findDecision(ev) { + var x = (ev.clientX+window.pageXOffset)/scale; + var y = (ev.clientY+window.pageYOffset-coy)/scale; + + for (var i = trace.length-1; i >= 0; i--) { + if (x >= trace[i].x-19 && x <= trace[i].x+19 && + y >= trace[i].y-19 && y <= trace[i].y+19) + return trace[i]; + } + }; + + var preview = document.getElementById('preview'); + var previewId = document.getElementById('previewid'); + var previewCalls = document.getElementById('previewcalls'); + function previewDecision(dec) { + preview.style.left = (dec.x*scale)+'px'; + preview.style.top = (dec.y*scale+coy+15)+'px'; + preview.style.display = 'block'; + previewId.textContent = dec.d; + + previewCalls.innerHTML = dec.previewCalls; + }; + + function overResponse(ev) { + var x = (ev.clientX+window.pageXOffset)/scale; + var y = (ev.clientY+window.pageYOffset-coy)/scale; + + return (x >= response.x-(response.width/2) + && x <= response.x+(response.width/2) + && y >= response.y-19 && y <= response.y+19); + }; + + decorateTrace(); + + var bg = new Image(3138, 2184); + + function drawMap() { + var ctx = canvas.getContext("2d"); + + ctx.save(); + ctx.scale(1/scale, 1/scale); + ctx.fillStyle = '#ffffff'; + ctx.fillRect(0, 0, 3138, 2184); + ctx.restore(); + + ctx.drawImage(bg, 0, 0); + drawTrace(); + }; + + bg.onload = function() { + canvas.getContext("2d").scale(scale, scale); + drawMap(scale); + + canvas.onmousemove = function(ev) { + if (findDecision(ev)) { + canvas.style.cursor = 'pointer'; + previewDecision(findDecision(ev)); + } + else { + preview.style.display = 'none'; + if (overResponse(ev)) + canvas.style.cursor = 'pointer'; + else + canvas.style.cursor = 'default'; + } + }; + + canvas.onclick = function(ev) { + var dec = findDecision(ev); + if (dec) { + detailPanels.setDecision(dec); + detailPanels.show('decision'); + } else if (overResponse(ev)) { + detailPanels.show('response'); + } + }; + + document.getElementById('zoomin').onclick = function() { + scale = scale*2; + canvas.getContext("2d").scale(2, 2); + drawMap(); + }; + + document.getElementById('zoomout').onclick = function() { + scale = scale/2; + canvas.getContext("2d").scale(0.5, 0.5); + drawMap(); + }; + }; + + bg.onerror = function() { + alert('Failed to load background image.'); + }; + + bg.src = 'static/map.png'; +}; diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/www/index.html b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/www/index.html new file mode 100644 index 0000000..e999208 --- /dev/null +++ b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/priv/www/index.html @@ -0,0 +1,8 @@ + + +It Worked + + +Running. + + diff --git a/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/rebar b/rabbitmq-server-3.3.5/plugins-src/webmachine-wrapper/webmachine-git/rebar new file mode 100755 index 0000000000000000000000000000000000000000..7bdb274306991818770baacbec3a76beff5befe6 GIT binary patch literal 119212 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

    +hL3E4;MWk>7 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