private void SaveResolvedDocument([NotNull] Document resolved, [NotNull] Conflict conflict)
        {
            InBatch(() =>
            {
                var doc      = conflict.Mine;
                var otherDoc = conflict.Theirs;

                // Figure out what revision to delete and what if anything to add
                string winningRevID, losingRevID;
                byte[] mergedBody = null;
                if (ReferenceEquals(resolved, otherDoc))
                {
                    winningRevID = otherDoc.RevID;
                    losingRevID  = doc.RevID;
                }
                else
                {
                    winningRevID = doc.RevID;
                    losingRevID  = otherDoc.RevID;
                    if (!ReferenceEquals(resolved, doc))
                    {
                        resolved.Database = this;
                        mergedBody        = resolved.Encode();
                    }
                }

                // Tell LiteCore to do the resolution:
                var rawDoc = doc.c4Doc?.HasValue == true ? doc.c4Doc.RawDoc : null;
                LiteCoreBridge.Check(err =>
                                     Native.c4doc_resolveConflict(rawDoc, winningRevID, losingRevID, mergedBody, err));
                LiteCoreBridge.Check(err => Native.c4doc_save(rawDoc, 0, err));
                var logID = new SecureLogString(doc.Id, LogMessageSensitivity.PotentiallyInsecure);
                Log.To.Database.I(Tag, $"Conflict resolved as doc '{logID}' rev {rawDoc->revID.CreateString()}");
            });
        }
Beispiel #2
0
        private void OnDocError(C4Error error, bool pushing, [NotNull] string docID, bool transient)
        {
            if (_disposed)
            {
                return;
            }

            var logDocID = new SecureLogString(docID, LogMessageSensitivity.PotentiallyInsecure);

            if (!pushing && error.domain == C4ErrorDomain.LiteCoreDomain && error.code == (int)C4ErrorCode.Conflict)
            {
                // Conflict pulling a document -- the revision was added but app needs to resolve it:
                var safeDocID = new SecureLogString(docID, LogMessageSensitivity.PotentiallyInsecure);
                Log.To.Sync.I(Tag, $"{this} pulled conflicting version of '{safeDocID}'");
                try {
                    Config.Database.ResolveConflict(docID);
                } catch (Exception e) {
                    Log.To.Sync.W(Tag, $"Conflict resolution of '{logDocID}' failed", e);
                }
            }
            else
            {
                var transientStr = transient ? "transient " : String.Empty;
                var dirStr       = pushing ? "pushing" : "pulling";
                Log.To.Sync.I(Tag,
                              $"{this}: {transientStr}error {dirStr} '{logDocID}' : {error.code} ({Native.c4error_getMessage(error)})");
            }
        }
