<commit_before>-module(mochiweb_websocket).
-author('<EMAIL>').

%% The MIT License (MIT)

%% Copyright (c) 2012 Zadane.pl sp. z o.o.

%% 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.

%% @doc Websockets module for Mochiweb. Based on Misultin websockets module.

-export([loop/5, upgrade_connection/2, request/5]).
-export([send/3]).

loop(Socket, Body, State, WsVersion, ReplyChannel) ->
    ok = mochiweb_socket:setopts(Socket, [{packet, 0}, {active, once}]),
    proc_lib:hibernate(?MODULE, request, [Socket, Body, State, WsVersion, ReplyChannel]).

request(Socket, Body, State, WsVersion, ReplyChannel) ->
    receive
        {tcp_closed, _} ->
            mochiweb_socket:close(Socket),
            exit(normal);
        {ssl_closed, _} ->
            mochiweb_socket:close(Socket),
            exit(normal);
        {tcp_error, _, _} ->
            mochiweb_socket:close(Socket),
            exit(normal);

        {tcp, _, WsFrames} ->
            {M, F} = Body,
            case parse_frames(WsVersion, WsFrames, Socket) of
                close ->
                    mochiweb_socket:close(Socket),
                    exit(normal);

                error ->
                    mochiweb_socket:close(Socket),
                    exit(normal);

                Payload ->
                    NewState = M:F(Payload, State, ReplyChannel),
                    loop(Socket, Body, NewState, WsVersion, ReplyChannel)
            end;

        _ ->
            mochiweb_socket:close(Socket),
            exit(normal)
    end.

send(Socket, Payload, hybi) ->
    Len = payload_length(iolist_size(Payload)),
    Data = <<1:1, 0:3, 1:4, 0:1, Len/bits, Payload/binary>>,
    mochiweb_socket:send(Socket, Data);

send(Socket, Payload, hixie) ->
    Data = <<0, Payload/binary, 255>>,
    mochiweb_socket:send(Socket, Data).

upgrade_connection(Req, Body) ->
    case make_handshake(Req) of
        {Version, Response} ->
            Req:respond(Response),

            Socket = Req:get(socket),
            ReplyChannel = fun(Payload) ->
                ?MODULE:send(Socket, Payload, Version)
            end,
            Reentry = fun (State) ->
                ?MODULE:loop(Socket, Body, State, Version, ReplyChannel)
            end,
            {Reentry, ReplyChannel};

        _ ->
            mochiweb_socket:close(Req:get(socket)),
            exit(normal)
    end.

make_handshake(Req) ->
    SecKey  = Req:get_header_value("sec-websocket-key"),
    Sec1Key = Req:get_header_value("Sec-WebSocket-Key1"),
    Sec2Key = Req:get_header_value("Sec-WebSocket-Key2"),
    Origin = Req:get_header_value(origin),
    if not (SecKey == undefined) ->
          hybi_handshake(SecKey);

        (not (Sec1Key == undefined)) and (not (Sec2Key == undefined)) ->
            Host = Req:get_header_value("Host"),
            Path = Req:get(path),
            Body = Req:recv(8),
            hixie_handshake(Host, Path, Sec1Key, Sec2Key, Body, Origin);

       true ->
          error
    end.

hybi_handshake(SecKey) ->
    BinKey = list_to_binary(SecKey),
    Bin = <<BinKey/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11">>,
    Challenge = base64:encode(crypto:hash(sha, Bin)),

    Response = {101, [{"Connection", "Upgrade"},
                      {"Upgrade", "websocket"},
                      {"Sec-Websocket-Accept", Challenge}], ""},
    {hybi, Response}.

