Merge default and resolve clashes.
This commit is contained in:
		
						commit
						769254ae61
					
				| 
						 | 
				
			
			@ -18,6 +18,7 @@
 | 
			
		|||
-define(HEADER_ACK, "ack").
 | 
			
		||||
-define(HEADER_AMQP_MESSAGE_ID, "amqp-message-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").
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,7 @@
 | 
			
		|||
-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,
 | 
			
		||||
| 
						 | 
				
			
			@ -30,69 +31,130 @@
 | 
			
		|||
 | 
			
		||||
initial_state() -> none.
 | 
			
		||||
 | 
			
		||||
parse(Content, {resume, Fun}) -> Fun(Content);
 | 
			
		||||
parse(Content, none)          -> parse_command(Content, []).
 | 
			
		||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 | 
			
		||||
%% 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.
 | 
			
		||||
%%
 | 
			
		||||
%%  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
 | 
			
		||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 | 
			
		||||
 | 
			
		||||
parse_command(<<>>, Acc) ->
 | 
			
		||||
    more(fun(Rest) -> parse_command(Rest, Acc) end);
 | 
			
		||||
parse_command(<<$\n, Rest/binary>>, []) ->  % inter-frame newline
 | 
			
		||||
    parse_command(Rest, []);
 | 
			
		||||
parse_command(<<0, Rest/binary>>, []) ->    % empty frame
 | 
			
		||||
    parse_command(Rest, []);
 | 
			
		||||
parse_command(<<$\n, Rest/binary>>, Acc) -> % end command
 | 
			
		||||
    parse_headers(Rest, lists:reverse(Acc));
 | 
			
		||||
parse_command(<<Ch:8, Rest/binary>>, Acc) ->
 | 
			
		||||
    parse_command(Rest, [Ch | Acc]).
 | 
			
		||||
%% explicit frame characters
 | 
			
		||||
-define(NUL,   0).
 | 
			
		||||
-define(CR,    $\r).
 | 
			
		||||
-define(LF,    $\n).
 | 
			
		||||
-define(BSL,   $\\).
 | 
			
		||||
-define(COLON, $:).
 | 
			
		||||
 | 
			
		||||