Beispiel #3
0
        private void OnDocEndedWithConflict(List <ReplicatedDocument> replications)
        {
            if (_disposed)
            {
                return;
            }

            for (int i = 0; i < replications.Count; i++)
            {
                var replication = replications[i];
                // Conflict pulling a document -- the revision was added but app needs to resolve it:
                var safeDocID = new SecureLogString(replication.Id, LogMessageSensitivity.PotentiallyInsecure);
                WriteLog.To.Sync.I(Tag, $"{this} pulled conflicting version of '{safeDocID}'");
                Task t = Task.Run(() =>
                {
                    try {
                        Config.Database.ResolveConflict(replication.Id, Config.ConflictResolver);
                        replication = replication.ClearError();
                    } catch (CouchbaseException e) {
                        replication.Error = e;
                    } catch (Exception e) {
                        replication.Error = new CouchbaseLiteException(C4ErrorCode.UnexpectedError, e.Message, e);
                    }

                    if (replication.Error != null)
                    {
                        WriteLog.To.Sync.W(Tag, $"Conflict resolution of '{replication.Id}' failed", replication.Error);
                    }

                    _documentEndedUpdate.Fire(this, new DocumentReplicationEventArgs(new[] { replication }, false));
                });
                _conflictTasks.TryAdd(t.ContinueWith(task => _conflictTasks.TryRemove(t, out var dummy)), 0);
            }
        }
        public void Purge(Document document)
        {
            CBDebug.MustNotBeNull(Log.To.Database, Tag, nameof(document), document);

            ThreadSafety.DoLocked(() =>
            {
                CheckOpen();
                VerifyDB(document);

                if (!document.Exists)
                {
                    var docID = new SecureLogString(document.Id, LogMessageSensitivity.PotentiallyInsecure);
                    Log.To.Database.V(Tag, $"Ignoring purge of non-existent document {docID}");
                    return;
                }

                InBatch(() =>
                {
                    var result = Native.c4doc_purgeRevision(document.c4Doc.RawDoc, null, null);
                    if (result >= 0)
                    {
                        LiteCoreBridge.Check(err => Native.c4doc_save(document.c4Doc.RawDoc, 0, err));
                    }
                });
            });
        }
        protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken)
        {
            int retryCount;

            do
            {
                if (Authenticator != null && response.StatusCode == HttpStatusCode.Unauthorized)
                {
                    retryCount = _retryMessages.GetOrAdd(response, 0);
                    if (retryCount >= 5)
                    {
                        // Multiple concurrent requests means that the Nc can sometimes get out of order
                        // so try again, but within reason.
                        break;
                    }

                    _retryMessages.TryUpdate(response, retryCount + 1, retryCount);
                    var newRequest = new HttpRequestMessage(response.RequestMessage.Method, response.RequestMessage.RequestUri);
                    foreach (var header in response.RequestMessage.Headers)
                    {
                        if (header.Key != "Authorization")
                        {
                            newRequest.Headers.Add(header.Key, header.Value);
                        }
                    }

                    newRequest.Content = response.RequestMessage.Content;
                    var challengeResponse = Authenticator.ResponseFromChallenge(response);
                    if (challengeResponse != null)
                    {
                        newRequest.Headers.Add("Authorization", challengeResponse);
                        return(ProcessResponse(SendAsync(newRequest, cancellationToken).Result, cancellationToken));
                    }
                }
            }  while(false);

            var hasSetCookie = response.Headers.Contains("Set-Cookie");

            if (hasSetCookie)
            {
                var cookie = default(Cookie);
                if (CookieParser.TryParse(response.Headers.GetValues("Set-Cookie").ElementAt(0), response.RequestMessage.RequestUri.Host,
                                          out cookie))
                {
                    lock (_locker) {
                        try {
                            _cookieStore.Add(cookie);
                        } catch (CookieException e) {
                            var headerValue = new SecureLogString(response.Headers.GetValues("Set-Cookie").ElementAt(0),
                                                                  LogMessageSensitivity.Insecure);
                            Log.To.Sync.W("DefaultAuthHandler",
                                          $"Invalid cookie string received from remote: {headerValue}", e);
                        }
                    }
                }
            }

            _retryMessages.TryRemove(response, out retryCount);
            return(response);
        }
Beispiel #6
0
        private void OnDocEnded(List <ReplicatedDocument> replications, bool pushing)
        {
            if (_disposed)
            {
                return;
            }

            for (int i = 0; i < replications.Count; i++)
            {
                var replication  = replications[i];
                var docID        = replication.Id;
                var error        = replication.NativeError;
                var transient    = replication.IsTransient;
                var logDocID     = new SecureLogString(docID, LogMessageSensitivity.PotentiallyInsecure);
                var transientStr = transient ? "transient " : String.Empty;
                var dirStr       = pushing ? "pushing" : "pulling";
                if (error.code > 0)
                {
                    WriteLog.To.Sync.I(Tag,
                                       $"{this}: {transientStr}error {dirStr} '{logDocID}' : {error.code} ({Native.c4error_getMessage(error)})");
                }
            }

            _documentEndedUpdate.Fire(this, new DocumentReplicationEventArgs(replications, pushing));
        }
        private void AddRequestHeaders(HttpRequestMessage request)
        {
            foreach (var requestHeaderKey in RequestHeaders.Keys)
            {
                if (requestHeaderKey.ToLowerInvariant() == "cookie")
                {
                    Cookie cookie;
                    var    cookieStr = RequestHeaders[requestHeaderKey];
                    if (!CookieParser.TryParse(cookieStr, request.RequestUri.Host, out cookie))
                    {
                        Log.To.Sync.W(Tag, "Invalid cookie string received, {0}",
                                      new SecureLogString(cookieStr, LogMessageSensitivity.Insecure));
                    }
                    else
                    {
                        try {
                            CookieStore.Add(cookie);
                        } catch (CookieException e) {
                            var headerValue = new SecureLogString(cookieStr, LogMessageSensitivity.Insecure);
                            Log.To.Sync.W(Tag, $"Invalid cookie string received, {headerValue}", e);
                        }
                    }

                    request.Headers.Add("Cookie", CookieStore.GetCookieHeader(request.RequestUri));
                    continue;
                }


                request.Headers.Add(requestHeaderKey, RequestHeaders.Get(requestHeaderKey));
            }
        }