hixie_handshake(Host, Path, Key1, Key2, Body, Origin) ->
  Ikey1 = [D || D <- Key1, $0 =< D, D =< $9],
  Ikey2 = [D || D <- Key2, $0 =< D, D =< $9],
  Blank1 = length([D || D <- Key1, D =:= 32]),
  Blank2 = length([D || D <- Key2, D =:= 32]),
  Part1 = erlang:list_to_integer(Ikey1) div Blank1,
  Part2 = erlang:list_to_integer(Ikey2) div Blank2,
  Ckey = <<Part1:4/big-unsigned-integer-unit:8, Part2:4/big-unsigned-integer-unit:8, Body/binary>>,
  Challenge = erlang:md5(Ckey),

  Location = lists:concat(["ws://", Host, Path]),

  Response = {101, [{"Upgrade", "WebSocket"},
                     {"Connection", "Upgrade"},
                     {"Sec-WebSocket-Origin", Origin},
                     {"Sec-WebSocket-Location", Location}],
                    Challenge},
  {hixie, Response}.

parse_frames(hybi, Frames, Socket) ->
    try parse_hybi_frames(Socket, Frames, []) of
        Parsed -> process_frames(Parsed, [])
    catch
        _:_ -> error
    end;

parse_frames(hixie, Frames, Socket) ->
    try parse_hixie_frames(Frames, []) of
        Payload -> Payload
    catch
        _:_ -> error
    end.

%%
%% Websockets internal functions for RFC6455 and hybi draft
%%
process_frames([], Acc) ->
    lists:reverse(Acc);

process_frames([{Opcode, Payload} | Rest], Acc) ->
    case Opcode of
        8 -> close;
        _ ->
            process_frames(Rest, [Payload | Acc])
    end.

parse_hybi_frames(_, <<>>, Acc) ->
    lists:reverse(Acc);