parse_headers(Rest, Command) -> % begin headers
 | 
			
		||||
    parse_headers(Rest, #stomp_frame{command = Command}, [], []).
 | 
			
		||||
%% header escape codes
 | 
			
		||||
-define(LF_ESC,    $n).
 | 
			
		||||
-define(BSL_ESC,   $\\).
 | 
			
		||||
-define(COLON_ESC, $c).
 | 
			
		||||
-define(CR_ESC,    $r).
 | 
			
		||||
 | 
			
		||||
parse_headers(<<>>, Frame, HeaderAcc, KeyAcc) ->
 | 
			
		||||
    more(fun(Rest) -> parse_headers(Rest, Frame, HeaderAcc, KeyAcc) end);
 | 
			
		||||
parse_headers(<<$\n, Rest/binary>>, Frame, HeaderAcc, _KeyAcc) -> % end headers
 | 
			
		||||
    parse_body(Rest, Frame#stomp_frame{headers = HeaderAcc});
 | 
			
		||||
parse_headers(<<$:, Rest/binary>>, Frame, HeaderAcc, KeyAcc) ->   % end key
 | 
			
		||||
    parse_header_value(Rest, Frame, HeaderAcc, lists:reverse(KeyAcc));
 | 
			
		||||
parse_headers(<<Ch:8, Rest/binary>>, Frame, HeaderAcc, KeyAcc) ->
 | 
			
		||||
    parse_headers(Rest, Frame, HeaderAcc, [Ch | KeyAcc]).
 | 
			
		||||
%% parser state
 | 
			
		||||
-record(state, {acc, cmd, hdrs, hdrname}).
 | 
			
		||||
 | 
			
		||||
parse_header_value(Rest, Frame, HeaderAcc, Key) -> % begin header value
 | 
			
		||||
    parse_header_value(Rest, Frame, HeaderAcc, Key, []).
 | 
			
		||||
parse(Content, {resume, Continuation}) -> Continuation(Content);
 | 
			
		||||
parse(Content, none                  ) -> parser(Content, noframe, #state{}).
 | 
			
		||||
 | 
			
		||||
parse_header_value(<<>>, Frame, HeaderAcc, Key, ValAcc) ->
 | 
			
		||||
    more(fun(Rest) -> parse_header_value(Rest, Frame, HeaderAcc, Key, ValAcc)
 | 
			
		||||
         end);
 | 
			
		||||
parse_header_value(<<$\n, Rest/binary>>, Frame, HeaderAcc, Key, ValAcc) ->
 | 
			
		||||
                                                                  % end value
 | 
			
		||||
    parse_headers(Rest, Frame,
 | 
			
		||||
                  insert_header(HeaderAcc, Key, lists:reverse(ValAcc)),
 | 
			
		||||
                  []);
 | 
			
		||||
parse_header_value(<<$\\, Rest/binary>>, Frame, HeaderAcc, Key, ValAcc) ->
 | 
			
		||||
    parse_header_value_escape(Rest, Frame, HeaderAcc, Key, ValAcc);
 | 
			
		||||
parse_header_value(<<Ch:8, Rest/binary>>, Frame, HeaderAcc, Key, ValAcc) ->
 | 
			
		||||
    parse_header_value(Rest, Frame, HeaderAcc, Key, [Ch | ValAcc]).
 | 
			
		||||
more(Continuation) -> {more, {resume, Continuation}}.
 | 
			
		||||
 | 
			
		||||
parse_header_value_escape(<<>>, Frame, HeaderAcc, Key, ValAcc) ->
 | 
			
		||||
    more(fun(Rest) ->
 | 
			
		||||
           parse_header_value_escape(Rest, Frame, HeaderAcc, Key, ValAcc)
 | 
			
		||||
         end);
 | 
			
		||||
parse_header_value_escape(<<Ch:8,  Rest/binary>>, Frame,
 | 
			
		||||
                          HeaderAcc, Key, ValAcc) ->
 | 
			
		||||
    case unescape(Ch) of
 | 
			
		||||
        {ok, EscCh} -> parse_header_value(Rest, Frame, HeaderAcc, Key,
 | 
			
		||||
                                          [EscCh | ValAcc]);
 | 
			
		||||
        error       -> {error, {bad_escape, Ch}}
 | 
			
		||||
    end.
 | 
			
		||||
%% 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(<<?CR>>,                     Term    ,  State) -> more(fun(Rest) -> parser(<<?CR, Rest/binary>>, Term, State) end);
 | 
			
		||||
parser(<<?CR, ?LF,   Rest/binary>>, Term    ,  State) -> parser(<<?LF, Rest/binary>>, Term, State);
 | 
			
		||||
parser(<<?CR, Ch:8, _Rest/binary>>, Term    , _State) -> {error, {unexpected_chars(Term), [?CR, Ch]}};
 | 
			
		||||
%% escape processing (only in hdrname and hdrvalue terms)
 | 
			
		||||
parser(<<?BSL>>,                    Term    ,  State) -> more(fun(Rest) -> parser(<<?BSL, Rest/binary>>, Term, State) end);
 | 
			
		||||
parser(<<?BSL, Ch:8, Rest/binary>>, Term    ,  State)
 | 
			
		||||
                               when Term == hdrname;
 | 
			
		||||
                                    Term == hdrvalue  -> unescape(Ch, fun(Ech) -> parser(Rest, Term, accum(Ech, State)) end);
 | 
			
		||||
%% inter-frame noise
 | 
			
		||||
parser(<<?NUL,       Rest/binary>>, noframe ,  State) -> parser(Rest, noframe, State);
 | 
			
		||||
parser(<<?LF,        Rest/binary>>, noframe ,  State) -> parser(Rest, noframe, State);
 | 
			
		||||
%% detect transitions
 | 
			
		||||
parser(              Rest,          noframe ,  State) -> goto(noframe,  command,  Rest, State);
 | 
			
		||||
parser(<<?LF,        Rest/binary>>, command ,  State) -> goto(command,  headers,  Rest, State);
 | 
			
		||||
parser(<<?LF,        Rest/binary>>, headers ,  State) -> goto(headers,  body,     Rest, State);
 | 
			
		||||
parser(              Rest,          headers ,  State) -> goto(headers,  hdrname,  Rest, State);
 | 
			
		||||
parser(<<?COLON,     Rest/binary>>, hdrname ,  State) -> goto(hdrname,  hdrvalue, Rest, State);
 | 
			
		||||
parser(<<?LF,        Rest/binary>>, hdrname ,  State) -> goto(hdrname,  headers,  Rest, State);
 | 
			
		||||
parser(<<?LF,        Rest/binary>>, hdrvalue,  State) -> goto(hdrvalue, headers,  Rest, State);
 | 
			
		||||
%% trap invalid colons
 | 
			
		||||
parser(<<?COLON,     Rest/binary>>, hdrvalue,  State) -> {error, {unexpected_char_in_header_value, [?COLON]}};
 | 
			
		||||
%% accumulate
 | 
			
		||||
parser(<<Ch:8,       Rest/binary>>, Term    ,  State) -> parser(Rest, Term, accum(Ch, State)).
 | 
			
		||||
 | 
			
		||||
insert_header(Headers, Key, Value) ->
 | 
			
		||||
    case lists:keysearch(Key, 1, Headers) of
 | 
			
		||||
        {value, _} -> Headers; % first header only
 | 
			
		||||
        false      -> [{Key, Value} | Headers]
 | 
			
		||||
%% 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, "content-length", unknown)).
 | 
			
		||||
               integer_header(Frame, ?HEADER_CONTENT_LENGTH, unknown)).
 | 
			
		||||
 | 
			
		||||