Beispiel #8
0
        private void OnDocEnded(ReplicatedDocument[] replications, bool pushing)
        {
            if (_disposed)
            {
                return;
            }

            for (int i = 0; i < replications.Length; i++)
            {
                var replication = replications[i];
                var docID       = replication.Id;
                var error       = replication.NativeError;
                var transient   = replication.IsTransient;
                var logDocID    = new SecureLogString(docID, LogMessageSensitivity.PotentiallyInsecure);
                if (!pushing && error.domain == C4ErrorDomain.LiteCoreDomain &&
                    error.code == (int)C4ErrorCode.Conflict)
                {
                    // Conflict pulling a document -- the revision was added but app needs to resolve it:
                    var safeDocID = new SecureLogString(docID, LogMessageSensitivity.PotentiallyInsecure);
                    WriteLog.To.Sync.I(Tag, $"{this} pulled conflicting version of '{safeDocID}'");
                    try {
                        Config.Database.ResolveConflict(docID);
                        replications[i] = replication.ClearError();
                    } catch (Exception e) {
                        WriteLog.To.Sync.W(Tag, $"Conflict resolution of '{logDocID}' failed", e);
                    }
                }
                else
                {
                    var transientStr = transient ? "transient " : String.Empty;
                    var dirStr       = pushing ? "pushing" : "pulling";
                    WriteLog.To.Sync.I(Tag,
                                       $"{this}: {transientStr}error {dirStr} '{logDocID}' : {error.code} ({Native.c4error_getMessage(error)})");
                }
            }

            _documentEndedUpdate.Fire(this, new DocumentReplicationEventArgs(replications, pushing));
        }
Beispiel #9
0
        /// <inheritdoc />
        public override string ToString()
        {
            var id = new SecureLogString(Id, LogMessageSensitivity.PotentiallyInsecure);

            return($"{GetType().Name}[{id}]");
        }
        internal void ResolveConflict([NotNull] string docID, [NotNull] IConflictResolver resolver)
        {
            Debug.Assert(docID != null);

            Document doc = null, otherDoc = null, baseDoc = null;
            var      inConflict = true;

            while (inConflict)
            {
                ThreadSafety.DoLocked(() =>
                {
                    LiteCoreBridge.Check(err => Native.c4db_beginTransaction(_c4db, err));
                    try {
                        doc = new Document(this, docID);
                        if (!doc.Exists)
                        {
                            doc.Dispose();
                            return;
                        }

                        otherDoc = new Document(this, docID);
                        if (!otherDoc.Exists)
                        {
                            doc.Dispose();
                            otherDoc.Dispose();
                            return;
                        }

                        otherDoc.SelectConflictingRevision();
                        baseDoc = new Document(this, docID);
                        if (!baseDoc.SelectCommonAncestor(doc, otherDoc) || baseDoc.ToDictionary() == null)
                        {
                            baseDoc.Dispose();
                            baseDoc = null;
                        }

                        LiteCoreBridge.Check(err => Native.c4db_endTransaction(_c4db, true, err));
                    } catch (Exception) {
                        doc?.Dispose();
                        otherDoc?.Dispose();
                        baseDoc?.Dispose();
                        LiteCoreBridge.Check(err => Native.c4db_endTransaction(_c4db, false, err));
                    }
                });

                var conflict = new Conflict(doc, otherDoc, baseDoc);
                var logID    = new SecureLogString(doc.Id, LogMessageSensitivity.PotentiallyInsecure);
                Log.To.Database.I(Tag, $"Resolving doc '{logID}' with {resolver.GetType().Name} (mine={doc.RevID}, theirs={otherDoc.RevID}, base={baseDoc?.RevID})");
                Document resolved = null;
                try {
                    resolved = resolver.Resolve(conflict);
                    if (resolved == null)
                    {
                        throw new LiteCoreException(new C4Error(C4ErrorCode.Conflict));
                    }

                    SaveResolvedDocument(resolved, conflict);
                    inConflict = false;
                } catch (LiteCoreException e) {
                    if (e.Error.domain == C4ErrorDomain.LiteCoreDomain && e.Error.code == (int)C4ErrorCode.Conflict)
                    {
                        continue;
                    }

                    throw;
                } finally {
                    resolved?.Dispose();
                    if (resolved != doc)
                    {
                        doc?.Dispose();
                    }

                    if (resolved != otherDoc)
                    {
                        otherDoc?.Dispose();
                    }

                    if (resolved != baseDoc)
                    {
                        baseDoc?.Dispose();
                    }
                }
            }
        }