parse_hybi_frames(S, <<_Fin:1,
               _Rsv:3,
               Opcode:4,
               _Mask:1,
               PayloadLen:7,
               MaskKey:4/binary,
               Payload:PayloadLen/binary-unit:8,<commit_msg>fix compiler warning<commit_after>-module(mochiweb_websocket).
-author('<EMAIL>').

%% The MIT License (MIT)

%% Copyright (c) 2012 Zadane.pl sp. z o.o.

%% 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.

%% @doc Websockets module for Mochiweb. Based on Misultin websockets module.

-export([loop/5, upgrade_connection/2, request/5]).
-export([send/3]).

loop(Socket, Body, State, WsVersion, ReplyChannel) ->
    ok = mochiweb_socket:setopts(Socket, [{packet, 0}, {active, once}]),
    proc_lib:hibernate(?MODULE, request, [Socket, Body, State, WsVersion, ReplyChannel]).

request(Socket, Body, State, WsVersion, ReplyChannel) ->
    receive
        {tcp_closed, _} ->
            mochiweb_socket:close(Socket),
            exit(normal);
        {ssl_closed, _} ->
            mochiweb_socket:close(Socket),
            exit(normal);
        {tcp_error, _, _} ->
            mochiweb_socket:close(Socket),
            exit(normal);

        {tcp, _, WsFrames} ->
            {M, F} = Body,
            case parse_frames(WsVersion, WsFrames, Socket) of
                close ->
                    mochiweb_socket:close(Socket),
                    exit(normal);

                error ->
                    mochiweb_socket:close(Socket),
                    exit(normal);

                Payload ->
                    NewState = M:F(Payload, State, ReplyChannel),
                    loop(Socket, Body, NewState, WsVersion, ReplyChannel)
            end;

        _ ->
            mochiweb_socket:close(Socket),
            exit(normal)
    end.

send(Socket, Payload, hybi) ->
    Len = payload_length(iolist_size(Payload)),
    Data = <<1:1, 0:3, 1:4, 0:1, Len/bits, Payload/binary>>,
    mochiweb_socket:send(Socket, Data);

send(Socket, Payload, hixie) ->
    Data = <<0, Payload/binary, 255>>,
    mochiweb_socket:send(Socket, Data).

upgrade_connection(Req, Body) ->
    case make_handshake(Req) of
        {Version, Response} ->
            Req:respond(Response),

            Socket = Req:get(socket),
            ReplyChannel = fun(Payload) ->
                ?MODULE:send(Socket, Payload, Version)
            end,
            Reentry = fun (State) ->
                ?MODULE:loop(Socket, Body, State, Version, ReplyChannel)
            end,
            {Reentry, ReplyChannel};

        _ ->
            mochiweb_socket:close(Req:get(socket)),
            exit(normal)
    end.

make_handshake(Req) ->
    SecKey  = Req:get_header_value("sec-websocket-key"),
    Sec1Key = Req:get_header_value("Sec-WebSocket-Key1"),
    Sec2Key = Req:get_header_value("Sec-WebSocket-Key2"),
    Origin = Req:get_header_value(origin),
    if not (SecKey == undefined) ->
          hybi_handshake(SecKey);

        (not (Sec1Key == undefined)) and (not (Sec2Key == undefined)) ->
            Host = Req:get_header_value("Host"),
            Path = Req:get(path),
            Body = Req:recv(8),
            hixie_handshake(Host, Path, Sec1Key, Sec2Key, Body, Origin);

       true ->
          error
    end.

hybi_handshake(SecKey) ->
    BinKey = list_to_binary(SecKey),
    Bin = <<BinKey/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11">>,
    Challenge = base64:encode(crypto:hash(sha, Bin)),

    Response = {101, [{"Connection", "Upgrade"},
                      {"Upgrade", "websocket"},
                      {"Sec-Websocket-Accept", Challenge}], ""},
    {hybi, Response}.

hixie_handshake(Host, Path, Key1, Key2, Body, Origin) ->
  Ikey1 = [D || D <- Key1, $0 =< D, D =< $9],
  Ikey2 = [D || D <- Key2, $0 =< D, D =< $9],
  Blank1 = length([D || D <- Key1, D =:= 32]),
  Blank2 = length([D || D <- Key2, D =:= 32]),
  Part1 = erlang:list_to_integer(Ikey1) div Blank1,
  Part2 = erlang:list_to_integer(Ikey2) div Blank2,
  Ckey = <<Part1:4/big-unsigned-integer-unit:8, Part2:4/big-unsigned-integer-unit:8, Body/binary>>,
  Challenge = erlang:md5(Ckey),

  Location = lists:concat(["ws://", Host, Path]),

  Response = {101, [{"Upgrade", "WebSocket"},
                     {"Connection", "Upgrade"},
                     {"Sec-WebSocket-Origin", Origin},
                     {"Sec-WebSocket-Location", Location}],
                    Challenge},
  {hixie, Response}.

parse_frames(hybi, Frames, Socket) ->
    try parse_hybi_frames(Socket, Frames, []) of
        Parsed -> process_frames(Parsed, [])
    catch
        _:_ -> error
    end;

parse_frames(hixie, Frames, _Socket) ->
    try parse_hixie_frames(Frames, []) of
        Payload -> Payload
    catch
        _:_ -> error
    end.

%%
%% Websockets internal functions for RFC6455 and hybi draft
%%
process_frames([], Acc) ->
    lists:reverse(Acc);

process_frames([{Opcode, Payload} | Rest], Acc) ->
    case Opcode of
        8 -> close;
        _ ->
            process_frames(Rest, [Payload | Acc])
    end.

parse_hybi_frames(_, <<>>, Acc) ->
    lists:reverse(Acc);

parse_hybi_frames(S, <<_Fin:1,
               _Rsv:3,
               Opcode:4,
               _Mask:1,
               PayloadLen:7,
               MaskKey:4/binary,
               Payload:PayloadLen/binary-unit:8,
{'fraction_non_alphanumeric': 0.13227775028863598, 'fraction_numerical': 0.016163615371928088, 'mean_word_length': 2.4757810260819717, 'pattern_counts': {'":': 0, '<': 45, '<?xml version=': 0, '>': 93, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '91065', 'n_tokens_mistral': 3929, 'n_tokens_neox': 3740, 'n_words': 967}