parse_body(Content, Frame, Chunks, unknown) ->
 | 
			
		||||
    parse_body2(Content, Frame, Chunks, case firstnull(Content) of
 | 
			
		||||
| 
						 | 
				
			
			@ -117,8 +179,6 @@ parse_body2(Content, Frame, Chunks, {done, Pos}) ->
 | 
			
		|||
finalize_chunk(<<>>,  Chunks) -> Chunks;
 | 
			
		||||
finalize_chunk(Chunk, Chunks) -> [Chunk | Chunks].
 | 
			
		||||
 | 
			
		||||
more(Continuation) -> {more, {resume, Continuation}}.
 | 
			
		||||
 | 
			
		||||
default_value({ok, Value}, _DefaultValue) -> Value;
 | 
			
		||||
default_value(not_found,    DefaultValue) -> DefaultValue.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -162,27 +222,27 @@ serialize(#stomp_frame{command = Command,
 | 
			
		|||
                       headers = Headers,
 | 
			
		||||
                       body_iolist = BodyFragments}) ->
 | 
			
		||||
    Len = iolist_size(BodyFragments),
 | 
			
		||||
    [Command, $\n,
 | 
			
		||||
    [Command, ?LF,
 | 
			
		||||
     lists:map(fun serialize_header/1,
 | 
			
		||||
               lists:keydelete("content-length", 1, Headers)),
 | 
			
		||||
               lists:keydelete(?HEADER_CONTENT_LENGTH, 1, Headers)),
 | 
			
		||||
     if
 | 
			
		||||
         Len > 0 -> ["content-length:", integer_to_list(Len), $\n];
 | 
			
		||||
         Len > 0 -> [?HEADER_CONTENT_LENGTH ++ ":", integer_to_list(Len), ?LF];
 | 
			
		||||
         true    -> []
 | 
			
		||||
     end,
 | 
			
		||||
     $\n, BodyFragments, 0].
 | 
			
		||||
     ?LF, BodyFragments, 0].
 | 
			
		||||
 | 
			
		||||
serialize_header({K, V}) when is_integer(V) -> [K, $:, integer_to_list(V), $\n];
 | 
			
		||||
serialize_header({K, V}) when is_list(V) -> [K, $:, [escape(C) || C <- V], $\n].
 | 
			
		||||
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)).
 | 
			
		||||
 | 
			
		||||
unescape($n)  -> {ok, $\n};
 | 
			
		||||