Beispiel #11
0
        // This is used by the listener
        internal Replication ReplicationWithProperties(IDictionary <string, object> properties)
        {
            // Extract the parameters from the JSON request body:
            // http://wiki.apache.org/couchdb/Replication

            bool push, createTarget;
            var  results = new Dictionary <string, object>()
            {
                { "database", null },
                { "remote", null },
                { "headers", null },
                { "authorizer", null }
            };

            Status result = ParseReplicationProperties(properties, out push, out createTarget, results);

            if (result.IsError)
            {
                throw Misc.CreateExceptionAndLog(Log.To.Listener, result.Code, TAG, "Failed to create replication");
            }

            object continuousObj = properties.Get("continuous");
            bool   continuous    = false;

            if (continuousObj is bool)
            {
                continuous = (bool)continuousObj;
            }

            var         scheduler = new SingleTaskThreadpoolScheduler();
            Replication rep       = null;

            if (push)
            {
                rep = new Pusher((Database)results["database"], (Uri)results["remote"], continuous, new TaskFactory(scheduler));
            }
            else
            {
                rep = new Puller((Database)results["database"], (Uri)results["remote"], continuous, new TaskFactory(scheduler));
            }

            rep.Filter             = properties.Get("filter") as string;
            rep.FilterParams       = properties.Get("query_params").AsDictionary <string, object>();
            rep.DocIds             = properties.Get("doc_ids").AsList <string>();
            rep.Headers            = new Dictionary <string, string>();
            rep.ReplicationOptions = new ReplicationOptions(properties);
            foreach (var header in results.Get("headers").AsDictionary <string, string>())
            {
                if (header.Key.ToLowerInvariant() == "cookie")
                {
                    var cookie = default(Cookie);
                    if (CookieParser.TryParse(header.Value, ((Uri)results["remote"]).Host, out cookie))
                    {
                        try {
                            rep.SetCookie(cookie.Name, cookie.Value, cookie.Path, cookie.Expires, cookie.Secure,
                                          cookie.HttpOnly);
                        } catch (CookieException e) {
                            var headerValue = new SecureLogString(header.Value, LogMessageSensitivity.Insecure);
                            Log.To.Listener.W(TAG, $"Invalid cookie string received ({headerValue}), throwing...", e);
                            throw new CouchbaseLiteException(StatusCode.BadRequest);
                        }
                    }
                    else
                    {
                        var headerValue = new SecureLogString(header.Value, LogMessageSensitivity.Insecure);
                        Log.To.Listener.W(TAG, $"Invalid cookie string received ({headerValue}), throwing...");
                        throw new CouchbaseLiteException(StatusCode.BadRequest);
                    }
                }
                else
                {
                    rep.Headers.Add(header.Key, header.Value);
                }
            }
            rep.Headers       = results.Get("headers").AsDictionary <string, string>();
            rep.Authenticator = results.Get("authorizer") as IAuthenticator;
            if (push)
            {
                ((Pusher)rep).CreateTarget = createTarget;
            }

            var db = (Database)results["database"];

            // If this is a duplicate, reuse an existing replicator:
            var activeReplicators = default(IList <Replication>);
            var existing          = default(Replication);

            if (db.ActiveReplicators.AcquireTemp(out activeReplicators))
            {
                existing = activeReplicators.FirstOrDefault(x => x.LocalDatabase == rep.LocalDatabase &&
                                                            x.RemoteUrl == rep.RemoteUrl && x.IsPull == rep.IsPull &&
                                                            x.RemoteCheckpointDocID().Equals(rep.RemoteCheckpointDocID()));
            }


            return(existing ?? rep);
        }