From 98b443a41f3d2043f8a05b94382012019d53535b Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Sun, 6 Feb 2011 22:37:39 -0500 Subject: [PATCH 01/14] Switch regexp module to re and fix obsolete guard --- elibs/reg.erl | 2 +- elibs/server.erl | 15 ++++++++------- elibs/upload_pack.erl | 8 ++++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/elibs/reg.erl b/elibs/reg.erl index 4438a68..a7e36fa 100644 --- a/elibs/reg.erl +++ b/elibs/reg.erl @@ -818,7 +818,7 @@ gsub_bin(Bin, P, Nfa, Rep) -> split(S, " ") -> split(S, "[ \t]+", true); %This is really special! split(S, Regexp) -> split(S, Regexp, false). -split(S, Regexp, Trim) when list(Regexp) -> +split(S, Regexp, Trim) when is_list(Regexp) -> case parse(Regexp) of {ok,RE} -> split(S, RE, Trim); {error,E} -> {error,E} diff --git a/elibs/server.erl b/elibs/server.erl index 13df068..45dcf79 100644 --- a/elibs/server.erl +++ b/elibs/server.erl @@ -67,17 +67,18 @@ handle_method_dispatch(invalid, Sock, _Host, _Header) -> ok = gen_tcp:close(Sock). extract_method_name(Header) -> - case regexp:match(Header, "....git[ -][a-z\-]+ ") of - {match, Start, Length} -> - {ok, string:substr(Header, Start + 8, Length - 9)}; + case re:run(Header, "....git[ -][a-z\\-]+ ", []) of + {match, [{Start, Length}]} -> + {ok, string:substr(Header, Start + 9, Length - 9)}; _Else -> invalid end. extract_host(Header) -> - case regexp:match(string:to_lower(Header), "\000host=[^\000]+\000") of - {match, Start, Length} -> - {ok, string:substr(Header, Start + 6, Length - 7)}; + case re:run(string:to_lower(Header), "\\000host=[^\\000]+\\000", []) of + {match, [{Start, Length}]} -> + io:format("Header: ~p Start ~p Length ~p~n", [Header, Start, Length]), + {ok, string:substr(Header, Start + 7, Length - 7)}; _Else -> {ok, "invalid"} - end. \ No newline at end of file + end. diff --git a/elibs/upload_pack.erl b/elibs/upload_pack.erl index cab5a2e..a084170 100644 --- a/elibs/upload_pack.erl +++ b/elibs/upload_pack.erl @@ -21,9 +21,9 @@ handle(Sock, Host, Header) -> % Extract the repo from the header. extract_repo_path(Sock, Host, Header) -> - case regexp:match(Header, " /[^\000]+\000") of - {match, Start, Length} -> - Path = string:substr(Header, Start + 2, Length - 3), + case re:run(Header, " /[^\\000]+\\000", []) of + {match, [{Start, Length}]} -> + Path = string:substr(Header, Start + 3, Length - 3), convert_path(Sock, Host, Path); _Else -> invalid @@ -130,4 +130,4 @@ last_byte(Bin) -> Size = erlang:size(Bin), {_B1, B2} = split_binary(Bin, Size - 1), [Byte] = binary_to_list(B2), - Byte. \ No newline at end of file + Byte. From 471b4a7a492761d6b272cf5a46d197fb06e5e6bf Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Mon, 7 Feb 2011 00:50:56 -0500 Subject: [PATCH 02/14] Start refactoring client socket handling out into a gen_server --- elibs/git_client.erl | 65 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 elibs/git_client.erl diff --git a/elibs/git_client.erl b/elibs/git_client.erl new file mode 100644 index 0000000..e1e145a --- /dev/null +++ b/elibs/git_client.erl @@ -0,0 +1,65 @@ +-module(git_client). +-behaviour(gen_server). + +-record(state, { + socket + }). + +%% ------------------------------------------------------------------ +%% API Function Exports +%% ------------------------------------------------------------------ + +-export([start_link/1]). + +%% ------------------------------------------------------------------ +%% gen_server Function Exports +%% ------------------------------------------------------------------ + +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). + +%% ------------------------------------------------------------------ +%% API Function Definitions +%% ------------------------------------------------------------------ + +start_link(Socket) -> + gen_server:start(?MODULE, [Socket], []). + +%% ------------------------------------------------------------------ +%% gen_server Function Definitions +%% ------------------------------------------------------------------ + +init([Socket]) -> + inet:setopts(Socket, [{active, once}]), + {ok, #state{socket = Socket}}. + +handle_call(_Request, _From, State) -> + {noreply, ok, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info({_SocketType, Socket, Packet}, #state{socket = Socket} = State) -> + case Packet of + <<_:4/binary, "git", _:1/binary, Rest/binary>> -> + [Command, Args] = binary:split(Rest, <<" ">>), + io:format("git command ~p; args ~p~n", [Command, Args]), + {noreply, State}; + _ -> + gen_tcp:send(Socket, "Invalid method declaration. Upgrade to the latest git.\n"), + gen_tcp:close(Socket), + {stop, normal, State} + end; +handle_info(_Info, State) -> + io:format("unhandled info ~p~n", [_Info]), + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%% ------------------------------------------------------------------ +%% Internal Function Definitions +%% ------------------------------------------------------------------ + From 8db0f4303cbaad81dd38ffb46ae5245f4641674f Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Mon, 7 Feb 2011 01:49:30 -0500 Subject: [PATCH 03/14] Add method dispatching and validate upload-pack. Port is created but not used yet --- elibs/git_client.erl | 84 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 12 deletions(-) diff --git a/elibs/git_client.erl b/elibs/git_client.erl index e1e145a..b699701 100644 --- a/elibs/git_client.erl +++ b/elibs/git_client.erl @@ -2,7 +2,8 @@ -behaviour(gen_server). -record(state, { - socket + socket, + port }). %% ------------------------------------------------------------------ @@ -38,17 +39,17 @@ handle_call(_Request, _From, State) -> handle_cast(_Msg, State) -> {noreply, State}. -handle_info({_SocketType, Socket, Packet}, #state{socket = Socket} = State) -> - case Packet of - <<_:4/binary, "git", _:1/binary, Rest/binary>> -> - [Command, Args] = binary:split(Rest, <<" ">>), - io:format("git command ~p; args ~p~n", [Command, Args]), - {noreply, State}; - _ -> - gen_tcp:send(Socket, "Invalid method declaration. Upgrade to the latest git.\n"), - gen_tcp:close(Socket), - {stop, normal, State} - end; + +%handle_info({_SocketTyoe, Socket +handle_info({_SocketType, Socket, <<_Length:4/binary, "git", _:1/binary, Rest/binary>>}, #state{socket = Socket} = State) -> + [Method, Other] = binary:split(Rest, <<" ">>), + [Args, <<"host=", Host/binary>>, <<>>] = binary:split(Other, <<0>>, [global]), + io:format("git method ~p; args ~p host ~p~n", [Method, Args, Host]), + dispatch_method(Method, Host, Args, State); +handle_info({_SocketType, Socket, _Packet}, #state{socket = Socket} = State) -> + gen_tcp:send(Socket, "Invalid method declaration. Upgrade to the latest git.\n"), + gen_tcp:close(Socket), + {stop, normal, State}; handle_info(_Info, State) -> io:format("unhandled info ~p~n", [_Info]), {noreply, State}. @@ -63,3 +64,62 @@ code_change(_OldVsn, State, _Extra) -> %% Internal Function Definitions %% ------------------------------------------------------------------ +dispatch_method(<<"upload-pack">>, Host, Path, #state{socket = Sock} = State) -> + try conf:convert_path(binary_to_list(Host), binary_to_list(Path)) of + {ok, FullPath} -> + case repo_existance(FullPath) of + false -> + throw(nomatch); + RealPath -> + GitDaemonExportOkFilePath = filename:join([RealPath, "git-daemon-export-ok"]), + case filelib:is_regular(GitDaemonExportOkFilePath) of + true -> + %% all validated, yay + io:format("ready to do an upload-pack~n"), + Port = make_port(Sock, "upload-pack", Host, Path, RealPath), + {noreply, State#state{port = Port}}; + false -> + throw({noexport, RealPath}) + end + end; + {error, nomatch} -> + throw(nomatch) + catch + throw:nomatch -> + error_logger:info_msg("no repo match: ~p~n", [Path]), + gen_tcp:send(Sock, "003b\n*********'\n\nNo matching repositories found.\n\n*********"), + gen_tcp:close(Sock), + {stop, normal, State}; + throw:{noexport, RealPath} -> + error_logger:info_msg("permission denied to repo: ~p~n", [RealPath]), + gen_tcp:send(Sock, "0048\n*********'\n\nPermission denied. Repository is not public.\n\n*********"), + gen_tcp:close(Sock), + {stop, normal, State} + end; +dispatch_method(<<"receive-pack">>, _Host, _Args, #state{socket = Sock} = State) -> + %% TODO make this message include the actual repo + gen_tcp:send(Sock, "006d\n*********'\n\nYou can't push to git://github.com/user/repo.git\nUse git@github.com:user/repo.git\n\n*********"), + gen_tcp:close(Sock), + {stop, normal, State}; +dispatch_method(_Method, _Host, _Args, #state{socket = Sock} = State) -> + gen_tcp:send(Sock, "Invalid method declaration. Upgrade to the latest git.\n"), + gen_tcp:close(Sock), + {stop, normal, State}. + +repo_existance(Path) -> + case filelib:is_dir(Path) of + true -> + Path; + _ -> + case filelib:is_dir(Path ++ ".git") of + true -> + Path ++ ".git"; + _ -> + false + end + end. + +make_port(Sock, Method, _Host, _Path, FullPath) -> + Command = lists:flatten(["git ", Method, " ", FullPath]), + open_port({spawn, Command}, [binary]). + From 90fdbb761f80dc35d199a86af51159cf7c5545b9 Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Mon, 7 Feb 2011 10:41:39 -0500 Subject: [PATCH 04/14] Switch to the new gen_server based connection handler (a checkout works) --- elibs/git_client.erl | 16 ++++++++++++++-- elibs/server.erl | 5 +++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/elibs/git_client.erl b/elibs/git_client.erl index b699701..19e4173 100644 --- a/elibs/git_client.erl +++ b/elibs/git_client.erl @@ -40,7 +40,15 @@ handle_cast(_Msg, State) -> {noreply, State}. -%handle_info({_SocketTyoe, Socket +handle_info({Port, {data, Data}}, #state{socket = Sock, port = Port} = State) -> + %io:format("forwarding data to socket ~p~n", [Data]), + gen_tcp:send(Sock, Data), + {noreply, State}; +handle_info({_SocketType, Socket, Packet}, #state{socket = Socket, port = Port} = State) when is_port(Port) -> + %io:format("forwarding data to port ~p~n", [Packet]), + port_command(Port, Packet), + inet:setopts(Socket, [{active, once}]), + {noreply, State}; handle_info({_SocketType, Socket, <<_Length:4/binary, "git", _:1/binary, Rest/binary>>}, #state{socket = Socket} = State) -> [Method, Other] = binary:split(Rest, <<" ">>), [Args, <<"host=", Host/binary>>, <<>>] = binary:split(Other, <<0>>, [global]), @@ -50,6 +58,8 @@ handle_info({_SocketType, Socket, _Packet}, #state{socket = Socket} = State) -> gen_tcp:send(Socket, "Invalid method declaration. Upgrade to the latest git.\n"), gen_tcp:close(Socket), {stop, normal, State}; +handle_info({tcp_closed, Socket}, #state{socket = Socket} = State) -> + {stop, normal, State}; handle_info(_Info, State) -> io:format("unhandled info ~p~n", [_Info]), {noreply, State}. @@ -77,6 +87,7 @@ dispatch_method(<<"upload-pack">>, Host, Path, #state{socket = Sock} = State) -> %% all validated, yay io:format("ready to do an upload-pack~n"), Port = make_port(Sock, "upload-pack", Host, Path, RealPath), + inet:setopts(Sock, [{active, once}]), {noreply, State#state{port = Port}}; false -> throw({noexport, RealPath}) @@ -119,7 +130,8 @@ repo_existance(Path) -> end end. -make_port(Sock, Method, _Host, _Path, FullPath) -> +make_port(_Sock, Method, _Host, _Path, FullPath) -> Command = lists:flatten(["git ", Method, " ", FullPath]), + io:format("making port with command ~p~n", [Command]), open_port({spawn, Command}, [binary]). diff --git a/elibs/server.erl b/elibs/server.erl index 45dcf79..00191dc 100644 --- a/elibs/server.erl +++ b/elibs/server.erl @@ -28,7 +28,7 @@ init_log(undefined) -> try_listen(0) -> error_logger:info_msg("Could not listen on port 9418~n"); try_listen(Times) -> - Res = gen_tcp:listen(9418, [list, {packet, 0}, {active, false}]), + Res = gen_tcp:listen(9418, [binary, {packet, 0}, {active, false}]), case Res of {ok, LSock} -> error_logger:info_msg("Listening on port 9418~n"), @@ -41,7 +41,8 @@ try_listen(Times) -> loop(LSock) -> {ok, Sock} = gen_tcp:accept(LSock), - spawn(fun() -> handle_method(Sock) end), + {ok, Pid} = git_client:start_link(Sock), + gen_tcp:controlling_process(Sock, Pid), loop(LSock). handle_method(Sock) -> From 543f6a780a86c1ef51e424f9d1fea169cfe9650c Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Tue, 8 Feb 2011 00:47:20 -0500 Subject: [PATCH 05/14] Remove a bunch of unused code (and convert from reg.erl to re module) --- elibs/conf.erl | 10 +- elibs/pipe.erl | 98 --- elibs/receive_pack.erl | 6 - elibs/reg.erl | 1344 ---------------------------------------- elibs/server.erl | 38 -- elibs/upload_pack.erl | 133 ---- 6 files changed, 4 insertions(+), 1625 deletions(-) delete mode 100644 elibs/pipe.erl delete mode 100644 elibs/receive_pack.erl delete mode 100644 elibs/reg.erl delete mode 100644 elibs/upload_pack.erl diff --git a/elibs/conf.erl b/elibs/conf.erl index 98348ea..a99565b 100644 --- a/elibs/conf.erl +++ b/elibs/conf.erl @@ -10,9 +10,8 @@ read_conf(Conf) -> convert_path(Host, Path) -> [{Host, {Regex, Transform}}] = ets:lookup(db, Host), - case reg:smatch(Path, Regex) of - {match, _A, _B, _C, MatchesTuple} -> - Matches = tuple_to_list(MatchesTuple), + case re:run(Path, Regex, [{capture, all_but_first, list}]) of + {match, Matches} -> Binding = create_binding(Matches), % io:format("binding = ~p~n", [Binding]), eval_erlang_expr(Transform, Binding); @@ -27,9 +26,8 @@ parse_conf_line(Line) -> ets:insert(db, {Host, {Regex, Transform}}). create_binding(Matches) -> - Modder = fun(M, Acc) -> + Modder = fun(Word, Acc) -> {I, Arr} = Acc, - {_A, _B, Word} = M, Mod = {I, Word}, {I + 1, lists:append(Arr, [Mod])} end, @@ -67,4 +65,4 @@ md5_namespace3(Name) -> hexmod8(Name) -> <> = erlang:md5(Name), - integer_to_list(A rem 8). \ No newline at end of file + integer_to_list(A rem 8). diff --git a/elibs/pipe.erl b/elibs/pipe.erl deleted file mode 100644 index 26f1b83..0000000 --- a/elibs/pipe.erl +++ /dev/null @@ -1,98 +0,0 @@ -% pipe.erl -% -% This module implements a pipe data structure. This pipe implementation is -% designed as a fifo for bytes. You write bytes *to* the pipe and then can -% read those same bytes *from* the pipe. This is useful when dealing with -% chunked data from an external port. All of the chunked data can be written -% to the pipe and then you can read specific numbers of bytes from the pipe. -% This is necessary if you wish to do your own packet length management. -% -% (The MIT License) -% -% Copyright (c) 2008 Tom Preston-Werner -% -% 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. - --module(pipe). --export([new/0, write/2, read/2, peek/2, size/1]). - --record(pipe, {pos = 0, size = 0, queue = queue:new()}). - -new() -> - #pipe{}. - -write(Bin, Pipe) -> - #pipe{size = Size, queue = Q} = Pipe, - {ok, Pipe#pipe{size = Size + erlang:size(Bin), queue = queue:in(Bin, Q)}}. - -read(Num, Pipe) -> - #pipe{size = Size, queue = Q1} = Pipe, - case Num =< Size of - true -> - {Acc, Q2} = read_internal([], Num, Q1), - Bin = list_to_binary(Acc), - P2 = Pipe#pipe{size = Size - Num, queue = Q2}, - {ok, Bin, P2}; - false -> - eof - end. - -peek(Num, Pipe) -> - #pipe{size = Size, queue = Q1} = Pipe, - case Num =< Size of - true -> - {Acc, _} = read_internal([], Num, Q1), - Bin = list_to_binary(Acc), - {ok, Bin}; - false -> - eof - end. - -read_internal(Acc, Num, Q1) -> - {{value, Bin}, Q2} = queue:out(Q1), - Size = erlang:size(Bin), - case spaceship(Num, Size) of - -1 -> - {B1, B2} = split_binary(Bin, Num), - Q3 = queue:in_r(B2, Q2), - Acc2 = lists:append(Acc, [B1]), - {Acc2, Q3}; - 0 -> - Acc2 = lists:append(Acc, [Bin]), - {Acc2, Q2}; - 1 -> - Acc2 = lists:append(Acc, [Bin]), - read_internal(Acc2, Num - Size, Q2) - end. - -size(Pipe) -> - Pipe#pipe.size. - -% util - -spaceship(A, B) -> - case A =< B of - true -> - case A < B of - true -> -1; - false -> 0 - end; - false -> 1 - end. \ No newline at end of file diff --git a/elibs/receive_pack.erl b/elibs/receive_pack.erl deleted file mode 100644 index b2da2a1..0000000 --- a/elibs/receive_pack.erl +++ /dev/null @@ -1,6 +0,0 @@ --module(receive_pack). --export([handle/3]). - -handle(Sock, _Host, _Header) -> - gen_tcp:send(Sock, "006d\n*********'\n\nYou can't push to git://github.com/user/repo.git\nUse git@github.com:user/repo.git\n\n*********"), - ok = gen_tcp:close(Sock). \ No newline at end of file diff --git a/elibs/reg.erl b/elibs/reg.erl deleted file mode 100644 index a7e36fa..0000000 --- a/elibs/reg.erl +++ /dev/null @@ -1,1344 +0,0 @@ -%% re4 => Upgrade to needed state, checking speed as we go -%% 0) fixed interval code and copying -%% 1) upgrade to next_match_XXX (+ slight improvement) -%% 2) put pos+char in global state arg (no noticable difference) -%% 3) get look-ahead character for proper eol (- 5-10% slower) - --module(reg). - --export([parse/1,match/2,first_match/2,matches/2,sub/3,gsub/3,split/2]). --export([smatch/2,first_smatch/2]). --export([tt/2,loadf/1]). - --import(string, [substr/2,substr/3]). --import(lists, [reverse/1,reverse/2,last/1,duplicate/2,seq/2]). --import(lists, [member/2,sort/1,keysearch/3,keysort/2,keydelete/3]). --import(lists, [map/2,foldl/3]). --import(ordsets, [is_element/2,add_element/2,union/2,subtract/2]). - -%%-compile([export_all]). - -%%-define(TP(F,As), io:fwrite(F, As)). -%%-define(TP(F,As), begin {F,As}, ok end). --define(TP(F,As), ok). - -%% NFA states -%% State type defines type of transition from the state. -%% N.B. all types have the id in the same field and all types except -%% estate have the next state pointer in the same field. This is an -%% invariant and is used in the code! - --record(cstate, {id,c,s}). %Character state --record(nstate, {id,cc,s}). %Character class state --record(estate, {id,s1,s2}). %Epsilon state --record(lstate, {id,n,s}). %Parentheses states --record(rstate, {id,n,s}). --record(pstate, {id,t,s}). %Position states - -%% This is the regular expression grammar used. It is equivalent to the -%% one used in AWK, except that we allow ^ $ to be used anywhere and fail -%% in the matching. -%% -%% reg -> reg1 : '$1'. -%% reg1 -> reg1 "|" reg2 : {'or','$1','$2'}. -%% reg1 -> reg2 : '$1'. -%% reg2 -> reg2 reg3 : {concat,'$1','$2'}. -%% reg2 -> reg3 : '$1'. -%% reg3 -> reg3 "*" : {kclosure,'$1'}. -%% reg3 -> reg3 "+" : {pclosure,'$1'}. -%% reg3 -> reg3 "?" : {optional,'$1'}. -%% reg3 -> reg3 "{" [Min],[Max] "}" : {closure_range, Num, '$1'} see below -%% reg3 -> reg4 : '$1'. -%% reg4 -> "(" reg ")" : '$2'. -%% reg4 -> "\\" char : '$2'. -%% reg4 -> "^" : bos. -%% reg4 -> "$" : eos. -%% reg4 -> "." : char. -%% reg4 -> "[" class "]" : {char_class,char_class('$2')} -%% reg4 -> "[" "^" class "]" : {comp_class,char_class('$3')} -%% reg4 -> "\"" chars "\"" : char_string('$2') -%% reg4 -> char : '$1'. -%% reg4 -> empty : epsilon. -%% The grammar of the current regular expressions. The actual parser -%% is a recursive descent implementation of the grammar. - -%% reg(String, NFA, NextState, SubCount) -> -%% {Frame,NFA,NewNextState,NewSubCount,RestString}. -%% Frame = {BegState,[EndState]}. - -reg(Cs0) -> - {F,Nfa0,N0,Sc,Cs1} = reg(Cs0, [], 1, 1), - Nfa1 = [#cstate{id=N0,c=done}|Nfa0], - {{Start,_},Nfa2,_} = concat(F, {N0,[N0]}, Nfa1, N0), - {ok,{list_to_tuple(keysort(#nstate.id, Nfa2)),Start,Sc-1},Cs1}. - -reg(Cs, Nfa, N, Sc) -> reg1(Cs, Nfa, N, Sc). - -%% reg1 -> reg2 reg1' -%% reg1' -> "|" reg2 reg1' -%% reg1' -> empty - -reg1(Cs0, Nfa0, N0, Sc0) -> - {F,Nfa1,N1,Sc1,Cs1} = reg2(Cs0, Nfa0, N0, Sc0), - reg1p(Cs1, F, Nfa1, N1, Sc1). - -reg1p([$||Cs0], Lf, Nfa0, N0, Sc0) -> - {Rf,Nfa1,N1,Sc1,Cs1} = reg2(Cs0, Nfa0, N0, Sc0), - {F,Nfa2,N2} = alt(Lf, Rf, Nfa1, N1), - reg1p(Cs1, F, Nfa2, N2, Sc1); -reg1p(Cs, F, Nfa, N, Sc) -> {F,Nfa,N,Sc,Cs}. - -%% reg2 -> reg3 reg2' -%% reg2' -> reg3 -%% reg2' -> empty - -reg2(S0, Nfa0, N0, Sc0) -> - {F,Nfa1,N1,Sc1,S1} = reg3(S0, Nfa0, N0, Sc0), - reg2p(S1, F, Nfa1, N1, Sc1). - -reg2p([C|_]=Cs0, Lf, Nfa0, N0, Sc0) when C /= $|, C /= $) -> - {Rf,Nfa1,N1,Sc1,Cs1} = reg3(Cs0, Nfa0, N0, Sc0), - {F,Nfa2,N2} = concat(Lf, Rf, Nfa1, N1), - reg2p(Cs1, F, Nfa2, N2, Sc1); -reg2p(Cs, F, Nfa, N, Sc) -> {F,Nfa,N,Sc,Cs}. - -%% reg3 -> reg4 reg3' -%% reg3' -> "*" reg3' -%% reg3' -> "+" reg3' -%% reg3' -> "?" reg3' -%% reg3' -> "{" [Min],[Max] "}" reg3' -%% reg3' -> empty - -reg3(Cs0, Nfa0, N0, Sc0) -> - {F,Nfa1,N1,Sc1,Cs1} = reg4(Cs0, Nfa0, N0, Sc0), - reg3p(Cs1, F, Nfa1, N1, Sc1). - -reg3p([$*|Cs], Lf, Nfa0, N0, Sc) -> - {F,Nfa1,N1} = kclosure(Lf, Nfa0, N0), - reg3p(Cs, F, Nfa1, N1, Sc); -reg3p([$+|Cs], Lf, Nfa0, N0, Sc) -> - {F,Nfa1,N1} = pclosure(Lf, Nfa0, N0), - reg3p(Cs, F, Nfa1, N1, Sc); -reg3p([$?|Cs], Lf, Nfa0, N0, Sc) -> - {F,Nfa1,N1} = optional(Lf, Nfa0, N0), - reg3p(Cs, F, Nfa1, N1, Sc); -reg3p([${|Cs0], Lf, Nfa0, N0, Sc) -> % $} - %% Have many special case so as not to create unnecessary new states. - case interval_range(Cs0) of - {0,0,[$}|Cs1]} -> %This is a null op! - %% The created states have been created but never be refenced! - {Nfa1,N1} = delete(Lf, Nfa0, N0), - reg3p(Cs1, epsilon, Nfa1, N1, Sc); - {0,Max,[$}|Cs1]} when is_integer(Max) -> - {F,Nfa1,N1} = optional(Max, Lf, Nfa0, N0), - %%?TP("I2: ~w x ~w x ~w\nI2 => ~w x ~w\n", [Lf,Max,Nfa0,F,Nfa1]), - reg3p(Cs1, F, Nfa1, N1, Sc); - {0,none,[$}|Cs1]} -> %This is a null op! - %% The created states have been created but never be refenced! - {Nfa1,N1} = delete(Lf, Nfa0, N0), - reg3p(Cs1, epsilon, Nfa1, N1, Sc); - {0,any,[$}|Cs1]} -> - {F1,Nfa1,N1} = kclosure(Lf, Nfa0, N0), - reg3p(Cs1, F1, Nfa1, N1, Sc); - {Min,Min,[$}|Cs1]} when is_integer(Min) -> - {F,Nfa1,N1} = copy_concat(Min, Lf, Nfa0, N0), - reg3p(Cs1, F, Nfa1, N1, Sc); - {Min,Max,[$}|Cs1]} when is_integer(Min), is_integer(Max), Max >= Min -> - {Fc,Nfa1,N1} = copy(Lf, Nfa0, N0), %Make copy first! - {F0,Nfa2,N2} = copy_concat(Min, Lf, Nfa1, N1), - {F1,Nfa3,N3} = optional(Max-Min, Fc, Nfa2, N2), - {F2,Nfa4,N4} = concat(F0, F1, Nfa3, N3), - reg3p(Cs1, F2, Nfa4, N4, Sc); - {Min,none,[$}|Cs1]} when is_integer(Min) -> - {F,Nfa1,N1} = copy_concat(Min, Lf, Nfa0, N0), - reg3p(Cs1, F, Nfa1, N1, Sc); - {Min,any,[$}|Cs1]} when is_integer(Min) -> - {Fc,Nfa1,N1} = copy(Lf, Nfa0, N0), %Make copy first! - {F0,Nfa2,N2} = copy_concat(Min, Lf, Nfa1, N1), - {F1,Nfa3,N3} = kclosure(Fc, Nfa2, N2), - {F2,Nfa4,N4} = concat(F0, F1, Nfa3, N3), - reg3p(Cs1, F2, Nfa4, N4, Sc); - {_N,_M,_Cs1} -> %Catches none,none as well - parse_error({interval_range,[${|Cs0]}) - end; -reg3p(Cs, Lf, Nfa, N, Sc) -> {Lf,Nfa,N,Sc,Cs}. - -reg4([$(,$?,$:|Cs0], Nfa0, N0, Sc0) -> % $) A little PERLism! - case reg(Cs0, Nfa0, N0, Sc0) of - {R,Nfa1,N1,Sc1,[$)|Cs1]} -> - {R,Nfa1,N1,Sc1,Cs1}; - {_,_,_,_,_} -> parse_error({unterminated,"(?:"}) - end; -reg4([$(|Cs0], Nfa0, N0, Sc0) -> % $) - {Lf,Nfa1,N1} = lparen(Sc0, Nfa0, N0), - case reg(Cs0, Nfa1, N1, Sc0+1) of - {R,Nfa2,N2,Sc2,[$)|Cs1]} -> - {Sf,Nfa3,N3} = rparen(Sc0, R, Lf, Nfa2, N2), - {Sf,Nfa3,N3,Sc2,Cs1}; - {_,_,_,_,_} -> parse_error({unterminated,"("}) - end; -reg4([$^|Cs], Nfa0, N0, Sc) -> - {F,Nfa1,N1} = pstate(bos, Nfa0, N0), - {F,Nfa1,N1,Sc,Cs}; -reg4([$$|Cs], Nfa0, N0, Sc) -> - {F,Nfa1,N1} = pstate(eos, Nfa0, N0), - {F,Nfa1,N1,Sc,Cs}; -reg4([$.|Cs], Nfa0, N0, Sc) -> - {F,Nfa1,N1} = nstate([{0,9},{11,maxchar}], Nfa0, N0), - {F,Nfa1,N1,Sc,Cs}; -reg4([$[,$^|Cs0], Nfa0, N0, Sc) -> - case comp_class(Cs0) of - {Cc,[$]|Cs1]} -> - {F,Nfa1,N1} = nstate(Cc, Nfa0, N0), - {F,Nfa1,N1,Sc,Cs1}; - {_,_} -> parse_error({unterminated,"["}) - end; -reg4([$[|Cs0], Nfa0, N0, Sc) -> - case char_class(Cs0) of - {Cc,[$]|Cs1]} -> - {F,Nfa1,N1} = nstate(Cc, Nfa0, N0), - {F,Nfa1,N1,Sc,Cs1}; - {_,_} -> parse_error({unterminated,"["}) - end; -reg4([C0|Cs0], Nfa0, N0, Sc) when - is_integer(C0), C0 /= $*, C0 /= $+, C0 /= $?, C0 /= $], C0 /= $), C0 /= $} -> - %% Handle \ quoted characters as well, at least those we see. - {C1,Cs1} = char(C0, Cs0), %Get the extended char - {F,Nfa1,N1} = cstate(C1, Nfa0, N0), - {F,Nfa1,N1,Sc,Cs1}; -reg4([$)|_]=Cs, Nfa, N, Sc) -> {epsilon,Nfa,N,Sc,Cs}; -reg4([C|_], _, _, _) -> parse_error({illegal,[C]}); -reg4([], Nfa, N, Sc) -> - ?TP("reg4: ~w\n", [{[],Nfa,N,Sc}]), - {epsilon,Nfa,N,Sc,[]}. - -%%% Is {N,[]} an epsilon state? Is it safe??????? - -lparen(Sc, Nfa0, N) -> - Nfa1 = [#lstate{id=N,n=Sc}|Nfa0], - {{N,[N]},Nfa1,N+1}. - -rparen(Sc, epsilon, {Lb,Les}, Nfa0, N) -> - Nfa1 = patch(Nfa0, Les, N), - Nfa2 = [#rstate{id=N,n=Sc}|Nfa1], - {{Lb,[N]},Nfa2,N+1}; -rparen(Sc, {B,Es}, {Lb,Les}, Nfa0, N) -> - Nfa1 = patch(Nfa0, Les, B), - Nfa2 = [#rstate{id=N,n=Sc}|Nfa1], - Nfa3 = patch(Nfa2, Es, N), - {{Lb,[N]},Nfa3,N+1}. - -kclosure(epsilon, Nfa, N) -> {epsilon,Nfa,N}; -kclosure({B,Es}, Nfa0, N) -> - Nfa1 = [#estate{id=N,s1=B,s2=none}|Nfa0], - {{N,[N]},patch(Nfa1, Es, N),N+1}. - -pclosure(epsilon, Nfa, N) -> {epsilon,Nfa,N}; -pclosure({B,Es}, Nfa0, N) -> - Nfa1 = [#estate{id=N,s1=B,s2=none}|Nfa0], - {{B,[N]},patch(Nfa1, Es, N),N+1}. - -optional(epsilon, Nfa, N) -> {epsilon,Nfa,N}; -optional({B,Es}, Nfa0, N) -> - Nfa1 = [#estate{id=N,s1=B,s2=none}|Nfa0], - {{N,Es ++ [N]},Nfa1,N+1}. - -cstate(C, Nfa0, N) -> - Nfa1 = [#cstate{id=N,c=C}|Nfa0], - {{N,[N]},Nfa1,N+1}. - -nstate(Cc, Nfa0, N) -> - Nfa1 = [#nstate{id=N,cc=Cc}|Nfa0], - {{N,[N]},Nfa1,N+1}. - -pstate(Type, Nfa0, N) -> - Nfa1 = [#pstate{id=N,t=Type}|Nfa0], - {{N,[N]},Nfa1,N+1}. - -concat(epsilon, F2, Nfa, N) -> {F2,Nfa,N}; -concat(F1, epsilon, Nfa, N) -> {F1,Nfa,N}; -concat({B1,Es1}, {B2,Es2}, Nfa0, N) -> - Nfa1 = patch(Nfa0, Es1, B2), - {{B1,Es2},Nfa1,N}. - -alt(epsilon, {B2,E2}, Nfa0, N) -> - Nfa1 = [#estate{id=N,s1=none,s2=B2}|Nfa0], - {{N,[N|E2]},Nfa1,N+1}; -alt({B1,E1}, epsilon, Nfa0, N) -> - Nfa1 = [#estate{id=N,s1=B1,s2=none}|Nfa0], - {{N,E1 ++ [N]},Nfa1,N+1}; -alt({B1,E1}, {B2,E2}, Nfa0, N) -> - Nfa1 = [#estate{id=N,s1=B1,s2=B2}|Nfa0], - {{N,E1 ++ E2},Nfa1,N+1}. - -%% optional(Count, Frame, Nfa, NextFree) -> {Frame,Nfa,NextFree}. -%% M x F => (...((F?)F)?...F)? Is this better than F?F?...F? ? -%% Original states will be destructively included in copy. -%% If Count == 0 then return epsilon. - -optional(M, F, Nfa0, N0) when M > 1 -> - {F1,Nfa1,N1} = copy(F, Nfa0, N0), - {F2,Nfa2,N2} = optional(M-1, F, Nfa1, N1), - {F3,Nfa3,N3} = concat(F1, F2, Nfa2, N2), - optional(F3, Nfa3, N3); -optional(1, F, Nfa, N) -> optional(F, Nfa, N); -optional(0, _, Nfa, N) -> {epsilon,Nfa,N}. - -%% copy_concat(Count, Frame, Nfa, NextFree) -> {Frame,Nfa,NextFree}. -%% Make Count copies of sub-expression in Frame concated together. -%% Original states will be destructively included in copy. -%% If Count == 0 then return epsilon. - -copy_concat(M, F0, Nfa0, N0) when M > 1 -> - {F1,Nfa1,N1} = copy(F0, Nfa0, N0), - {F2,Nfa2,N2} = copy_concat(M-1, F0, Nfa1, N1), - concat(F1, F2, Nfa2, N2); -copy_concat(1, F, Nfa, N) -> {F,Nfa,N}; -copy_concat(0, _, Nfa, N) -> {epsilon,Nfa,N}. - -%% copy(Frame, Nfa, NextFree) -> {Frame,Nfa,NextFree}. -%% Making a copy of a sub expression is a bit of a pain. We -%% recursivley descend from the start through the graph building new -%% states as we go back up. We assume that the graph to be copied has -%% not been already prepended to another set of states as the -%% termination condition is a non-numeric "next state". - -copy({B0,Es}, Nfa0, N0) -> - {B1,Nfa1,N1,D} = copy(B0, Nfa0, N0, []), - %% Build a new list of end states from the new copies. - Es1 = map(fun (E) -> {value,{E,E1}} = keysearch(E, 1, D), E1 end, Es), - {{B1,Es1},Nfa1,N1}. - -copy(B, Nfa0, N0, D0) when is_integer(B) -> - case keysearch(B, 1, D0) of - {value,{B,Rep}} -> {Rep,Nfa0, N0, D0}; - false -> - case keysearch(B, #cstate.id, Nfa0) of - {value,#estate{s1=S0,s2=T0}=St} -> - {S1,Nfa1,N1,D1} = copy(S0, Nfa0, N0, D0), - {T1,Nfa2,N2,D2} = copy(T0, Nfa1, N1, D1), - Nfa3 = [St#estate{id=N2,s1=S1,s2=T1}|Nfa2], - {N2,Nfa3,N2+1,[{B,N2}|D2]}; - {value,St0} -> - %% All other state types have the next state in - %% the same place. - S0 = element(#cstate.s, St0), - {S1,Nfa1,N1,D1} = copy(S0, Nfa0, N0, D0), - St1 = setelement(#cstate.id, St0, N1), %{id=N1,s=S1} - St2 = setelement(#cstate.s, St1, S1), - {N1,[St2|Nfa1],N1+1,[{B,N1}|D1]} - end -%% {value,#cstate{s=S0}=St} -> -%% {S1,Nfa1,N1,D1} = copy(S0, Nfa0, N0, D0), -%% Nfa2 = [St#cstate{id=N1,s=S1}|Nfa1], -%% {N1,Nfa2,N1+1,[{B,N1}|D1]}; - end; -copy(B, Nfa, N, D) -> {B,Nfa,N,D}. - -%% delete(Frame, Nfa, NextFree) -> {Nfa,NextFree}. -%% Delete all the states in a frame if possible. -%% This is hairy. Can ony delete from the highest element as holes -%% not allowed. - -delete({B,_}, Nfa, N0) -> - Ss0 = span_states(B, Nfa, []), %All states in this frame - Ss1 = reverse(sort(Ss0)), %Reverse order - delete1(Ss1, Nfa, N0). %Remove until not highest. - -delete1([S|Ss], Nfa, N) -> - if S == N-1 -> %Highest id element. - delete1(Ss, keydelete(S, #cstate.id, Nfa), N-1); - true -> {Nfa,N} %No need to go on - end; -delete1([], Nfa, N) -> {Nfa,N}. - -span_states(B, Nfa, Seen) when is_integer(B) -> - case member(B, Seen) of - true -> Seen; - false -> - case keysearch(B, #cstate.id, Nfa) of - {value,#estate{s1=S,s2=T}} -> - span_states(T, Nfa, span_states(S, Nfa, [B|Seen])); - {value,St} -> - %% All other state types have the next state in - %% the same place. - span_states(element(#cstate.s, St), Nfa, [B|Seen]) - end - end; -span_states(_, _, Seen) -> Seen. - -%% patch(NFA, EndStates, Beginning) -> NFA. -%% Patch Endstates so they all point to Beginning. - -patch(Nfa, Es, B) -> - lists:foldl(fun (E, Nfa0) -> patch1(Nfa0, E, B) end, Nfa, Es). - -patch1([#cstate{id=E}=Nst|Nfa], E, B) -> - [Nst#cstate{s=B}|Nfa]; -patch1([#nstate{id=E}=Nst|Nfa], E, B) -> - [Nst#nstate{s=B}|Nfa]; -%% Patch empty slot of estate, assume there is only 1 empty. -patch1([#estate{id=E,s1=none}=Nst|Nfa], E, B) -> - [Nst#estate{s1=B}|Nfa]; -patch1([#estate{id=E,s2=none}=Nst|Nfa], E, B) -> - [Nst#estate{s2=B}|Nfa]; -patch1([#lstate{id=E}=Nst|Nfa], E, B) -> - [Nst#lstate{s=B}|Nfa]; -patch1([#rstate{id=E}=Nst|Nfa], E, B) -> - [Nst#rstate{s=B}|Nfa]; -patch1([#pstate{id=E}=Nst|Nfa], E, B) -> - [Nst#pstate{s=B}|Nfa]; -patch1([Nst|Nfa], E, B) -> - [Nst|patch1(Nfa, E, B)]. - -parse_error(E) -> throw({error,E}). - -char($\\, [O1,O2,O3|S]) when - O1 >= $0, O1 =< $7, O2 >= $0, O2 =< $7, O3 >= $0, O3 =< $7 -> - {(O1*8 + O2)*8 + O3 - 73*$0,S}; -char($\\, [C|S]) -> {escape_char(C),S}; -char($\\, []) -> parse_error({unterminated,"\\"}); -char(C, S) -> {C,S}. - -escape_char($n) -> $\n; %\n = LF -escape_char($r) -> $\r; %\r = CR -escape_char($t) -> $\t; %\t = TAB -escape_char($v) -> $\v; %\v = VT -escape_char($b) -> $\b; %\b = BS -escape_char($f) -> $\f; %\f = FF -escape_char($e) -> $\e; %\e = ESC -escape_char($s) -> $\s; %\s = SPACE -escape_char($d) -> $\d; %\d = DEL -escape_char(C) -> C. - -char_class([$]|S0]) -> - {Cc,S1} = char_class(S0, [$]]), - {pack_cc(Cc),S1}; -char_class(S0) -> - {Cc,S1} = char_class(S0, []), - {pack_cc(Cc),S1}. - -comp_class(Cs0) -> - {Cc,Cs1} = char_class(Cs0), - {comp_class(Cc, 0),Cs1}. - -comp_class([{C1,C2}|Crs], Last) -> - [{Last,C1-1}|comp_class(Crs, C2+1)]; -comp_class([C|Crs], Last) when Last == C-1 -> - [Last|comp_class(Crs, C+1)]; -comp_class([C|Crs], Last) when is_integer(C) -> - [{Last,C-1}|comp_class(Crs, C+1)]; -comp_class([], Last) -> [{Last,maxchar}]. - -%% pack_cc(CharClass) -> CharClass -%% Pack and optimise a character class specification (bracket -%% expression). First sort it and then compact it. - -pack_cc(Cc0) -> - %% First sort the list ... - Cc1 = lists:usort(fun ({Cf1,_}, {Cf2,_}) -> Cf1 < Cf2; - ({Cf1,_}, C) -> Cf1 < C; - (C, {Cf,_}) -> C < Cf; - (C1, C2) -> C1 =< C2 - end, Cc0), - %% ... then compact it. - pack_cc1(Cc1). - -pack_cc1([{Cf1,Cl1},{Cf2,Cl2}|Cc]) when Cl1 >= Cf2, Cl1 =< Cl2 -> - %% Cf1 Cl1 - %% Cf2 Cl2 - pack_cc1([{Cf1,Cl2}|Cc]); -pack_cc1([{Cf1,Cl1},{Cf2,Cl2}|Cc]) when Cl1 >= Cf2, Cl1 >= Cl2 -> - %% Cf1 Cl1 - %% Cf2 Cl2 - pack_cc1([{Cf1,Cl1}|Cc]); -pack_cc1([{Cf1,Cl1},{Cf2,Cl2}|Cc]) when Cl1+1 == Cf2 -> - %% Cf1 Cl1 - %% Cf2 Cl2 - pack_cc1([{Cf1,Cl2}|Cc]); -pack_cc1([{Cf,Cl},C|Cc]) when Cl >= C -> pack_cc1([{Cf,Cl}|Cc]); -pack_cc1([{Cf,Cl},C|Cc]) when Cl+1 == C -> pack_cc1([{Cf,C}|Cc]); -pack_cc1([C,{Cf,Cl}|Cc]) when C == Cf-1 -> pack_cc1([{C,Cl}|Cc]); -pack_cc1([C1,C2|Cc]) when C1+1 == C2 -> pack_cc1([{C1,C2}|Cc]); -pack_cc1([C|Cc]) -> [C|pack_cc1(Cc)]; -pack_cc1([]) -> []. - -char_class("[:" ++ S0, Cc0) -> %Start of POSIX char class - case posix_cc(S0, Cc0) of - {Cc1,":]" ++ S1} -> char_class(S1, Cc1); - {_,_S1} -> parse_error({posix_cc,"[:" ++ S0}) - end; -char_class([C1|S0], Cc) when C1 /= $] -> - case char(C1, S0) of - {Cf,[$-,C2|S1]} when C2 /= $] -> - case char(C2, S1) of - {Cl,S2} when Cf < Cl -> char_class(S2, [{Cf,Cl}|Cc]); - {_Cl,_S2} -> parse_error({char_class,[C1|S0]}) - end; - {C,S1} -> char_class(S1, [C|Cc]) - end; -char_class(S, Cc) -> {Cc,S}. - -%% posix_cc(String, CharClass) -> {NewCharClass,RestString}. -%% Handle POSIX character classes, use Latin-1 character set. - -posix_cc("alnum" ++ S, Cc) -> - {[{$0,$9},{$A,$Z},{192,214},{216,223},{$a,$z},{224,246},{248,255}|Cc],S}; -posix_cc("alpha" ++ S, Cc) -> - {[{$A,$Z},{192,214},{216,223},{$a,$z},{224,246},{248,255}|Cc],S}; -posix_cc("blank" ++ S, Cc) -> {[$\s,$\t,160|Cc],S}; -posix_cc("cntrl" ++ S, Cc) -> {[{0,31},{127,159}|Cc],S}; -posix_cc("digit" ++ S, Cc) -> {[{$0,$9}|Cc],S}; -posix_cc("graph" ++ S, Cc) -> {[{33,126},{161,255}|Cc],S}; -posix_cc("lower" ++ S, Cc) -> {[{$a,$z},{224,246},{248,255}|Cc],S}; -posix_cc("print" ++ S, Cc) -> {[{32,126},{160,255}|Cc],S}; -posix_cc("punct" ++ S, Cc) -> {[{$!,$/},{$:,$?},{${,$~},{161,191}|Cc],S}; -posix_cc("space" ++ S, Cc) -> {[$\s,$\t,$\f,$\r,$\v,160|Cc],S}; -posix_cc("upper" ++ S, Cc) -> {[{$A,$Z},{192,214},{216,223}|Cc],S}; -posix_cc("xdigit" ++ S, Cc) -> {[{$a,$f},{$A,$F},{$0,$9}|Cc],S}; -posix_cc(S, _Cc) -> parse_error({posix_cc,"[:" ++ S}). - -interval_range(Cs0) -> - case number(Cs0) of - {none,Cs1} -> {none,none,Cs1}; - {N,[$,|Cs1]} -> - case number(Cs1) of - {none,Cs2} -> {N,any,Cs2}; - {M,Cs2} -> {N,M,Cs2} - end; - {N,Cs1} -> {N,none,Cs1} - end. - -number([C|Cs]) when C >= $0, C =< $9 -> - number(Cs, C - $0); -number(Cs) -> {none,Cs}. - -number([C|Cs], Acc) when C >= $0, C =< $9 -> - number(Cs, 10*Acc + (C - $0)); -number(Cs, Acc) -> {Acc,Cs}. - -%% The interface functions. - -parse(Cs) -> - case catch reg(Cs) of - {ok,R,[]} -> {ok,{nfa,R}}; - {ok,_R,[C|_]} -> {error,{illegal,[C]}}; - {error,E} -> {error,E} - end. - -%% match(String, RegExp) -> {match,Start,Length} | nomatch | {error,E}. -%% Find the longest match of RegExp in String. - -match(S, RegExp) when is_list(RegExp) -> - case parse(RegExp) of - {ok,RE} -> match(S, RE); - {error,E} -> {error,E} - end; -match(S, {nfa,NFA}) when is_binary(S) -> - case match_bin(S, 1, NFA, 0, -1) of - {Start,Len} when Len >= 0 -> {match,Start,Len}; - {_,_} -> nomatch - end; -match(S, {nfa,NFA}) -> - case match_str(S, 1, NFA, 0, -1) of - {Start,Len} when Len >= 0 -> {match,Start,Len}; - {_,_} -> nomatch - end. - -match_str(Cs0, P, Nfa, Mst, Mlen) -> - case next_match_str(Cs0, P, Nfa) of - {match,St,Len,[_|Cs],_} -> - if Len > Mlen -> match_str(Cs, St+1, Nfa, St, Len); - true -> match_str(Cs, St+1, Nfa, Mst, Mlen) - end; - {match,St,Len,[],_} -> %Empty match at end - if Len > Mlen -> {St,Len}; - true -> {Mst,Mlen} - end; - nomatch -> {Mst,Mlen} - end. - -match_bin(Bin, P, Nfa, Mst, Mlen) -> - case next_match_bin(Bin, P, Nfa) of - {match,St,Len} when St+Len == size(Bin) -> %Empty match at end - if Len > Mlen -> {St,Len}; - true -> {Mst,Mlen} - end; - {match,St,Len} -> - if Len > Mlen -> match_bin(Bin, St+1, Nfa, St, Len); - true -> match_bin(Bin, St+1, Nfa, Mst, Mlen) - end; - nomatch -> {Mst,Mlen} - end. - -%% match1(String, RegExp) -> {match,Start,Length} | nomatch | {error,E}. -%% first_match(String, RegExp) -> {match,Start,Length} | nomatch | {error,E}. -%% Find the first match of RegExp in String, return Start and Length. - -first_match(S, RegExp) when is_list(RegExp) -> - {ok,RE} = parse(RegExp), - first_match(S, RE); -first_match(S, {nfa,RE}) when is_binary(S) -> - first_match_bin(S, 1, RE); -first_match(S, {nfa,RE}) -> - first_match_str(S, 1, RE). - -first_match_str(Cs, P, Nfa) -> - case next_match_str(Cs, P, Nfa) of - {match,St,Len,_,_} -> {match,St,Len}; - nomatch -> nomatch - end. - -first_match_bin(Bin, P0, Nfa) -> - case next_match_bin(Bin, P0, Nfa) of - {match,St,Len} -> {match,St,Len}; - nomatch -> nomatch - end. - -%% smatch(String, RegExp) -> -%% {match,Start,Length,String,SubExprs} | nomatch | {error,E}. -%% Find the longest match of RegExp in String. - -smatch(S, RegExp) when is_list(RegExp) -> - case parse(RegExp) of - {ok,RE} -> smatch(S, RE); - {error,E} -> {error,E} - end; -smatch(S, {nfa,Nfa}) when is_binary(S) -> - case smatch_bin(S, 1, Nfa, {0,-1,none}) of - {St,Len,Subs} when Len >= 0 -> - {match,St,Len,bin_to_list(S, St, Len),fix_subs_bin(Subs, S)}; - {_,_,_} -> nomatch - end; -smatch(S, {nfa,Nfa}) -> - case smatch_str(S, 1, Nfa, {0,-1,[],none}) of - {St,Len,Cs,Subs} when Len >= 0 -> - {match,St,Len,substr(Cs, 1, Len),fix_subs_str(Subs, St, Cs)}; - {_,_,_,_} -> nomatch - end. - -smatch_str(Cs0, P, Nfa, {_,Mlen,_,_}=M) -> - case next_smatch_str(Cs0, P, Nfa) of - {match,St,Len,[_|Cs]=Cs1,Subs,_} -> %Found a match - if Len > Mlen -> smatch_str(Cs, St+1, Nfa, {St,Len,Cs1,Subs}); - true -> smatch_str(Cs, St+1, Nfa, M) - end; - {match,St,Len,[],Subs,_} -> - if Len > Mlen -> {St,Len,[],Subs}; - true -> M - end; - nomatch -> M - end. - -smatch_bin(Bin, P, Nfa, {_,Mlen,_}=M) -> - case next_smatch_bin(Bin, P, Nfa) of - {match,St,Len,Subs} when St+Len == size(Bin) -> - if Len > Mlen -> {St,Len,Subs}; - true -> M - end; - {match,St,Len,Subs} -> - if Len > Mlen -> smatch_bin(Bin, St+1, Nfa, {St,Len,Subs}); - true -> smatch_bin(Bin, St+1, Nfa, M) - end; - nomatch -> M - end. - -%% first_smatch(String, RegExp) -> -%% {match,Start,Length,SubExprs} | nomatch | {error,E}. -%% Find the longest match of RegExp in String, return Start and Length -%% as well as tuple of sub-expression matches. - -first_smatch(S, RegExp) when is_list(RegExp) -> - {ok,RE} = parse(RegExp), - first_smatch(S, RE); -first_smatch(S, {nfa,RE}) when is_binary(S) -> - first_smatch_bin(S, 1, RE); -first_smatch(S, {nfa,RE}) -> - first_smatch_str(S, 1, RE). - -first_smatch_str(Cs, P, Nfa) -> - case next_smatch_str(Cs, P, Nfa) of - {match,St,Len,_,Subs,_} -> {match,St,Len,fix_subs_str(Subs,1,Cs)}; - nomatch -> nomatch - end. - -first_smatch_bin(Bin, P, Nfa) -> - case next_smatch_bin(Bin, P, Nfa) of - {match,St,Len,Subs} -> {match,St,Len,fix_subs_bin(Subs, Bin)}; - nomatch -> nomatch - end. - -%% matches(String, RegExp) -> {match,[{Start,Length}]} | {error,E}. -%% Return the all the non-overlapping matches of RegExp in String. - -matches(S, RegExp) when is_list(RegExp) -> - case parse(RegExp) of - {ok,RE} -> matches(S, RE); - {error,E} -> {error,E} - end; -matches(S, {nfa,NFA}) when is_binary(S) -> - {match,matches_bin(S, 1, NFA)}; -matches(S, {nfa,NFA}) -> - {match,matches_str(S, 1, NFA)}. - -matches_str(Cs0, P0, Nfa) -> - case next_match_str(Cs0, P0, Nfa) of - {match,St,0,_,[_|Cs1]} -> - [{St,0}|matches_str(Cs1, St+1, Nfa)]; - {match,St,0,_,[]} -> [{St,0}]; - {match,St,Len,_,Cs1} -> - [{St,Len}|matches_str(Cs1, St+Len, Nfa)]; - nomatch -> [] - end. - -matches_bin(Bin, P0, Nfa) -> - case next_match_bin(Bin, P0, Nfa) of - {match,St,0} when St =< size(Bin) -> - [{St,0}|matches_bin(Bin, St+1, Nfa)]; - {match,St,0} -> [{St,0}]; - {match,St,Len} -> - [{St,Len}|matches_bin(Bin, St+Len, Nfa)]; - nomatch -> [] - end. - -%% sub(String, RegExp, Replace) -> {ok,RepString,RepCount} | {error,E}. -%% Substitute the first match of the regular expression RegExp with -%% the string Replace in String. Accept pre-parsed regular -%% expressions. - -sub(S, RegExp, Rep) when is_list(RegExp) -> - case parse(RegExp) of - {ok,RE} -> sub(S, RE, Rep); - {error,E} -> {error,E} - end; -sub(S, {nfa,Nfa}, Rep) when is_binary(S) -> - case sub_bin(S, 1, Nfa, Rep) of - {yes,NewBin} -> {ok,list_to_binary(NewBin),1}; - no -> {ok,S,0} - end; -sub(S, {nfa,Nfa}, Rep) -> - case sub_str(S, 1, Nfa, Rep) of - {yes,NewStr} -> {ok,NewStr,1}; - no -> {ok,S,0} - end. - -%% sub_str(String, Position, NFA, Replacement) -> -%% {yes,NewString} | no. -%% sub_bin(String, Position, NFA, Replacement) -> -%% {yes,NewString} | no. -%% Step forward over String until a match is found saving stepped over -%% chars in Before. Return reversed Before prepended to replacement -%% and rest of string. - -sub_str(Cs0, P, Nfa, Rep) -> - case next_match_str(Cs0, P, Nfa) of - {match,St,Len,Cs,Cs1} -> - {yes,substr_app(St-P, Cs0, - sub_repl(Rep, substr(Cs, 1, Len), Cs1))}; - nomatch -> no - end. - -substr_app(0, _, App) -> App; -substr_app(N, [C|Cs], App) -> - [C|substr_app(N-1, Cs, App)]; -substr_app(_, [], App) -> App. - -sub_bin(Bin, P, Nfa, Rep) -> - case next_match_bin(Bin, P, Nfa) of - {match,St,Len} -> - {yes,[sub_bin(Bin, P, St - P), - sub_repl(Rep, binary_to_list(Bin, St, St+Len-1), - sub_bin(Bin, St+Len))]}; - nomatch -> no - end. - -sub_repl([$&|Rep], M, Rest) -> M ++ sub_repl(Rep, M, Rest); -sub_repl([$\\,$&|Rep], M, Rest) -> [$&|sub_repl(Rep, M, Rest)]; -sub_repl([C|Rep], M, Rest) -> [C|sub_repl(Rep, M, Rest)]; -sub_repl([], _M, Rest) -> Rest. - -%% gsub(String, RegExp, Replace) -> {ok,RepString,RepCount} | {error,E}. -%% Substitute every match of the regular expression RegExp with the -%% string New in String. Accept pre-parsed regular expressions. - -gsub(S, RegExp, Rep) when is_list(RegExp) -> - case parse(RegExp) of - {ok,RE} -> gsub(S, RE, Rep); - {error,E} -> {error,E} - end; -gsub(S, {nfa,Nfa}, Rep) when is_binary(S) -> - case gsub_bin(S, 1, Nfa, Rep) of - {NewStr,N} -> {ok,list_to_binary(NewStr),N}; - no -> {ok,S,0} %No substitutions - end; -gsub(S, {nfa,Nfa}, Rep) -> - case gsub_str(S, 1, Nfa, Rep) of - {NewStr,N} -> {ok,NewStr,N}; - no -> {ok,S,0} %No substitutions - end. - -%% gsub_str(String, Position, NFA, Replacement) -> -%% {NewString,Count} | no. -%% Step forward over String until a match is found saving stepped over -%% chars in Before. Call recursively to do rest of string after -%% match. Return reversed Before prepended to return from recursive -%% call. - -gsub_str(Cs0, P, Nfa, Rep) -> - case next_match_str(Cs0, P, Nfa) of - {match,St,0,_,[C|Cs1]} -> - {New,N} = gsub_str(Cs1, St+1, Nfa, Rep), - {substr_app(St-P, Cs0, sub_repl(Rep, [], [C|New])),N+1}; - {match,_,0,_,[]} -> {sub_repl(Rep, [], []),1}; - {match,St,Len,Cs,Cs1} -> - {New,N} = gsub_str(Cs1, St+Len, Nfa, Rep), - {substr_app(St-P, Cs0, - sub_repl(Rep, substr(Cs, 1, Len), New)),N+1}; - nomatch -> {Cs0,0} - end. - -gsub_bin(Bin, P, Nfa, Rep) -> - case next_match_bin(Bin, P, Nfa) of - {match,St,0} when St =< size(Bin) -> - {New,N} = gsub_bin(Bin, St+1, Nfa, Rep), - New1 = binary_to_list(Bin, St, St) ++ New, - {[sub_bin(Bin, P, St - P), sub_repl(Rep, [], New1)],N+1}; - {match,_,0} -> {sub_repl(Rep, [], []),1}; - {match,St,Len} -> - {New,N} = gsub_bin(Bin, St+Len, Nfa, Rep), - {[sub_bin(Bin, P, St - P), - sub_repl(Rep, binary_to_list(Bin, St, St+Len-1), New)], N+1}; - nomatch -> {sub_bin(Bin, P),0} - end. - -%% split(String, RegExp) -> {ok,[SubString]} | {error,E}. -%% Split a string into substrings where the RegExp describes the -%% field seperator. The RegExp " " is specially treated. - -split(S, " ") -> split(S, "[ \t]+", true); %This is really special! -split(S, Regexp) -> split(S, Regexp, false). - -split(S, Regexp, Trim) when is_list(Regexp) -> - case parse(Regexp) of - {ok,RE} -> split(S, RE, Trim); - {error,E} -> {error,E} - end; -split(S, {nfa,Nfa}, Trim) when is_binary(S) -> - case split_bin(S, 1, Nfa, Trim) of - [[]|Ss] when Trim -> {ok,Ss}; - Ss -> {ok,Ss} - end; -split(S, {nfa,Nfa}, Trim) -> - case split_str(S, 1, Nfa, Trim) of - [[]|Ss] when Trim -> {ok,Ss}; - Ss -> {ok,Ss} - end. - -split_str(Cs0, P, Nfa, Trim) -> - case next_match_str(Cs0, P, Nfa) of - {match,St,0,_,[C|Cs1]} -> - Ss1 = case split_str(Cs1, St+1, Nfa, Trim) of - [S1|Ss] -> [[C|S1]|Ss]; - [] -> [[C]] - end, - [substr(Cs0, 1, St-P)|Ss1]; - {match,St,0,_,[]} -> [substr(Cs0, 1, St-P)]; - {match,St,Len,_,Cs1} -> - [substr(Cs0, 1, St-P)|split_str(Cs1, St+Len, Nfa, Trim)]; - nomatch -> - if Trim, Cs0 == [] -> []; - true -> [Cs0] - end - end. - -split_bin(Bin, P, Nfa, Trim) -> - case next_match_bin(Bin, P, Nfa) of - {match,St,0} when St =< size(Bin) -> - C = bin_to_list(Bin, St, 1), - Ss1 = case split_bin(Bin, St+1, Nfa, Trim) of - [S1|Ss] -> [list_to_binary([C|S1])|Ss]; - [] -> [C] - end, - [sub_bin(Bin, P, St-P)|Ss1]; - {match,St,0} -> [sub_bin(Bin, P, St-P)]; - {match,St,Len} -> - [sub_bin(Bin, P, St-P)|split_bin(Bin, St+Len, Nfa, Trim)]; - nomatch -> - if Trim, P > size(Bin) -> []; - P > size(Bin) -> [<<>>]; - true -> [sub_bin(Bin, P)] - end - end. - -fix_subs_str(Subs, St, S) -> - Subsl = fix_subs_str(Subs, St, S, size(Subs), []), - list_to_tuple(Subsl). - -fix_subs_str(_, _, _, 0, Ss) -> Ss; -fix_subs_str(Subs, P, S, N, Ss) -> - E = case element(N, Subs) of - {St,L} -> {-St,L,substr(S, -St-P+1, L)}; - undefined -> undefined - end, - fix_subs_str(Subs, P, S, N-1, [E|Ss]). - -fix_subs_bin(Subs, Bin) -> - Subsl = fix_subs_bin(Subs, Bin, size(Subs), []), - list_to_tuple(Subsl). - -fix_subs_bin(_, _, 0, Ss) -> Ss; -fix_subs_bin(Subs, Bin, N, Ss) -> - E = case element(N, Subs) of - {St,L} -> {-St,L,bin_to_list(Bin, -St, L)}; - undefined -> undefined - end, - fix_subs_bin(Subs, Bin, N-1, [E|Ss]). - -%% bin_to_list(Binary, Start) -> Chars. -%% bin_to_list(Binary, Start, Length) -> Chars. -%% As it should be! - -% bin_to_list(Bin, St) -> binary_to_list(Bin, St, size(Bin)). - -bin_to_list(_, _, 0) -> []; -bin_to_list(Bin, St, L) -> binary_to_list(Bin, St, St+L-1). - -sub_bin(Bin, St) -> - St1 = St - 1, - <<_:St1/binary,Sub/binary>> = Bin, - Sub. - -sub_bin(Bin, St, Len) -> - St1 = St - 1, - <<_:St1/binary,Sub:Len/binary,_/binary>> = Bin, - Sub. - -%% The NFA engines. -%% We have two separate engines depending on whether we want to -%% capture sub-expressions. Both have a top-level driver for strings -%% and binaries. We need to do one character lookahead to get correct -%% end of string behaviour as we match both [] and [$\n]. This is a -%% pain! - -%% next_match_str(String, StartPos, NFA) -> -%% {match,Start,Length,Chars,RestChars} | nomatch. -%% Find the next match in String. Try successive positions until -%% either a match is found or we reach the end of the string. - -next_match_str(Cs, P, {Nfa,Start,_}) -> - next_match_str(Cs, P, Nfa, eclosure(Start, Nfa, [], [])). - -next_match_str([_|Cs1]=Cs0, P0, Nfa, Ss) -> - case nfa_str(Cs0, P0, Nfa, Ss, nomatch) of - {match,P1,Cs} -> {match,P0,P1-P0,Cs0,Cs}; - nomatch -> next_match_str(Cs1, P0+1, Nfa, Ss) - end; -next_match_str([], P0, Nfa, Ss) -> - case nfa_str([], P0, Nfa, Ss, nomatch) of %Try for null match at end. - {match,P1,Cs} -> {match,P0,P1-P0,[],Cs}; - nomatch -> nomatch - end. - -%% nfa_str(Chars, Pos, NFA, States, Accept) -> {match,NextPos,Rest} | nomatch. -%% Run the NFA machine over binary starting at one position until we -%% either have a match or not a match. - -nfa_str(_, _, _, [], A) -> A; %No matching states -nfa_str([C|[C1|_]=Cs1]=Cs0, P, Nfa, Ss0, A) -> - Gl = {P,C,C1}, - case step(C, Gl, Nfa, Ss0, [], false) of - {Ss1,true} -> - nfa_str(Cs1, P+1, Nfa, Ss1, {match,P,Cs0}); - {Ss1,false} -> - nfa_str(Cs1, P+1, Nfa, Ss1, A) - end; -nfa_str([C]=Cs0, P, Nfa, Ss0, A) -> - Gl = {P,C,eos}, - case step(C, Gl, Nfa, Ss0, [], false) of - {Ss1,true} -> - nfa_str([], P+1, Nfa, Ss1, {match,P,Cs0}); - {Ss1,false} -> - nfa_str([], P+1, Nfa, Ss1, A) - end; -nfa_str([], P, Nfa, Ss, A) -> %No more characters - case has_match(P, Nfa, Ss) of - yes -> {match,P,[]}; - no -> A %Take what we got - end. - -%% next_match_bin(Binary, StartPos, NFA) -> -%% {match,Start,Length} | nomatch. -%% Find the next match in Binary. Try successive positions until -%% either a match is found or we reach the end of the string. - -next_match_bin(Bin, P, {Nfa,Start,_}) -> - next_match_bin(Bin, P, Nfa, eclosure(Start, Nfa, [], [])). - -next_match_bin(Bin, P0, Nfa, Ss) when P0 < size(Bin) -> - case nfa_bin(Bin, P0, Nfa, Ss, nomatch) of - {match,P1} -> {match,P0,P1-P0}; - nomatch -> next_match_bin(Bin, P0+1, Nfa, Ss) - end; -next_match_bin(Bin, P0, Nfa, Ss) -> - case nfa_bin(Bin, P0, Nfa, Ss, nomatch) of %Try for null match at end. - {match,P1} -> {match,P0,P1-P0}; - nomatch -> nomatch - end. - -%% nfa_bin(Binary, Pos, NFA, States, Accept) -> {match,NextPos} | nomatch. -%% Run the NFA machine over binary starting at one position until we -%% either have a match or not a match. - -nfa_bin(_, _, _, [], A) -> A; %No matching states -nfa_bin(Bin, P, Nfa, Ss0, A) -> - P1 = P-1, %Number of chars before - case Bin of - <<_:P1/binary,C,C1,_/binary>> -> - Gl = {P,C,C1}, - case step(C, Gl, Nfa, Ss0, [], false) of - {Ss1,true} -> - nfa_bin(Bin, P+1, Nfa, Ss1, {match,P}); - {Ss1,false} -> - nfa_bin(Bin, P+1, Nfa, Ss1, A) - end; - <<_:P1/binary,C,_/binary>> -> - Gl = {P,C,eos}, - case step(C, Gl, Nfa, Ss0, [], false) of - {Ss1,true} -> - nfa_bin(Bin, P+1, Nfa, Ss1, {match,P}); - {Ss1,false} -> - nfa_bin(Bin, P+1, Nfa, Ss1, A) - end; - _ -> %No more characters. - case has_match(P, Nfa, Ss0) of - yes -> {match,P}; - no -> A %Take what we got - end - end. - -%% step(Char, GlobalState, NFA, States, NewStates, Done) -> {NewStates,Done}. -%% Pos is the position of the current character. - -step(C, Gl, Nfa, [S|Ss], News, D) -> - case element(S, Nfa) of - #cstate{c=C,s=N} -> - step(C, Gl, Nfa, Ss, eclosure(N, Nfa, [], News), D); - #cstate{c=done} -> step(C, Gl, Nfa, Ss, News, true); - #cstate{} -> step(C, Gl, Nfa, Ss, News, D); - #nstate{cc=Cc,s=N} -> - case match_char(C, Cc) of - true -> - step(C, Gl, Nfa, Ss, eclosure(N, Nfa, [], News), D); - false -> step(C, Gl, Nfa, Ss, News, D) - end; - #pstate{t=bos,s=N} -> - if element(1, Gl) == 1 -> - %% Add eclosure to *this* level of states - Ss1 = eclosure(N, Nfa, [], Ss), - step(C, Gl, Nfa, Ss1, News, D); - true -> step(C, Gl, Nfa, Ss, News, D) - end; - #pstate{t=eos,s=N} -> - Ss1 = if element(2, Gl) == $\n, element(3, Gl) == eos -> - %% Add eclosure to *this* level of states - eclosure(N, Nfa, [], Ss); - true -> Ss - end, - step(C, Gl, Nfa, Ss1, News, D) - end; -step(_, _, _, [], News, D) -> {News,D}. - -%% eclosure(State, Nfa, SeenStates, NewStates) -> NewStates. - -eclosure(S, Nfa, Es, Rest) -> - case element(S, Nfa) of - #estate{s1=S1,s2=S2} -> - %% Must track of where we have been to avoid loops. - case member(S, Es) of - true -> Rest; - false -> - Es1 = [S|Es], - eclosure(S1, Nfa, Es1, eclosure(S2, Nfa, Es1, Rest)) - end; - %% Just ignore parentheses states here. - #lstate{s=S1} -> eclosure(S1, Nfa, Es, Rest); - #rstate{s=S1} -> eclosure(S1, Nfa, Es, Rest); - %% All other states get added to state list. - _St -> add_state(S, Rest, Rest) - end. - -%% add_state(State, States, States) -> States. -%% Add a state to list of states. As list generally short it is better -%% to carry it around in extra argument and prepend new to beginning -%% rather than rebuilding every call. - -add_state(S, [S|_Ss], All) -> All; -add_state(S, [_|Ss], All) -> add_state(S, Ss, All); -add_state(S, [], All) -> [S|All]. - -%% match_char(Char, Class) -> bool(). - -match_char(C, [{C1,C2}|_Cc]) when C >= C1, C =< C2 -> true; -match_char(C, [C|_Cc]) -> true; -match_char(C, [_|Cc]) -> match_char(C, Cc); -match_char(_, []) -> false. - -has_match(P, Nfa, [S|Ss]) -> - case element(S, Nfa) of - #cstate{c=done} -> yes; - #pstate{t=bos,s=N} -> - if P == 1 -> - %% Add eclosure to *this* level of states - Ss1 = eclosure(N, Nfa, [], Ss), - has_match(P, Nfa, Ss1); - true -> has_match(P, Nfa, Ss) - end; - #pstate{t=eos,s=N} -> %EOS is always valid here - Ss1 = eclosure(N, Nfa, [], Ss), - has_match(P, Nfa, Ss1); - _ -> has_match(P, Nfa, Ss) - end; -has_match(_, _, []) -> no. - -%% next_smatch_str(String, StartPos, NFA) -> -%% {match,Start,Length,Chars,Subs,RestChars} | nomatch. -%% Find the next match in String. Try successive positions until -%% either a match is found or we reach the end of the string. - -next_smatch_str(Cs, P, {Nfa,Start,Sc}) -> - Subs = erlang:make_tuple(Sc, undefined), - next_smatch_str(Cs, P, Nfa, Start, Subs). - -next_smatch_str([_|Cs1]=Cs0, P0, Nfa, Start, Subs0) -> - Ss = eclosure_s(Start, Nfa, [], [], P0, Subs0), - case nfa_str_s(Cs0, P0, Nfa, Ss, nomatch) of - {match,Subs1,P1,Cs} -> {match,P0,P1-P0,Cs0,Subs1,Cs}; - nomatch -> next_smatch_str(Cs1, P0+1, Nfa, Start, Subs0) - end; -next_smatch_str([], P0, Nfa, Start, Subs0) -> - Ss = eclosure_s(Start, Nfa, [], [], P0, Subs0), - case nfa_str_s([], P0, Nfa, Ss, nomatch) of %Try for null match at end. - {match,Subs1,P1,Cs} -> {match,P0,P1-P0,[],Subs1,Cs}; - nomatch -> nomatch - end. - -%% Must do eclosure and parentheses marking when we have a new -%% character. - -%% nfa_str_s(Chars, Pos, NFA, States, Accept) -> -%% {match,Subs,NextPos,RestCars} | nomatch. -%% Run the NFA machine over binary starting at one position until we -%% either have a match or not a match. - -nfa_str_s(_, _, _, [], A) -> A; %No matching states -nfa_str_s([C|[C1|_]=Cs1]=Cs0, P, Nfa, Ss0, A0) -> - Gl = {P,C,C1}, - ?TP("N: ~w x ~w x ~w\n", [Gl,Ss0,A0]), - case step_s(C, Gl, Nfa, Ss0, [], none) of - {Ss1,none} -> - ?TP("N: => ~w x ~w\n", [Ss1,none]), - nfa_str_s(Cs1, P+1, Nfa, Ss1, A0); - {Ss1,Subs} -> - ?TP("N: => ~w x ~w\n", [Ss1,Subs]), - nfa_str_s(Cs1, P+1, Nfa, Ss1, {match,Subs,P,Cs0}) - end; -nfa_str_s([C]=Cs0, P, Nfa, Ss0, A0) -> - Gl = {P,C,eos}, - ?TP("N: ~w x ~w x ~w\n", [Gl,Ss0,A0]), - case step_s(C, Gl, Nfa, Ss0, [], none) of - {Ss1,none} -> - ?TP("N: => ~w x ~w\n", [Ss1,none]), - nfa_str_s([], P+1, Nfa, Ss1, A0); - {Ss1,Subs} -> - ?TP("N: => ~w x ~w\n", [Ss1,Subs]), - nfa_str_s([], P+1, Nfa, Ss1, {match,Subs,P,Cs0}) - end; -nfa_str_s([], P, Nfa, Ss, A) -> %No more characters - case has_match_s(P, Nfa, Ss) of - {yes,Subs} -> {match,Subs,P,[]}; - no -> A %Take what we got - end. - -%% next_smatch_bin(Binary, StartPos, NFA) -> -%% {match,Start,Length,Subs} | nomatch. -%% Find the next match in Binary. Try successive positions until -%% either a match is found or we reach the end of the string. - -next_smatch_bin(Bin, P, {Nfa,Start,Sc}) -> - Subs = erlang:make_tuple(Sc, undefined), - next_smatch_bin(Bin, P, Nfa, Start, Subs). - -next_smatch_bin(Bin, P0, Nfa, Start, Subs0) when P0 < size(Bin) -> - Ss = eclosure_s(Start, Nfa, [], [], P0, Subs0), - case nfa_bin_s(Bin, P0, Nfa, Ss, nomatch) of - {match,Subs1,P1} -> {match,P0,P1-P0,Subs1}; - nomatch -> next_smatch_bin(Bin, P0+1, Nfa, Start, Subs0) - end; -next_smatch_bin(Bin, P0, Nfa, Start, Subs0) -> - Ss = eclosure_s(Start, Nfa, [], [], P0, Subs0), - %% Try for null match at end. - case nfa_bin_s(Bin, P0, Nfa, Ss, nomatch) of - {match,Subs1,P1} -> {match,P0,P1-P0,Subs1}; - nomatch -> nomatch - end. - -%% nfa_bin_s(Binary, Pos, NFA, States, Accept) -> -%% {match,Subs,NextPos} | nomatch. -%% Run the NFA machine over binary starting at one position until we -%% either have a match or not a match. - -nfa_bin_s(_, _, _, [], A) -> A; %No matching states -nfa_bin_s(Bin, P, Nfa, Ss0, A) -> - P1 = P-1, %Number of chars before - case Bin of - <<_:P1/binary,C,C1,_/binary>> -> - Gl = {P,C,C1}, - case step_s(C, Gl, Nfa, Ss0, [], none) of - {Ss1,none} -> - nfa_bin_s(Bin, P+1, Nfa, Ss1, A); - {Ss1,Subs} -> - nfa_bin_s(Bin, P+1, Nfa, Ss1, {match,Subs,P}) - end; - <<_:P1/binary,C,_/binary>> -> - Gl = {P,C,eos}, - case step_s(C, Gl, Nfa, Ss0, [], none) of - {Ss1,none} -> - nfa_bin_s(Bin, P+1, Nfa, Ss1, A); - {Ss1,Subs} -> - nfa_bin_s(Bin, P+1, Nfa, Ss1, {match,Subs,P}) - end; - _ -> %No more characters. - case has_match_s(P, Nfa, Ss0) of - {yes,Subs} -> {match,Subs,P}; - no -> A %Take what we got - end - end. - -%% step_s(Char, GlobalState, NFA, States, NewStates, BestThread) -> -%% {NewStates,NewBest}. -%% Pos is the position of the current character. - -step_s(C, {P,_,_}=Gl, Nfa, [{S,Subs}|Ss], News0, Best) -> - case element(S, Nfa) of - #cstate{c=C,s=N} -> - News1 = eclosure_s(N, Nfa, [], News0, P+1, Subs), - step_s(C, Gl, Nfa, Ss, News1, Best); - #cstate{c=done} -> - step_s(C, Gl, Nfa, Ss, News0, best_subs(Best, Subs)); - #cstate{} -> step_s(C, Gl, Nfa, Ss, News0, Best); - #nstate{cc=Cc,s=N} -> - case match_char(C, Cc) of - true -> - News1 = eclosure_s(N, Nfa, [], News0, P+1, Subs), - step_s(C, Gl, Nfa, Ss, News1, Best); - false -> step_s(C, Gl, Nfa, Ss, News0, Best) - end; - #pstate{t=bos,s=N} -> - if P == 1 -> - %% Add eclosure to *this* level of states - Ss1 = eclosure_s(N, Nfa, [], Ss, P, Subs), - step_s(C, Gl, Nfa, Ss1, News0, Best); - true -> step_s(C, Gl, Nfa, Ss, News0, Best) - end; - #pstate{t=eos,s=N} -> - Ss1 = if element(2, Gl) == $\n, element(3, Gl) == eos -> - %% Add eclosure to *this* level of states - eclosure_s(N, Nfa, [], Ss, P, Subs); - true -> Ss - end, - step_s(C, Gl, Nfa, Ss1, News0, Best) - end; -step_s(_, _, _, [], News, Best) -> {News,Best}. - -%% eclosure_s(State, Nfa, SeenEstates, NewStates, Pos, Subs) -> NewStates. -%% Pos is the position of the *next* character to be processed. - -eclosure_s(S, Nfa, Es, Ss0, P, Subs0) -> - case element(S, Nfa) of - #estate{s1=S1,s2=S2} -> - case member(S, Es) of - true -> Ss0; - false -> - Es1 = [S|Es], - Ss1 = eclosure_s(S1, Nfa, Es1, Ss0, P, Subs0), - eclosure_s(S2, Nfa, Es1, Ss1, P, Subs0) - end; - #lstate{s=S1,n=N} -> - Subs1 = add_lparen(N, P, Subs0), - eclosure_s(S1, Nfa, Es, Ss0, P, Subs1); - #rstate{s=S1,n=N} -> - Subs1 = add_rparen(N, P, Subs0), - eclosure_s(S1, Nfa, Es, Ss0, P, Subs1); - %% All other states get added to state list. - _ -> add_state_s(S, Subs0, Ss0, Ss0) - end. - -%% Want the longest leftmost for sub exprs by saving each parenthesis -%% pair as {-Start,Length} | undefined then a simple comparison of the -%% subs tuples gives the right answer. Bigger is better! - -%% best_subs(OldSubs, NewSubs) -> BestSubs. - -best_subs(Old, New) when Old >= New -> Old; -best_subs(_Old, New) -> New. - -add_state_s(S, Subs, [{S,Subs1}|_Ss], All) when Subs1 > Subs -> All; -add_state_s(S, Subs, [_|Ss], All) -> add_state_s(S, Subs, Ss, All); -add_state_s(S, Subs, [], All) -> [{S,Subs}|All]. - -add_lparen(N, P, Subs) -> - ?TP("L: ~w\n", [{N,P,Subs}]), - %%Negative start to make easier comparison, bigger is better! - Pm = -P, - case element(N, Subs) of - undefined -> setelement(N, Subs, Pm); - {P1,_} when Pm =< P1 -> Subs; - P1 when Pm =< P1 -> Subs - end. - -add_rparen(N, P, Subs) -> - ?TP("R: ~w\n", [{N,P,Subs}]), - case element(N, Subs) of - P1 when is_integer(P1) -> - %% Negative start to make easier comparison, bigger is better! - setelement(N, Subs, {P1,P+P1}); - {_,P2} when P2 =< P -> Subs - end. - -has_match_s(P, Nfa, [{S,Subs}|Ss]) -> - case element(S, Nfa) of - #pstate{t=bos,s=N} -> - if P == 1 -> - %% Add eclosure to *this* level of states - Ss1 = eclosure_s(N, Nfa, [], Ss, P, Subs), - has_match_s(P, Nfa, Ss1); - true -> has_match_s(P, Nfa, Ss) - end; - #pstate{t=eos,s=S1} -> %EOS is always valid here - Ss1 = eclosure_s(S1, Nfa, [], Ss, P, Subs), - has_match_s(P, Nfa, Ss1); - #cstate{c=done} -> - case has_match_s(P, Nfa, Ss) of - {yes,Subs1}=Yes when Subs1 > Subs -> Yes; - _ -> {yes,Subs} - end; - _ -> has_match_s(P, Nfa, Ss) - end; -has_match_s(_, _, []) -> no. - -tt(N, F) -> - statistics(runtime), - statistics(reductions), - Res = tt_loop(N, F), - {_,Reds} = statistics(reductions), - {_,Cpu} = statistics(runtime), - {Res,Reds,Cpu}. - -tt_loop(N, F) when N > 1 -> - F(), tt_loop(N-1, F); -tt_loop(1, F) -> F(); -tt_loop(0, _) -> none. - -loadf(File) -> - {ok,B} = file:read_file(File), - binary_to_list(B). diff --git a/elibs/server.erl b/elibs/server.erl index 00191dc..134f20e 100644 --- a/elibs/server.erl +++ b/elibs/server.erl @@ -45,41 +45,3 @@ loop(LSock) -> gen_tcp:controlling_process(Sock, Pid), loop(LSock). -handle_method(Sock) -> - % get the requested host and method - case gen_tcp:recv(Sock, 0) of - {ok, Header} -> - % io:format("header = ~p~n", [Header]), - {ok, Host} = extract_host(Header), - Method = extract_method_name(Header), - - % dispatch - handle_method_dispatch(Method, Sock, Host, Header); - {error, closed} -> - ok = gen_tcp:close(Sock) - end. - -handle_method_dispatch({ok, "upload-pack"}, Sock, Host, Header) -> - upload_pack:handle(Sock, Host, Header); -handle_method_dispatch({ok, "receive-pack"}, Sock, Host, Header) -> - receive_pack:handle(Sock, Host, Header); -handle_method_dispatch(invalid, Sock, _Host, _Header) -> - gen_tcp:send(Sock, "Invalid method declaration. Upgrade to the latest git.\n"), - ok = gen_tcp:close(Sock). - -extract_method_name(Header) -> - case re:run(Header, "....git[ -][a-z\\-]+ ", []) of - {match, [{Start, Length}]} -> - {ok, string:substr(Header, Start + 9, Length - 9)}; - _Else -> - invalid - end. - -extract_host(Header) -> - case re:run(string:to_lower(Header), "\\000host=[^\\000]+\\000", []) of - {match, [{Start, Length}]} -> - io:format("Header: ~p Start ~p Length ~p~n", [Header, Start, Length]), - {ok, string:substr(Header, Start + 7, Length - 7)}; - _Else -> - {ok, "invalid"} - end. diff --git a/elibs/upload_pack.erl b/elibs/upload_pack.erl deleted file mode 100644 index a084170..0000000 --- a/elibs/upload_pack.erl +++ /dev/null @@ -1,133 +0,0 @@ --module(upload_pack). --export([handle/3]). - --define(READ_SOCKET_TIMEOUT, 10). --define(READ_PORT_TIMEOUT, 100). - -%**************************************************************************** -% -% Entry -% -%**************************************************************************** - -handle(Sock, Host, Header) -> - extract_repo_path(Sock, Host, Header). - -%**************************************************************************** -% -% Main flow -% -%**************************************************************************** - -% Extract the repo from the header. -extract_repo_path(Sock, Host, Header) -> - case re:run(Header, " /[^\\000]+\\000", []) of - {match, [{Start, Length}]} -> - Path = string:substr(Header, Start + 3, Length - 3), - convert_path(Sock, Host, Path); - _Else -> - invalid - end. - -% Convert the repo path to an absolute path as specified by the config file. -convert_path(Sock, Host, Path) -> - case conf:convert_path(Host, Path) of - {ok, FullPath} -> - repo_existence(Sock, Host, Path, FullPath); - {error, nomatch} -> - error_logger:info_msg("no repo match: ~p~n", [Path]), - gen_tcp:send(Sock, "003b\n*********'\n\nNo matching repositories found.\n\n*********"), - ok = gen_tcp:close(Sock) - end. - -% Ensure that the repo exists. -repo_existence(Sock, Host, Path, FullPath) -> - case file_exists(FullPath) of - true -> - export_ok(Sock, Host, Path, FullPath); - false -> - repo_existence_ext(Sock, Host, Path, FullPath) - end. - -% The repo may always be specified without .git on the end -repo_existence_ext(Sock, Host, Path, FullPath) -> - FullPathExt = FullPath ++ ".git", - case file_exists(FullPathExt) of - true -> - export_ok(Sock, Host, Path, FullPathExt); - false -> - error_logger:info_msg("no such repo: ~p~n", [FullPath]), - gen_tcp:send(Sock, "003b\n*********'\n\nNo matching repositories found.\n\n*********"), - ok = gen_tcp:close(Sock) - end. - -% Ensure that a 'git-daemon-export-ok' file is present in the repo. -export_ok(Sock, Host, Path, FullPath) -> - GitDaemonExportOkFilePath = filename:join([FullPath, "git-daemon-export-ok"]), - case file_exists(GitDaemonExportOkFilePath) of - true -> - make_port(Sock, Host, Path, FullPath); - false -> - error_logger:info_msg("permission denied to repo: ~p~n", [FullPath]), - gen_tcp:send(Sock, "0048\n*********'\n\nPermission denied. Repository is not public.\n\n*********"), - ok = gen_tcp:close(Sock) - end. - -% Create the port to 'git upload-pack'. -make_port(Sock, _Host, _Path, FullPath) -> - Command = "git upload-pack " ++ FullPath, - Port = open_port({spawn, Command}, [binary]), - send_port_to_socket(Port, Sock). - -% Send output from port to socket -send_port_to_socket(Port, Sock) -> - receive - {Port, {data, Data}} -> - % io:format("port(~p) = ~p~n", [erlang:size(Data), Data]), - gen_tcp:send(Sock, Data), - case erlang:size(Data) of - 16384 -> - send_port_to_socket(Port, Sock); - _SizeElse -> - case last_byte(Data) of - 10 -> - send_port_to_socket(Port, Sock); - 13 -> - send_port_to_socket(Port, Sock); - _ByteElse -> - send_socket_to_port(Port, Sock) - end - end; - Msg -> - error_logger:error_msg("unknown message ~p~n", [Msg]), - send_socket_to_port(Port, Sock) - after ?READ_PORT_TIMEOUT -> - error_logger:error_msg("timed out waiting for port~n"), - send_socket_to_port(Port, Sock) - end. - -% Send input from socket to port -send_socket_to_port(Port, Sock) -> - case gen_tcp:recv(Sock, 0, ?READ_SOCKET_TIMEOUT) of - {ok, Data} -> - % io:format("socket = ~p~n", [Data]), - port_command(Port, Data), - send_port_to_socket(Port, Sock); - {error, timeout} -> - error_logger:error_msg("read socket timeout~n", []), - send_port_to_socket(Port, Sock); - {error, Reason} -> - error_logger:error_msg("read socket error ~p~n", [Reason]) - end. - -file_exists(FullPath) -> - case file:read_file_info(FullPath) of - {ok, _Info} -> true; - {error, _Reason} -> false - end. - -last_byte(Bin) -> - Size = erlang:size(Bin), - {_B1, B2} = split_binary(Bin, Size - 1), - [Byte] = binary_to_list(B2), - Byte. From 13f2993ceee691307324cf88985ca33a42906b0d Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Tue, 8 Feb 2011 20:56:40 -0500 Subject: [PATCH 06/14] Switch to rebar for the build system --- Makefile | 7 +++++++ README | 6 ++++-- Rakefile | 13 ------------- rebar | Bin 0 -> 95259 bytes {elibs => src}/conf.erl | 0 ebin/egitd.app => src/egitd.app.src | 2 +- {elibs => src}/egitd.erl | 0 {elibs => src}/egitd_app.erl | 0 {elibs => src}/egitd_sup.erl | 0 {elibs => src}/git_client.erl | 0 {elibs => src}/log.erl | 0 {elibs => src}/md5.erl | 0 {elibs => src}/server.erl | 0 13 files changed, 12 insertions(+), 16 deletions(-) create mode 100644 Makefile delete mode 100644 Rakefile create mode 100755 rebar rename {elibs => src}/conf.erl (100%) rename ebin/egitd.app => src/egitd.app.src (81%) rename {elibs => src}/egitd.erl (100%) rename {elibs => src}/egitd_app.erl (100%) rename {elibs => src}/egitd_sup.erl (100%) rename {elibs => src}/git_client.erl (100%) rename {elibs => src}/log.erl (100%) rename {elibs => src}/md5.erl (100%) rename {elibs => src}/server.erl (100%) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..08e2693 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +compile: + @./rebar compile + +clean: + @./rebar clean + +.PHONY: compile clean diff --git a/README b/README index 6be8a96..95bc074 100644 --- a/README +++ b/README @@ -18,7 +18,9 @@ INSTALL $ git clone git@github.com:mojombo/egitd.git $ cd egitd -$ rake +$ make + +You can also build by using ./rebar compile, which the Makefile invokes. RUN @@ -32,4 +34,4 @@ CONF FILE The config file specifies the mapping between specified repo name and file location. -github.com (.+)/(.+) "/data/git/repositories/" ++ Match1 ++ "/" ++ Match2. \ No newline at end of file +github.com (.+)/(.+) "/data/git/repositories/" ++ Match1 ++ "/" ++ Match2. diff --git a/Rakefile b/Rakefile deleted file mode 100644 index f27b0af..0000000 --- a/Rakefile +++ /dev/null @@ -1,13 +0,0 @@ -require 'rubygems' -require 'rake' - -ERLC_FLAGS = "+debug_info -W2 -o ../ebin" - -task :default do - cd "elibs" - sh "erlc #{ERLC_FLAGS} *.erl" -end - -task :console do - sh "erl +Bc +K true -smp enable -pz ./ebin/ -sname local_console_#{$$} -kernel start_boot_server true" -end \ No newline at end of file diff --git a/rebar b/rebar new file mode 100755 index 0000000000000000000000000000000000000000..a97a678910a41dc331bc6f1fee02e86323aa51ba GIT binary patch literal 95259 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�GLt2J6wrdDp%Huh79X}QXp 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 literal 0 HcmV?d00001 diff --git a/elibs/conf.erl b/src/conf.erl similarity index 100% rename from elibs/conf.erl rename to src/conf.erl diff --git a/ebin/egitd.app b/src/egitd.app.src similarity index 81% rename from ebin/egitd.app rename to src/egitd.app.src index 4c9dcb9..4700cdb 100644 --- a/ebin/egitd.app +++ b/src/egitd.app.src @@ -1,7 +1,7 @@ {application, egitd, [{description, "The Erlang git-daemon"}, {vsn, "0.0.0"}, - {modules, [egitd_app, egitd_sup, server]}, + {modules, []}, {registered, [server]}, {applications, [kernel, stdlib]}, {mod, {egitd_app, []}}, diff --git a/elibs/egitd.erl b/src/egitd.erl similarity index 100% rename from elibs/egitd.erl rename to src/egitd.erl diff --git a/elibs/egitd_app.erl b/src/egitd_app.erl similarity index 100% rename from elibs/egitd_app.erl rename to src/egitd_app.erl diff --git a/elibs/egitd_sup.erl b/src/egitd_sup.erl similarity index 100% rename from elibs/egitd_sup.erl rename to src/egitd_sup.erl diff --git a/elibs/git_client.erl b/src/git_client.erl similarity index 100% rename from elibs/git_client.erl rename to src/git_client.erl diff --git a/elibs/log.erl b/src/log.erl similarity index 100% rename from elibs/log.erl rename to src/log.erl diff --git a/elibs/md5.erl b/src/md5.erl similarity index 100% rename from elibs/md5.erl rename to src/md5.erl diff --git a/elibs/server.erl b/src/server.erl similarity index 100% rename from elibs/server.erl rename to src/server.erl From 004ae965d69771fb3aeedbee262cb98b48f0b607 Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Tue, 8 Feb 2011 21:15:13 -0500 Subject: [PATCH 07/14] Cleanup module names to be less likely to crash --- src/egitd.app.src | 2 +- src/{conf.erl => egitd_conf.erl} | 2 +- src/{git_client.erl => egitd_connection.erl} | 2 +- src/{server.erl => egitd_server.erl} | 4 ++-- src/egitd_sup.erl | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename src/{conf.erl => egitd_conf.erl} (98%) rename src/{git_client.erl => egitd_connection.erl} (99%) rename src/{server.erl => egitd_server.erl} (93%) diff --git a/src/egitd.app.src b/src/egitd.app.src index 4700cdb..9f1392c 100644 --- a/src/egitd.app.src +++ b/src/egitd.app.src @@ -2,7 +2,7 @@ [{description, "The Erlang git-daemon"}, {vsn, "0.0.0"}, {modules, []}, - {registered, [server]}, + {registered, [egitd_server]}, {applications, [kernel, stdlib]}, {mod, {egitd_app, []}}, {start_phases, []} diff --git a/src/conf.erl b/src/egitd_conf.erl similarity index 98% rename from src/conf.erl rename to src/egitd_conf.erl index a99565b..d73517b 100644 --- a/src/conf.erl +++ b/src/egitd_conf.erl @@ -1,4 +1,4 @@ --module(conf). +-module(egitd_conf). -export([read_conf/1, convert_path/2, eval_erlang_expr/1, eval_erlang_expr/2, concat/2, namespace3/1, md5_namespace3/1, hexmod8/1]). diff --git a/src/git_client.erl b/src/egitd_connection.erl similarity index 99% rename from src/git_client.erl rename to src/egitd_connection.erl index 19e4173..41ba563 100644 --- a/src/git_client.erl +++ b/src/egitd_connection.erl @@ -1,4 +1,4 @@ --module(git_client). +-module(egitd_connection). -behaviour(gen_server). -record(state, { diff --git a/src/server.erl b/src/egitd_server.erl similarity index 93% rename from src/server.erl rename to src/egitd_server.erl index 134f20e..b9b7b7c 100644 --- a/src/server.erl +++ b/src/egitd_server.erl @@ -1,4 +1,4 @@ --module(server). +-module(egitd_server). -export([start_link/0, init/1]). start_link() -> @@ -41,7 +41,7 @@ try_listen(Times) -> loop(LSock) -> {ok, Sock} = gen_tcp:accept(LSock), - {ok, Pid} = git_client:start_link(Sock), + {ok, Pid} = egitd_connection:start_link(Sock), gen_tcp:controlling_process(Sock, Pid), loop(LSock). diff --git a/src/egitd_sup.erl b/src/egitd_sup.erl index 235f4d1..5c9e2f4 100644 --- a/src/egitd_sup.erl +++ b/src/egitd_sup.erl @@ -24,6 +24,6 @@ init([]) -> {ok, {{one_for_one, 100, 300}, [{server, - {server, start_link, []}, + {egitd_server, start_link, []}, permanent, 10000, worker, [server]} ]}}. From dcbd2259b18524549626dc790b686bbbce6490cb Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Tue, 8 Feb 2011 23:15:09 -0500 Subject: [PATCH 08/14] Tider --- src/egitd_conf.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/egitd_conf.erl b/src/egitd_conf.erl index d73517b..26e46a7 100644 --- a/src/egitd_conf.erl +++ b/src/egitd_conf.erl @@ -29,7 +29,7 @@ create_binding(Matches) -> Modder = fun(Word, Acc) -> {I, Arr} = Acc, Mod = {I, Word}, - {I + 1, lists:append(Arr, [Mod])} + {I + 1, Arr ++ [Mod]} end, {_X, Matches2} = lists:foldl(Modder, {1, []}, Matches), Binder = fun(Match, B) -> From ecaa0c29a016262759c9f57c538da993c32a6211 Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Wed, 9 Feb 2011 12:16:20 -0500 Subject: [PATCH 09/14] Fix the throw/catch flow contol --- src/egitd_connection.erl | 46 +++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/egitd_connection.erl b/src/egitd_connection.erl index 41ba563..64093e4 100644 --- a/src/egitd_connection.erl +++ b/src/egitd_connection.erl @@ -75,34 +75,36 @@ code_change(_OldVsn, State, _Extra) -> %% ------------------------------------------------------------------ dispatch_method(<<"upload-pack">>, Host, Path, #state{socket = Sock} = State) -> - try conf:convert_path(binary_to_list(Host), binary_to_list(Path)) of - {ok, FullPath} -> - case repo_existance(FullPath) of - false -> - throw(nomatch); - RealPath -> - GitDaemonExportOkFilePath = filename:join([RealPath, "git-daemon-export-ok"]), - case filelib:is_regular(GitDaemonExportOkFilePath) of - true -> - %% all validated, yay - io:format("ready to do an upload-pack~n"), - Port = make_port(Sock, "upload-pack", Host, Path, RealPath), - inet:setopts(Sock, [{active, once}]), - {noreply, State#state{port = Port}}; - false -> - throw({noexport, RealPath}) - end - end; - {error, nomatch} -> - throw(nomatch) + try + case conf:convert_path(binary_to_list(Host), binary_to_list(Path)) of + {ok, FullPath} -> + case repo_existance(FullPath) of + false -> + throw(nomatch); + RealPath -> + GitDaemonExportOkFilePath = filename:join([RealPath, "git-daemon-export-ok"]), + case filelib:is_regular(GitDaemonExportOkFilePath) of + true -> + %% all validated, yay + io:format("ready to do an upload-pack~n"), + Port = make_port(Sock, "upload-pack", Host, Path, RealPath), + inet:setopts(Sock, [{active, once}]), + {noreply, State#state{port = Port}}; + false -> + throw({noexport, RealPath}) + end + end; + {error, nomatch} -> + throw(nomatch) + end catch throw:nomatch -> error_logger:info_msg("no repo match: ~p~n", [Path]), gen_tcp:send(Sock, "003b\n*********'\n\nNo matching repositories found.\n\n*********"), gen_tcp:close(Sock), {stop, normal, State}; - throw:{noexport, RealPath} -> - error_logger:info_msg("permission denied to repo: ~p~n", [RealPath]), + throw:{noexport, ThePath} -> + error_logger:info_msg("permission denied to repo: ~p~n", [ThePath]), gen_tcp:send(Sock, "0048\n*********'\n\nPermission denied. Repository is not public.\n\n*********"), gen_tcp:close(Sock), {stop, normal, State} From d3b8a83e4eaa0d496546b52322128cbbac2e7dd5 Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Fri, 11 Feb 2011 21:56:01 -0500 Subject: [PATCH 10/14] Better error messages to client --- src/egitd_connection.erl | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/egitd_connection.erl b/src/egitd_connection.erl index 64093e4..1ceee92 100644 --- a/src/egitd_connection.erl +++ b/src/egitd_connection.erl @@ -55,7 +55,7 @@ handle_info({_SocketType, Socket, <<_Length:4/binary, "git", _:1/binary, Rest/bi io:format("git method ~p; args ~p host ~p~n", [Method, Args, Host]), dispatch_method(Method, Host, Args, State); handle_info({_SocketType, Socket, _Packet}, #state{socket = Socket} = State) -> - gen_tcp:send(Socket, "Invalid method declaration. Upgrade to the latest git.\n"), + send_error(Socket, "\n*********\n\nInvalid method declaration. Upgrade to the latest git.\n\n*********'"), gen_tcp:close(Socket), {stop, normal, State}; handle_info({tcp_closed, Socket}, #state{socket = Socket} = State) -> @@ -100,22 +100,22 @@ dispatch_method(<<"upload-pack">>, Host, Path, #state{socket = Sock} = State) -> catch throw:nomatch -> error_logger:info_msg("no repo match: ~p~n", [Path]), - gen_tcp:send(Sock, "003b\n*********'\n\nNo matching repositories found.\n\n*********"), + send_error(Sock, ["\n*********\n\nNo matching repositories found for git://", Host, Path, ".\n\n*********"]), gen_tcp:close(Sock), {stop, normal, State}; throw:{noexport, ThePath} -> error_logger:info_msg("permission denied to repo: ~p~n", [ThePath]), - gen_tcp:send(Sock, "0048\n*********'\n\nPermission denied. Repository is not public.\n\n*********"), + send_error(Sock, ["\n*********\n\nPermission denied. Repository git://", Host, Path, " is not public.\n\n*********"]), gen_tcp:close(Sock), {stop, normal, State} end; -dispatch_method(<<"receive-pack">>, _Host, _Args, #state{socket = Sock} = State) -> - %% TODO make this message include the actual repo - gen_tcp:send(Sock, "006d\n*********'\n\nYou can't push to git://github.com/user/repo.git\nUse git@github.com:user/repo.git\n\n*********"), +dispatch_method(<<"receive-pack">>, Host, Path, #state{socket = Sock} = State) -> + SSHPath = [":", binary:part(Path, {1, byte_size(Path) -1})], + send_error(Sock, ["\n*********\n\nYou can't push to git://", Host, Path, "\nUse git@", Host, SSHPath, "\n\n*********"]), gen_tcp:close(Sock), {stop, normal, State}; -dispatch_method(_Method, _Host, _Args, #state{socket = Sock} = State) -> - gen_tcp:send(Sock, "Invalid method declaration. Upgrade to the latest git.\n"), +dispatch_method(Method, _Host, _Args, #state{socket = Sock} = State) -> + send_error(Sock, ["\n*********\n\nInvalid method declaration: '", Method, "'. Upgrade to the latest git.\n\n*********'"]), gen_tcp:close(Sock), {stop, normal, State}. @@ -134,6 +134,10 @@ repo_existance(Path) -> make_port(_Sock, Method, _Host, _Path, FullPath) -> Command = lists:flatten(["git ", Method, " ", FullPath]), - io:format("making port with command ~p~n", [Command]), open_port({spawn, Command}, [binary]). +send_error(Socket, Error) -> + FlatError = list_to_binary(Error), + ErrorMsg = io_lib:format("~4.16.0B~s", [byte_size(FlatError)+4, FlatError]), + gen_tcp:send(Socket, ErrorMsg). + From d904c77c50cfb3731d08265a3874a872c7b41c56 Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Fri, 11 Feb 2011 22:02:55 -0500 Subject: [PATCH 11/14] Cleanup some debug messages --- src/egitd_connection.erl | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/egitd_connection.erl b/src/egitd_connection.erl index 1ceee92..03bcd3b 100644 --- a/src/egitd_connection.erl +++ b/src/egitd_connection.erl @@ -41,18 +41,15 @@ handle_cast(_Msg, State) -> handle_info({Port, {data, Data}}, #state{socket = Sock, port = Port} = State) -> - %io:format("forwarding data to socket ~p~n", [Data]), gen_tcp:send(Sock, Data), {noreply, State}; handle_info({_SocketType, Socket, Packet}, #state{socket = Socket, port = Port} = State) when is_port(Port) -> - %io:format("forwarding data to port ~p~n", [Packet]), port_command(Port, Packet), inet:setopts(Socket, [{active, once}]), {noreply, State}; handle_info({_SocketType, Socket, <<_Length:4/binary, "git", _:1/binary, Rest/binary>>}, #state{socket = Socket} = State) -> [Method, Other] = binary:split(Rest, <<" ">>), [Args, <<"host=", Host/binary>>, <<>>] = binary:split(Other, <<0>>, [global]), - io:format("git method ~p; args ~p host ~p~n", [Method, Args, Host]), dispatch_method(Method, Host, Args, State); handle_info({_SocketType, Socket, _Packet}, #state{socket = Socket} = State) -> send_error(Socket, "\n*********\n\nInvalid method declaration. Upgrade to the latest git.\n\n*********'"), @@ -61,7 +58,7 @@ handle_info({_SocketType, Socket, _Packet}, #state{socket = Socket} = State) -> handle_info({tcp_closed, Socket}, #state{socket = Socket} = State) -> {stop, normal, State}; handle_info(_Info, State) -> - io:format("unhandled info ~p~n", [_Info]), + error_logger:info_msg("unhandled info ~p~n", [_Info]), {noreply, State}. terminate(_Reason, _State) -> @@ -86,7 +83,6 @@ dispatch_method(<<"upload-pack">>, Host, Path, #state{socket = Sock} = State) -> case filelib:is_regular(GitDaemonExportOkFilePath) of true -> %% all validated, yay - io:format("ready to do an upload-pack~n"), Port = make_port(Sock, "upload-pack", Host, Path, RealPath), inet:setopts(Sock, [{active, once}]), {noreply, State#state{port = Port}}; From 3e0ff4f3955011b2ecbe615fbdea9da8000bdd52 Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Fri, 11 Feb 2011 22:09:39 -0500 Subject: [PATCH 12/14] Update README with details of the rewrite --- README | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README b/README index 95bc074..36efb75 100644 --- a/README +++ b/README @@ -12,6 +12,15 @@ if the upload-pack takes a long time to respond (for big repos), either the timeouts have to be increased to unreasonable values (slowing the entire transfer down), or some connections will timeout and fail. +The above problem has actually been solved by a rewrite I (Vagabond) have +been doing as an exercise in how to improve the performance of erlang +applications. egitd is now only marginally slower than git-daemon on the +same repo, and does not suffer the timeout issues mentioned above even on +the large repos like gentoo's portage where github was having problems. I +still don't advocate its use in production, but it might be usable now. + +You can see my rewrite notes here: http://andrew.hijacked.us/by_keyword/328/egit + INSTALL ------- From 71496623250309e7acf2fe5d36d4c61510b08643 Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Mon, 28 Feb 2011 09:13:30 -0500 Subject: [PATCH 13/14] Fix a reference to the old module name, thanks Jay True --- src/egitd_server.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/egitd_server.erl b/src/egitd_server.erl index b9b7b7c..55fccf0 100644 --- a/src/egitd_server.erl +++ b/src/egitd_server.erl @@ -16,7 +16,7 @@ init(Parent) -> read_conf() -> {ok, Conf} = application:get_env(conf), error_logger:info_msg("Using conf file ~p~n", [Conf]), - conf:read_conf(Conf). + egitd_conf:read_conf(Conf). init_log() -> init_log(application:get_env(log)). From 093f078803b9466a148572f394623a2f497bd431 Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Thu, 1 Sep 2011 13:05:17 -0400 Subject: [PATCH 14/14] Fix typo reported by Jiang Bob --- src/egitd_connection.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/egitd_connection.erl b/src/egitd_connection.erl index 03bcd3b..6de2957 100644 --- a/src/egitd_connection.erl +++ b/src/egitd_connection.erl @@ -73,7 +73,7 @@ code_change(_OldVsn, State, _Extra) -> dispatch_method(<<"upload-pack">>, Host, Path, #state{socket = Sock} = State) -> try - case conf:convert_path(binary_to_list(Host), binary_to_list(Path)) of + case egitd_conf:convert_path(binary_to_list(Host), binary_to_list(Path)) of {ok, FullPath} -> case repo_existance(FullPath) of false ->