unescape($\\) -> {ok, $\\};
 | 
			
		||||
unescape($c)  -> {ok, $:};
 | 
			
		||||
unescape(_)   -> error.
 | 
			
		||||
hdr(K, V) -> [K, ?COLON, V, ?LF].
 | 
			
		||||
 | 
			
		||||
escape($:)  -> "\\c";
 | 
			
		||||
escape($\\) -> "\\\\";
 | 
			
		||||
escape($\n) -> "\\n";
 | 
			
		||||
escape(C)   -> C.
 | 
			
		||||
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).
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -196,13 +196,13 @@ process_connect(Implicit, Frame,
 | 
			
		|||
                      {Username, Creds} = creds(Frame1, SSLLoginName, Config),
 | 
			
		||||
                      {ok, DefaultVHost} =
 | 
			
		||||
                          application:get_env(rabbit, default_vhost),
 | 
			
		||||
                      {ProtoName, _} = AdapterInfo#adapter_info.protocol,
 | 
			
		||||
                      {ProtoName, _} = AdapterInfo#amqp_adapter_info.protocol,
 | 
			
		||||
                      Res = do_login(
 | 
			
		||||
                              Username, Creds,
 | 
			
		||||
                              login_header(Frame1, ?HEADER_HOST, DefaultVHost),
 | 
			
		||||
                              login_header(Frame1, ?HEADER_HEART_BEAT, "0,0"),
 | 
			
		||||
                                AdapterInfo#adapter_info{
 | 
			
		||||
                                  protocol = {ProtoName, Version}}, Version,
 | 
			
		||||
                              AdapterInfo#amqp_adapter_info{
 | 
			
		||||
                                protocol = {ProtoName, Version}}, Version,
 | 
			
		||||
                              StateN#state{frame_transformer = FT}),
 | 
			
		||||
                      case {Res, Implicit} of
 | 
			
		||||
                          {{ok, _, StateN1}, implicit} -> ok(StateN1);
 | 
			
		||||
| 
						 | 
				
			
			@ -500,21 +500,22 @@ do_login(Username, Creds, VirtualHost, Heartbeat, AdapterInfo, Version,
 | 
			
		|||
                {error, auth_failure} ->
 | 
			
		||||
                    rabbit_log:error("STOMP login failed - auth_failure "
 | 
			
		||||
                                     "(user vanished)~n"),
 | 
			
		||||
                    error("Bad CONNECT", "Authentication failure", State);
 | 
			
		||||
                    error("Bad CONNECT", "User failure after authentication", State);
 | 
			
		||||
                {error, access_refused} ->
 | 
			
		||||
                    rabbit_log:warning("STOMP login failed - access_refused "
 | 
			
		||||
                                       "(vhost access not allowed)~n"),
 | 
			
		||||
                    error("Bad CONNECT", "Authentication failure", State)
 | 
			
		||||
                    error("Bad CONNECT", "Virtual host '" ++
 | 
			
		||||
                                         binary_to_list(VirtualHost) ++
 | 
			
		||||
                                         "' access denied", State)
 | 
			
		||||
            end;
 | 
			
		||||
        {refused, Msg, Args} ->
 | 
			
		||||
            rabbit_log:warning("STOMP login failed: " ++ Msg ++ "~n", Args),
 | 
			
		||||
            error("Bad CONNECT", "Authentication failure", State)
 | 
			
		||||
            error("Bad CONNECT", "Access refused: " ++ Msg ++ "~n", Args, State)
 | 
			
		||||
    end.
 | 
			
		||||
 | 
			
		||||
server_header() ->
 | 
			
		||||
    Props = rabbit_reader:server_properties(?PROTOCOL),
 | 
			
		||||
    {_, Product} = rabbit_misc:table_lookup(Props, <<"product">>),
 | 
			
		||||
    {_, Version} = rabbit_misc:table_lookup(Props, <<"version">>),
 | 
			
		||||
    {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,
 | 
			
		||||
| 
						 | 
				
			
			@ -962,7 +963,7 @@ ok(Command, Headers, BodyFragments, State) ->
 | 
			
		|||
                      body_iolist = BodyFragments}, State}.
 | 
			
		||||
 | 
			
		||||
