1 -module(noregex).
   2 -export([main/1]).
   3 
   4 isalphanum(C) when C > 47, C < 58; C > 64, C < 91; C > 96, C < 123 -> true;
   5 isalphanum(_) -> false.
   6 
   7 %% Generate a temporary file name of length N
   8 genname(0, L) -> L;
   9 genname(N, L) ->
  10     R = random:uniform(123),
  11     case isalphanum(R) of
  12         true -> genname(N-1, [R|L]);
  13         false -> genname(N, L)
  14     end.
  15 
  16 %% Returns a randomly generated temporary file path where the basename is
  17 %% of length N
  18 mktemppath(Prefix, N) -> Prefix ++ "/" ++ genname(N, []).
  19 
  20 %% Removes passwords embedded in URLs from a log file.
  21 scrub_file(Tmpdir, F) ->
  22     %% make a temporary directory if it does not exist yet.
  23     case file:make_dir(Tmpdir) of
  24         ok -> ok;
  25         {error,eexist} -> ok;
  26         _ -> exit({error, failed_to_make_tmpdir})
  27     end,
  28 
  29     %% Move the original file out of the way.
  30     T = mktemppath(Tmpdir, 16),
  31     case file:rename(F, T) of
  32         ok -> ok;
  33         _ -> exit({error, failed_to_move_file})
  34     end,
  35 
  36     %% Now open it for reading.
  37     {_, In} = file:open([T], read),
  38     %% Open the original path for writing.
  39     {_, Out} = file:open([F], write),
  40 
  41     %% Call the function that will scrub the lines.
  42     scrub_lines(In, Out),
  43 
  44     %% Close the file handles and return the path to the original file.
  45     file:close(Out),
  46     file:close(In),
  47     T.
  48 
  49 %% This is where the log file is actually read linewise and where
  50 %% the scrubbing function is invoked for lines that contain URLs.
  51 scrub_lines(In, Out) ->
  52     L = io:get_line(In, ''),
  53     case L of
  54         eof -> ok;
  55         _ ->
  56             %% Does the line contain URLs?
  57             case string:str(L, "://") of
  58                 0 -> io:format(Out, "~s", [L]);
  59                 _ -> S = scrub(split(L), 0, []),
  60                      io:format(Out, "~s", [S])
  61             end,
  62             %% Continue with next line.
  63             scrub_lines(In, Out)
  64     end.
  65 
  66 %% This function operates on a list of strings where each string with an
  67 %% odd index was originally on the right hand side of a "://" token.
  68 %% It is these odd numbered strings that need to be checked for embedded
  69 %% authentication credentials.
  70 scrub([], _, R) -> string_join("://", lists:reverse(R));
  71 scrub([H|T], Idx, R) ->
  72     case (Idx rem 2) of
  73         %% This is a string that was originally to left of a "://" token,
  74         %% no need to sanitize.
  75         0 -> scrub(T, Idx+1, [H|R]);
  76         %% This string was originally to right of a "://" token, sanitize!
  77         1 -> scrub(T, Idx+1, [scrub_token(H)|R])
  78     end.
  79 
  80 %% Remove an embedded "user:password@" sequence in the string passed.
  81 scrub_token(Text) ->
  82     Pos = string:chr(Text, $@),
  83     case Pos of
  84         0 -> Text;
  85         _ -> string:right(Text, string:len(Text)-Pos)
  86     end.
  87 
  88 %% Split lines that contain URLs into strings ("://" is the delimiter).
  89 split(L) -> splitline(L, []).
  90 
  91 splitline(Text, R) ->
  92     Pos = string:str(Text, "://"),
  93     %% Does the text contain the "://" token?
  94     case Pos of
  95         0 -> lists:reverse([Text|R]);   %% no
  96         _ ->
  97             %% "://" token found, add the left hand side text to the result
  98             %% accumulator and continue tokenizing the text to the right.
  99             Left = string:left(Text, Pos-1),
 100             Right = string:right(Text, string:len(Text)-Pos-2),
 101             splitline(Right, [Left|R])
 102     end.
 103 
 104 %% Borrowed from http://www.trapexit.org/String_join_with %%%%%%%%%%%%%%
 105 %% Thanks folks!
 106 string_join(Join, L) ->
 107     string_join(Join, L, fun(E) -> E end).
 108 
 109 string_join(_Join, L=[], _Conv) ->
 110     L;
 111 string_join(Join, [H|Q], Conv) ->
 112     lists:flatten(lists:concat(
 113         [Conv(H)|lists:map(fun(E) -> [Join, Conv(E)] end, Q)]
 114     )).
 115 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 116 
 117 %% Main function.
 118 main([A]) ->
 119     {A1,A2,A3} = now(),
 120     random:seed(A1, A2, A3),
 121 
 122     %% A single argument (the name of the file to be scrubbed) is expected.
 123     F = atom_to_list(A),
 124     T = scrub_file("tmp", F),
 125 
 126     %% The scrubbed file content will be written to a new file that's
 127     %% in the place of the original file. Where was the latter moved to?
 128     io:format("~s~n", [T]),
 129 
 130     init:stop().