Avoid logging creds on couch_replicator termination

When couch_replicator terminates with an error we log the #rep
record which can contain credentials for the source or target
of a replication, either in the url directly or in an Authorization
header.

This commit adds a function to strip credentials from the #httpdb
records in the #rep record and replace them with ****.

Specifically this concerns the url and headers fields of the
 #rep.source and #rep.target #httpdb records.

We also add the format_status/2 callback and strip creds from the
 #rep_state record in the gen_server state to prevent the creds
in the state getting logged in the event of a crash.

Closes COUCHDB-2949

This closes #25
diff --git a/src/couch_replicator.erl b/src/couch_replicator.erl
index ff18223..29225e3 100644
--- a/src/couch_replicator.erl
+++ b/src/couch_replicator.erl
@@ -24,6 +24,7 @@
 % gen_server callbacks
 -export([init/1, terminate/2, code_change/3]).
 -export([handle_call/3, handle_cast/2, handle_info/2]).
+-export([format_status/2]).
 
 -export([details/1]).
 
@@ -502,6 +503,42 @@
     {ok, State}.
 
 
+headers_strip_creds([], Acc) ->
+    lists:reverse(Acc);
+headers_strip_creds([{Key, Value0} | Rest], Acc) ->
+    Value = case string:to_lower(Key) of
+    "authorization" ->
+        "****";
+    _ ->
+        Value0
+    end,
+    headers_strip_creds(Rest, [{Key, Value} | Acc]).
+
+
+httpdb_strip_creds(#httpdb{url = Url, headers = Headers} = HttpDb) ->
+    HttpDb#httpdb{
+        url = couch_util:url_strip_password(Url),
+        headers = headers_strip_creds(Headers, [])
+    }.
+
+
+rep_strip_creds(#rep{source = Source, target = Target} = Rep) ->
+    Rep#rep{
+        source = httpdb_strip_creds(Source),
+        target = httpdb_strip_creds(Target)
+    }.
+
+
+state_strip_creds(#rep_state{rep_details = Rep, source = Source, target = Target} = State) ->
+    % #rep_state contains the source and target at the top level and also
+    % in the nested #rep_details record
+    State#rep_state{
+        rep_details = rep_strip_creds(Rep),
+        source = httpdb_strip_creds(Source),
+        target = httpdb_strip_creds(Target)
+    }.
+
+
 terminate(normal, #rep_state{rep_details = #rep{id = RepId} = Rep,
     checkpoint_history = CheckpointHistory} = State) ->
     terminate_cleanup(State),
@@ -516,8 +553,9 @@
 terminate(shutdown, {error, Class, Error, Stack, InitArgs}) ->
     #rep{id=RepId} = InitArgs,
     couch_stats:increment_counter([couch_replicator, failed_starts]),
+    CleanInitArgs = rep_strip_creds(InitArgs),
     couch_log:error("~p:~p: Replication failed to start for args ~p: ~p",
-             [Class, Error, InitArgs, Stack]),
+             [Class, Error, CleanInitArgs, Stack]),
     case Error of
     {unauthorized, DbUri} ->
         NotifyError = {unauthorized, <<"unauthorized to access or create database ", DbUri/binary>>};
@@ -548,6 +586,10 @@
     couch_replicator_api_wrap:db_close(State#rep_state.target).
 
 
+format_status(_Opt, [_PDict, State]) ->
+    [{data, [{"State", state_strip_creds(State)}]}].
+
+
 do_last_checkpoint(#rep_state{seqs_in_progress = [],
     highest_seq_done = {_Ts, ?LOWEST_SEQ}} = State) ->
     {stop, normal, cancel_timer(State)};