amqp_death(ReplyCode, Explanation, State) ->
 | 
			
		||||
    ErrorName = ?PROTOCOL:amqp_exception(ReplyCode),
 | 
			
		||||
    ErrorName = amqp_connection:error_atom(ReplyCode),
 | 
			
		||||
    ErrorDesc = rabbit_misc:format("~s~n", [Explanation]),
 | 
			
		||||
    log_error(ErrorName, ErrorDesc, none),
 | 
			
		||||
    {stop, normal, send_error(atom_to_list(ErrorName), ErrorDesc, State)}.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -174,13 +174,13 @@ adapter_info(Sock) ->
 | 
			
		|||
               {ok, Res3} -> Res3;
 | 
			
		||||
               _          -> unknown
 | 
			
		||||
           end,
 | 
			
		||||
    #adapter_info{protocol        = {'STOMP', 0},
 | 
			
		||||
                  name            = list_to_binary(Name),
 | 
			
		||||
                  address         = Addr,
 | 
			
		||||
                  port            = Port,
 | 
			
		||||
                  peer_address    = PeerAddr,
 | 
			
		||||
                  peer_port       = PeerPort,
 | 
			
		||||
                  additional_info = maybe_ssl_info(Sock)}.
 | 
			
		||||
    #amqp_adapter_info{protocol        = {'STOMP', 0},
 | 
			
		||||
                       name            = list_to_binary(Name),
 | 
			
		||||
                       address         = Addr,
 | 
			
		||||
                       port            = Port,
 | 
			
		||||
                       peer_address    = PeerAddr,
 | 
			
		||||
                       peer_port       = PeerPort,
 | 
			
		||||
                       additional_info = maybe_ssl_info(Sock)}.
 | 
			
		||||
 | 
			
		||||
maybe_ssl_info(Sock) ->
 | 
			
		||||
    case rabbit_net:is_ssl(Sock) of
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,20 +80,20 @@ class TestLifecycle(base.BaseTest):
 | 
			
		|||
        ''' Test bad username'''
 | 
			
		||||
        self.bad_connect(stomp.Connection(user="gust",
 | 
			
		||||
                                          passcode="guest"),
 | 
			
		||||
                         "Authentication failure\n")
 | 
			
		||||
                         "Access refused: user 'gust' - invalid credentials\n")
 | 
			
		||||
 | 
			
		||||
    def test_bad_password(self):
 | 
			
		||||
        ''' Test bad password'''
 | 
			
		||||
        self.bad_connect(stomp.Connection(user="guest",
 | 
			
		||||
                                          passcode="gust"),
 | 
			
		||||
                         "Authentication failure\n")
 | 
			
		||||
                         "Access refused: user 'guest' - invalid credentials\n")
 | 
			
		||||
 | 
			
		||||
    def test_bad_vhost(self):
 | 
			
		||||
        ''' Test bad virtual host'''
 | 
			
		||||
        self.bad_connect(stomp.Connection(user="guest",
 | 
			
		||||
                                          passcode="guest",
 | 
			
		||||
                                          virtual_host="//"),
 | 
			
		||||
                         "Authentication failure\n")
 | 
			
		||||
                         "Virtual host '//' access denied\n")
 | 
			
		||||
 | 
			
		||||
    def bad_connect(self, new_conn, expected):
 | 
			
		||||
        self.conn.disconnect()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,7 +33,7 @@ test_messages_not_dropped_on_disconnect() ->
 | 
			
		|||
       [integer_to_list(Count)]) || Count <- lists:seq(1, 1000)],
 | 
			
		||||
    rabbit_stomp_client:disconnect(Client),
 | 
			
		||||
    QName = rabbit_misc:r(<<"/">>, queue, <<"bulk-test">>),
 | 
			
		||||
    timer:sleep(1000),
 | 
			
		||||
    timer:sleep(3000),
 | 
			
		||||
    rabbit_amqqueue:with(
 | 
			
		||||
      QName, fun(Q) ->
 | 
			
		||||
                     1000 = pget(messages, rabbit_amqqueue:info(Q, [messages]))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,12 +19,20 @@
 | 
			
		|||
-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"),
 | 
			
		||||
                           "Body Content",
 | 
			
		||||
                           Term),
 | 
			
		||||
    {"COMMAND", Frame, _State} = parse_complete(Content),
 | 
			
		||||
    [?assertEqual({ok, Value},
 | 
			
		||||
                  rabbit_stomp_frame:header(Frame, Key)) ||
 | 
			
		||||
| 
						 | 
				
			
			@ -34,7 +42,7 @@ parse_simple_frame_test() ->
 | 
			
		|||
 | 
			
		||||
parse_simple_frame_with_null_test() ->
 | 
			
		||||
    Headers = [{"header1", "value1"}, {"header2", "value2"},
 | 
			
		||||
               {"content-length", "12"}],
 | 
			
		||||
               {?HEADER_CONTENT_LENGTH, "12"}],
 | 
			
		||||
    Content = frame_string("COMMAND",
 | 
			
		||||
                           Headers,
 | 
			
		||||
                           "Body\0Content"),
 | 
			
		||||
| 
						 | 
				
			
			@ -48,7 +56,7 @@ parse_simple_frame_with_null_test() ->
 | 
			
		|||
parse_large_content_frame_with_nulls_test() ->
 | 
			
		||||
    BodyContent = string:copies("012345678\0", 1024),
 | 
			
		||||
    Headers = [{"header1", "value1"}, {"header2", "value2"},
 | 
			
		||||
               {"content-length", integer_to_list(string:len(BodyContent))}],
 | 
			
		||||
               {?HEADER_CONTENT_LENGTH, integer_to_list(string:len(BodyContent))}],
 | 
			
		||||
    Content = frame_string("COMMAND",
 | 
			
		||||
                           Headers,
 | 
			
		||||
                           BodyContent),
 | 
			
		||||
| 
						 | 
				
			
			@ -68,11 +76,17 @@ parse_ignore_empty_frames_test() ->
 | 
			
		|||
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() ->
 | 
			
		||||
    {ok, #stomp_frame{command = "\rCOMMAND"}, _Rest} = parse("\rCOMMAND\n\n\0").
 | 
			
		||||
    {error, {unexpected_chars_between_frames, "\rC"}} = parse("\rCOMMAND\n\n\0").
 | 
			
		||||
 | 
			
		||||
parse_carriage_return_mid_command_test() ->
 | 
			
		||||
    {ok, #stomp_frame{command = "COMM\rAND"}, _Rest} = parse("COMM\rAND\n\n\0").
 | 
			
		||||
    {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",
 | 
			
		||||
| 
						 | 
				
			
			@ -118,11 +132,41 @@ parse_multiple_headers_test() ->
 | 
			
		|||
    {ok, Val} = rabbit_stomp_frame:header(Frame, "header"),
 | 
			
		||||
    ?assertEqual("correct", Val).
 | 
			
		||||
 | 
			
		||||
headers_escaping_roundtrip_test() ->
 | 
			
		||||
    Content = "COMMAND\nheader:\\c\\n\\\\\n\n\0",
 | 
			
		||||
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),
 | 
			
		||||
    {ok, Val} = rabbit_stomp_frame:header(Frame, "header"),
 | 
			
		||||
    ?assertEqual(":\n\\", Val),
 | 
			
		||||
    ?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",
 | 
			
		||||
    {error, {unexpected_char_in_header_value, ":"}} = parse(Content).
 | 
			
		||||
 | 
			
		||||
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])).
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -136,7 +180,10 @@ parse_complete(Content) ->
 | 
			
		|||
    {Command, Frame, State}.
 | 
			
		||||
 | 
			
		||||
frame_string(Command, Headers, BodyContent) ->
 | 
			
		||||
    HeaderString =
 | 
			
		||||
        lists:flatten([Key ++ ":" ++ Value ++ "\n" || {Key, Value} <- Headers]),
 | 
			
		||||
    Command ++ "\n" ++ HeaderString ++ "\n" ++ BodyContent ++ "\0".
 | 
			
		||||
    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".
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue