/// <summary>
        /// Returns the file attachment associated with the document. The raw data of the associated attachment is returned
        /// (just as if you were accessing a static file. The returned Content-Type will be the same as the content type
        /// set when the document attachment was submitted into the database.
        /// </summary>
        /// <returns>The response state for further HTTP processing</returns>
        /// <param name="context">The context of the Couchbase Lite HTTP request</param>
        /// <remarks>
        /// http://docs.couchdb.org/en/latest/api/document/attachments.html#get--db-docid-attname
        /// </remarks>
        public static ICouchbaseResponseState GetAttachment(ICouchbaseListenerContext context)
        {
            return(DatabaseMethods.PerformLogicWithDatabase(context, true, db =>
            {
                Status status = new Status();
                var revID = context.GetQueryParam("rev");
                var rev = db.GetDocument(context.DocumentName, revID == null ? null : revID.AsRevID(), false, status);

                if (rev == null)
                {
                    return context.CreateResponse(status.Code);
                }
                if (context.CacheWithEtag(rev.RevID.ToString()))
                {
                    return context.CreateResponse(StatusCode.NotModified);
                }

                string acceptEncoding = context.RequestHeaders["Accept-Encoding"];
                bool acceptEncoded = acceptEncoding != null && acceptEncoding.Contains("gzip") &&
                                     context.RequestHeaders["Range"] == null;

                var attachment = db.GetAttachmentForRevision(rev, context.AttachmentName);
                if (attachment == null)
                {
                    return context.CreateResponse(StatusCode.AttachmentNotFound);
                }

                var response = context.CreateResponse();
                if (context.Method.Equals(HttpMethod.Head))
                {
                    var length = attachment.Length;
                    if (acceptEncoded && attachment.Encoding == AttachmentEncoding.GZIP &&
                        attachment.EncodedLength > 0)
                    {
                        length = attachment.EncodedLength;
                    }

                    response["Content-Length"] = length.ToString();
                }
                else
                {
                    var contents = acceptEncoded ? attachment.EncodedContent : attachment.Content;
                    if (contents == null)
                    {
                        response.InternalStatus = StatusCode.NotFound;
                        return response;
                    }

                    response.BinaryBody = contents;
                }

                response["Content-Type"] = attachment.ContentType;
                if (acceptEncoded && attachment.Encoding == AttachmentEncoding.GZIP)
                {
                    response["Content-Encoding"] = "gzip";
                }

                return response;
            }).AsDefaultState());
        }
        /// <summary>
        /// Creates a new named document, or creates a new revision of the existing document.
        /// </summary>
        /// <returns>The response state for further HTTP processing</returns>
        /// <param name="context">The context of the Couchbase Lite HTTP request</param>
        /// <remarks>
        /// http://docs.couchdb.org/en/latest/api/document/common.html#put--db-docid
        /// </remarks>
        public static ICouchbaseResponseState UpdateDocument(ICouchbaseListenerContext context)
        {
            return(PerformLogicWithDocumentBody(context, (db, body) =>
            {
                var response = context.CreateResponse();
                string docId = context.DocumentName;

                db.ForgetDesignDocument(context.DesignDocName);
                if (context.GetQueryParam <bool>("new_edits", bool.TryParse, true))
                {
                    // Regular PUT:
                    return UpdateDb(context, db, docId, body, false);
                }
                else
                {
                    // PUT with new_edits=false -- forcible insertion of existing revision:
                    RevisionInternal rev = new RevisionInternal(body);
                    if (rev == null)
                    {
                        response.InternalStatus = StatusCode.BadJson;
                        return response;
                    }

                    if (!docId.Equals(rev.DocID) || rev.RevID == null)
                    {
                        response.InternalStatus = StatusCode.BadId;
                        return response;
                    }

                    var history = Database.ParseCouchDBRevisionHistory(body.GetProperties());
                    Status status = new Status(StatusCode.Ok);
                    var castContext = context as ICouchbaseListenerContext2;
                    var source = (castContext != null && !castContext.IsLoopbackRequest) ? castContext.Sender : null;

                    try {
                        Log.To.Router.I(TAG, "Force inserting {0}", rev);
                        Log.To.Router.V(TAG, "With history {0}", new LogJsonString(history));
                        db.ForceInsert(rev, history, source);
                    } catch (CouchbaseLiteException e) {
                        status = e.CBLStatus;
                    }

                    if (!status.IsError)
                    {
                        response.JsonBody = new Body(new Dictionary <string, object> {
                            { "ok", true },
                            { "id", rev.DocID },
                            { "rev", rev.RevID }
                        });
                    }

                    response.InternalStatus = status.Code;
                    return response;
                }
            }).AsDefaultState());
        }
Пример #3
0
        /// <summary>
        /// Creates a new named document, or creates a new revision of the existing document.
        /// </summary>
        /// <returns>The response state for further HTTP processing</returns>
        /// <param name="context">The context of the Couchbase Lite HTTP request</param>
        /// <remarks>
        /// http://docs.couchdb.org/en/latest/api/document/common.html#put--db-docid
        /// <remarks>
        public static ICouchbaseResponseState UpdateDocument(ICouchbaseListenerContext context)
        {
            return(PerformLogicWithDocumentBody(context, (db, body) =>
            {
                var response = context.CreateResponse();
                string docId = context.DocumentName;
                if (context.GetQueryParam <bool>("new_edits", bool.TryParse, true))
                {
                    // Regular PUT:
                    return UpdateDb(context, db, docId, body, false);
                }
                else
                {
                    // PUT with new_edits=false -- forcible insertion of existing revision:
                    RevisionInternal rev = new RevisionInternal(body);
                    if (rev == null)
                    {
                        response.InternalStatus = StatusCode.BadJson;
                        return response;
                    }

                    if (!docId.Equals(rev.GetDocId()) || rev.GetRevId() == null)
                    {
                        response.InternalStatus = StatusCode.BadId;
                        return response;
                    }

                    var history = Database.ParseCouchDBRevisionHistory(body.GetProperties());
                    Status status = new Status();
                    try {
                        db.ForceInsert(rev, history, null, status);
                    } catch (CouchbaseLiteException e) {
                        status = e.GetCBLStatus();
                    }

                    if (!status.IsError)
                    {
                        response.JsonBody = new Body(new Dictionary <string, object> {
                            { "ok", true },
                            { "id", rev.GetDocId() },
                            { "rev", rev.GetRevId() }
                        });
                    }

                    response.InternalStatus = status.GetCode();
                    return response;
                }
            }).AsDefaultState());
        }
Пример #4
0
        /// <summary>
        /// Deletes the specified database, and all the documents and attachments contained within it.
        /// </summary>
        /// <returns>The response state for further HTTP processing</returns>
        /// <param name="context">The context of the Couchbase Lite HTTP request</param>
        /// <remarks>
        /// http://docs.couchdb.org/en/latest/api/database/common.html#delete--db
        /// <remarks>
        public static ICouchbaseResponseState DeleteConfiguration(ICouchbaseListenerContext context)
        {
            return(PerformLogicWithDatabase(context, false, db =>
            {
                if (context.GetQueryParam("rev") != null)
                {
                    // CouchDB checks for this; probably meant to be a document deletion
                    return context.CreateResponse(StatusCode.BadId);
                }

                try {
                    db.Delete();
                } catch (CouchbaseLiteException) {
                    return context.CreateResponse(StatusCode.InternalServerError);
                }

                return context.CreateResponse();
            }).AsDefaultState());
        }
Пример #5
0
        /// <summary>
        /// Requests one or more Universally Unique Identifiers (UUIDs) from the Couchbase Lite instance.
        /// </summary>
        /// <returns>The response state for further HTTP processing</returns>
        /// <param name="context">The context of the Couchbase Lite HTTP request</param>
        /// <remarks>
        /// http://docs.couchdb.org/en/latest/api/server/common.html#get--_uuids
        /// <remarks>
        public static ICouchbaseResponseState GetUUIDs(ICouchbaseListenerContext context)
        {
            int count = context.GetQueryParam <int>("count", int.TryParse, 1);

            if (count > 1000)
            {
                return(context.CreateResponse(StatusCode.Forbidden).AsDefaultState());
            }

            var uuidList = new List <object>();

            for (int i = 0; i < count; i++)
            {
                uuidList.Add(Guid.NewGuid());
            }

            var couchResponse = context.CreateResponse();

            couchResponse.JsonBody = new Body(uuidList);
            return(couchResponse.AsDefaultState());
        }
Пример #6
0
        // Update the given attachment using the provided info
        private static CouchbaseLiteResponse UpdateAttachment(ICouchbaseListenerContext context, Database db,
                                                              string attachment, string docId, BlobStoreWriter body)
        {
            RevisionInternal rev = db.UpdateAttachment(attachment, body, context.RequestHeaders["Content-Type"], AttachmentEncoding.None,
                                                       docId, context.GetQueryParam("rev") ?? context.IfMatch());

            var response = context.CreateResponse();

            response.JsonBody = new Body(new Dictionary <string, object> {
                { "ok", true },
                { "id", rev.GetDocId() },
                { "rev", rev.GetRevId() }
            });
            context.CacheWithEtag(rev.GetRevId());
            if (body != null)
            {
                response["Location"] = context.RequestUrl.AbsoluteUri;
            }

            return(response);
        }
        // Update the given attachment using the provided info
        private static CouchbaseLiteResponse UpdateAttachment(ICouchbaseListenerContext context, Database db,
                                                              string attachment, string docId, BlobStoreWriter body)
        {
            var castContext      = context as ICouchbaseListenerContext2;
            var source           = castContext != null && !castContext.IsLoopbackRequest ? castContext.Sender : null;
            RevisionInternal rev = db.UpdateAttachment(attachment, body, context.RequestHeaders["Content-Type"], AttachmentEncoding.None,
                                                       docId, (context.GetQueryParam("rev") ?? context.IfMatch()).AsRevID(), source);

            var response = context.CreateResponse();

            response.JsonBody = new Body(new Dictionary <string, object> {
                { "ok", true },
                { "id", rev.DocID },
                { "rev", rev.RevID }
            });
            context.CacheWithEtag(rev.RevID.ToString());
            if (body != null)
            {
                response["Location"] = context.RequestUrl.AbsoluteUri;
            }

            return(response);
        }
        /// <summary>
        /// Attempt to update a document based on the information in the HTTP request
        /// </summary>
        /// <returns>The resulting status of the operation</returns>
        /// <param name="context">The request context</param>
        /// <param name="db">The database in which the document exists</param>
        /// <param name="docId">The ID of the document being updated</param>
        /// <param name="body">The new document body</param>
        /// <param name="deleting">Whether or not the document is being deleted</param>
        /// <param name="allowConflict">Whether or not to allow a conflict to be inserted</param>
        /// <param name="outRev">The resulting revision of the document</param>
        public static StatusCode UpdateDocument(ICouchbaseListenerContext context, Database db, string docId, Body body, bool deleting,
                                                bool allowConflict, out RevisionInternal outRev)
        {
            outRev = null;
            if (body != null && !body.IsValidJSON())
            {
                return(StatusCode.BadJson);
            }

            string prevRevId;

            if (!deleting)
            {
                var properties = body.GetProperties();
                deleting = properties.GetCast <bool>("_deleted");
                if (docId == null)
                {
                    // POST's doc ID may come from the _id field of the JSON body.
                    docId = properties.CblID();
                    if (docId == null && deleting)
                    {
                        return(StatusCode.BadId);
                    }
                }

                // PUT's revision ID comes from the JSON body.
                prevRevId = properties.GetCast <string>("_rev");
            }
            else
            {
                // DELETE's revision ID comes from the ?rev= query param
                prevRevId = context.GetQueryParam("rev");
            }

            // A backup source of revision ID is an If-Match header:
            if (prevRevId == null)
            {
                prevRevId = context.IfMatch();
            }

            if (docId == null && deleting)
            {
                return(StatusCode.BadId);
            }

            RevisionInternal rev = new RevisionInternal(docId, null, deleting);

            rev.SetBody(body);

            // Check for doc expiration
            var expirationTime = default(DateTime?);
            var tmp            = default(object);
            var props          = rev.GetProperties();
            var hasValue       = false;

            if (props != null && props.TryGetValue("_exp", out tmp))
            {
                hasValue = true;
                if (tmp != null)
                {
                    if (tmp is DateTime || tmp is DateTimeOffset)
                    {
                        expirationTime = (DateTime)tmp;
                    }
                    else
                    {
                        try {
                            expirationTime = Convert.ToDateTime(tmp);
                        } catch (Exception) {
                            try {
                                var num = Convert.ToInt64(tmp);
                                expirationTime = Misc.OffsetFromEpoch(TimeSpan.FromSeconds(num));
                            } catch (Exception) {
                                Log.To.Router.E(TAG, "Invalid value for _exp: {0}", tmp);
                                return(StatusCode.BadRequest);
                            }
                        }
                    }
                }

                props.Remove("_exp");
                rev.SetProperties(props);
            }

            var        castContext = context as ICouchbaseListenerContext2;
            var        source      = castContext != null && !castContext.IsLoopbackRequest ? castContext.Sender : null;
            StatusCode status      = deleting ? StatusCode.Ok : StatusCode.Created;

            try {
                if (docId != null && docId.StartsWith("_local"))
                {
                    if (expirationTime.HasValue)
                    {
                        return(StatusCode.BadRequest);
                    }

                    Log.To.Router.I(TAG, "Attempting to insert local {0} on top of {1} from PUT request", rev, prevRevId != null ? prevRevId.ToString() : "<root>");
                    outRev = db.Storage.PutLocalRevision(rev, prevRevId.AsRevID(), true); //TODO: Doesn't match iOS
                }
                else
                {
                    Log.To.Router.I(TAG, "Attempting to insert {0} on top of {1} from PUT request", rev, prevRevId != null ? prevRevId.ToString() : "<root>");
                    outRev = db.PutRevision(rev, prevRevId.AsRevID(), allowConflict, source);
                    if (hasValue)
                    {
                        db.Storage?.SetDocumentExpiration(rev.DocID, expirationTime);
                    }
                }
            } catch (CouchbaseLiteException e) {
                status = e.Code;
            }

            return(status);
        }
        /// <summary>
        /// Creates a new named document, or creates a new revision of the existing document.
        /// </summary>
        /// <returns>The response state for further HTTP processing</returns>
        /// <param name="context">The context of the Couchbase Lite HTTP request</param>
        /// <remarks>
        /// http://docs.couchdb.org/en/latest/api/document/common.html#put--db-docid
        /// <remarks>
        public static ICouchbaseResponseState UpdateDocument(ICouchbaseListenerContext context)
        {
            return PerformLogicWithDocumentBody(context, (db, body) =>
            {
                var response = context.CreateResponse();
                string docId = context.DocumentName;
                if(context.GetQueryParam<bool>("new_edits", bool.TryParse, true)) {
                    // Regular PUT:
                    return UpdateDb(context, db, docId, body, false);
                } else {
                    // PUT with new_edits=false -- forcible insertion of existing revision:
                    RevisionInternal rev = new RevisionInternal(body);
                    if(rev == null) {
                        response.InternalStatus = StatusCode.BadJson;
                        return response;
                    }

                    if(!docId.Equals(rev.GetDocId()) || rev.GetRevId() == null) {
                        response.InternalStatus = StatusCode.BadId;
                        return response;
                    }

                    var history = Database.ParseCouchDBRevisionHistory(body.GetProperties());
                    Status status = new Status();
                    try {
                      db.ForceInsert(rev, history, null);
                    } catch(CouchbaseLiteException e) {
                        status = e.CBLStatus;
                    }

                    if(!status.IsError) {
                        response.JsonBody = new Body(new Dictionary<string, object> {
                            { "ok", true },
                            { "id", rev.GetDocId() },
                            { "rev", rev.GetRevId() }
                        });
                    }

                    response.InternalStatus = status.Code;
                    return response;
                }
            }).AsDefaultState();
        }
Пример #10
0
        /// <summary>
        /// Attempt to update a document based on the information in the HTTP request
        /// </summary>
        /// <returns>The resulting status of the operation</returns>
        /// <param name="context">The request context</param>
        /// <param name="db">The database in which the document exists</param>
        /// <param name="docId">The ID of the document being updated</param>
        /// <param name="body">The new document body</param>
        /// <param name="deleting">Whether or not the document is being deleted</param>
        /// <param name="allowConflict">Whether or not to allow a conflict to be inserted</param>
        /// <param name="outRev">The resulting revision of the document</param>
        public static StatusCode UpdateDocument(ICouchbaseListenerContext context, Database db, string docId, Body body, bool deleting,
                                                bool allowConflict, out RevisionInternal outRev)
        {
            outRev = null;
            if (body != null && !body.IsValidJSON())
            {
                return(StatusCode.BadJson);
            }

            string prevRevId;

            if (!deleting)
            {
                var properties = body.GetProperties();
                deleting = properties.GetCast <bool>("_deleted");
                if (docId == null)
                {
                    // POST's doc ID may come from the _id field of the JSON body.
                    docId = properties.GetCast <string>("_id");
                    if (docId == null && deleting)
                    {
                        return(StatusCode.BadId);
                    }
                }

                // PUT's revision ID comes from the JSON body.
                prevRevId = properties.GetCast <string>("_rev");
            }
            else
            {
                // DELETE's revision ID comes from the ?rev= query param
                prevRevId = context.GetQueryParam("rev");
            }

            // A backup source of revision ID is an If-Match header:
            if (prevRevId == null)
            {
                prevRevId = context.IfMatch();
            }

            if (docId == null && deleting)
            {
                return(StatusCode.BadId);
            }

            RevisionInternal rev = new RevisionInternal(docId, null, deleting);

            rev.SetBody(body);

            StatusCode status = StatusCode.Created;

            try {
                if (docId.StartsWith("_local"))
                {
                    outRev = db.PutLocalRevision(rev, prevRevId); //TODO: Doesn't match iOS
                }
                else
                {
                    Status retStatus = new Status();
                    outRev = db.PutRevision(rev, prevRevId, allowConflict, retStatus);
                    status = retStatus.GetCode();
                }
            } catch (CouchbaseLiteException e) {
                status = e.Code;
            }

            return(status);
        }
        // Update the given attachment using the provided info
        private static CouchbaseLiteResponse UpdateAttachment(ICouchbaseListenerContext context, Database db, 
            string attachment, string docId, BlobStoreWriter body)
        {
            var castContext = context as ICouchbaseListenerContext2;
            var source = castContext != null && !castContext.IsLoopbackRequest ? castContext.Sender : null;
            RevisionInternal rev = db.UpdateAttachment(attachment, body, context.RequestHeaders["Content-Type"], AttachmentEncoding.None,
                    docId, (context.GetQueryParam("rev") ?? context.IfMatch()).AsRevID(), source);

            var response = context.CreateResponse();
            response.JsonBody = new Body(new Dictionary<string, object> {
                { "ok", true },
                { "id", rev.DocID },
                { "rev", rev.RevID }
            });
            context.CacheWithEtag(rev.RevID.ToString());
            if (body != null) {
                response["Location"] = context.RequestUrl.AbsoluteUri;
            }

            return response;
        }
Пример #12
0
        /// <summary>
        /// Returns document by the specified docid from the specified db. Unless you request a 
        /// specific revision, the latest revision of the document will always be returned.
        /// </summary>
        /// <returns>The response state for further HTTP processing</returns>
        /// <param name="context">The context of the Couchbase Lite HTTP request</param>
        /// <remarks>
        /// http://docs.couchdb.org/en/latest/api/document/common.html#get--db-docid
        /// <remarks>
        public static ICouchbaseResponseState GetDocument(ICouchbaseListenerContext context)
        {
            return DatabaseMethods.PerformLogicWithDatabase(context, true, db => {
                var response = context.CreateResponse();
                string docId = context.DocumentName;
                bool isLocalDoc = docId.StartsWith("_local");

                DocumentContentOptions options = context.ContentOptions;
                string openRevsParam = context.GetQueryParam("open_revs");
                bool mustSendJson = context.ExplicitlyAcceptsType("application/json");
                if (openRevsParam == null || isLocalDoc) {
                    //Regular GET:
                    string revId = context.GetQueryParam("rev"); //often null
                    RevisionInternal rev;
                    bool includeAttachments = false, sendMultipart = false;
                    if (isLocalDoc) {
                        rev = db.Storage.GetLocalDocument(docId, revId);
                    } else {
                        includeAttachments = options.HasFlag(DocumentContentOptions.IncludeAttachments);
                        if(includeAttachments) {
                            sendMultipart = !mustSendJson;
                            options &= ~DocumentContentOptions.IncludeAttachments;
                        }

                        Status status = new Status();
                        rev = db.GetDocument(docId, revId, true, status);
                        if(rev != null) {
                            rev = ApplyOptions(options, rev, context, db, status);
                        }

                        if(rev == null) {
                            if(status.Code == StatusCode.Deleted) {
                                response.StatusReason = "deleted";
                            } else {
                                response.StatusReason = "missing";
                            }

                            response.InternalStatus = status.Code;
                            return response;
                        }
                    }

                    if(rev == null) {
                        response.InternalStatus = StatusCode.NotFound;
                        return response;
                    }

                    if(context.CacheWithEtag(rev.GetRevId())) {
                        response.InternalStatus = StatusCode.NotModified;
                        return response;
                    }

                    if(!isLocalDoc && includeAttachments) {
                        int minRevPos = 1;
                        IList<string> attsSince = context.GetJsonQueryParam("atts_since").AsList<string>();
                        string ancestorId = db.Storage.FindCommonAncestor(rev, attsSince);
                        if(ancestorId != null) {
                            minRevPos = RevisionInternal.GenerationFromRevID(ancestorId) + 1;
                        }

                        Status status = new Status();
                        bool attEncodingInfo = context.GetQueryParam<bool>("att_encoding_info", bool.TryParse, false);
                        if(!db.ExpandAttachments(rev, minRevPos, sendMultipart, attEncodingInfo, status)) {
                            response.InternalStatus = status.Code;
                            return response;
                        }
                    }

                    if(sendMultipart) {
                        response.MultipartWriter = db.MultipartWriterForRev(rev, "multipart/related");
                    } else {
                        response.JsonBody = rev.GetBody();
                    }
                } else {
                    // open_revs query:
                    IList<IDictionary<string, object>> result;
                    if(openRevsParam.Equals("all")) {
                        // ?open_revs=all returns all current/leaf revisions:
                        bool includeDeleted = context.GetQueryParam<bool>("include_deleted", bool.TryParse, false);
                        RevisionList allRevs = db.Storage.GetAllDocumentRevisions(docId, true);

                        result = new List<IDictionary<string, object>>();
                        foreach(var rev in allRevs) {
                            if(!includeDeleted && rev.IsDeleted()) {
                                continue;
                            }

                            Status status = new Status();
                            RevisionInternal loadedRev = db.RevisionByLoadingBody(rev, status);
                            if(loadedRev != null) {
                                ApplyOptions(options, loadedRev, context, db, status);
                            }

                            if(loadedRev != null) {
                                result.Add(new Dictionary<string, object> { { "ok", loadedRev.GetProperties() } });
                            } else if(status.Code <= StatusCode.InternalServerError) {
                                result.Add(new Dictionary<string, object> { { "missing", rev.GetRevId() } });
                            } else {
                                response.InternalStatus = status.Code;
                                return response;
                            }
                        }
                    } else {
                        // ?open_revs=[...] returns an array of specific revisions of the document:
                        var openRevs = context.GetJsonQueryParam("open_revs").AsList<object>();
                        if(openRevs == null) {
                            response.InternalStatus = StatusCode.BadParam;
                            return response;
                        }

                        result = new List<IDictionary<string, object>>();
                        foreach(var revIDObj in openRevs) {
                            var revID = revIDObj as string;
                            if(revID == null) {
                                response.InternalStatus = StatusCode.BadId;
                                return response;
                            }

                            Status status = new Status();
                            var rev = db.GetDocument(docId, revID, true, status);
                            if(rev != null) {
                                rev = ApplyOptions(options, rev, context, db, status);
                            }

                            if(rev != null) {
                                result.Add(new Dictionary<string, object>{ { "ok", rev.GetProperties() } });
                            } else {
                                result.Add(new Dictionary<string, object>{ { "missing", revID } });
                            }
                        }
                    }

                    if(mustSendJson) {
                        response["Content-Type"] = "application/json";
                        response.JsonBody = new Body(result.Cast<object>().ToList());
                    } else {
                        response.SetMultipartBody(result.Cast<object>().ToList(), "multipart/mixed");
                    }
                }

                return response;
            }).AsDefaultState();
        }
Пример #13
0
        // Update the given attachment using the provided info
        private static CouchbaseLiteResponse UpdateAttachment(ICouchbaseListenerContext context, Database db, 
            string attachment, string docId, BlobStoreWriter body)
        {
            RevisionInternal rev = db.UpdateAttachment(attachment, body, context.RequestHeaders["Content-Type"], AttachmentEncoding.None,
                    docId, context.GetQueryParam("rev") ?? context.IfMatch());

            var response = context.CreateResponse();
            response.JsonBody = new Body(new Dictionary<string, object> {
                { "ok", true },
                { "id", rev.GetDocId() },
                { "rev", rev.GetRevId() }
            });
            context.CacheWithEtag(rev.GetRevId());
            if (body != null) {
                response["Location"] = context.RequestUrl.AbsoluteUri;
            }

            return response;
        }
        // Apply the options in the URL query to the specified revision and create a new revision object
        internal static RevisionInternal ApplyOptions(DocumentContentOptions options, RevisionInternal rev, ICouchbaseListenerContext context,
            Database db, Status outStatus)
        {
            if ((options & (DocumentContentOptions.IncludeRevs | DocumentContentOptions.IncludeRevsInfo | DocumentContentOptions.IncludeConflicts |
                DocumentContentOptions.IncludeAttachments | DocumentContentOptions.IncludeLocalSeq)
                | DocumentContentOptions.IncludeExpiration) != 0) {
                var dst = rev.GetProperties() ?? new Dictionary<string, object>(); 
                if (options.HasFlag(DocumentContentOptions.IncludeLocalSeq)) {
                    dst["_local_seq"] = rev.Sequence;
                }

                if (options.HasFlag(DocumentContentOptions.IncludeRevs)) {
                    var revs = db.GetRevisionHistory(rev, null);
                    dst["_revisions"] = TreeRevisionID.MakeRevisionHistoryDict(revs);
                }

                if (options.HasFlag(DocumentContentOptions.IncludeRevsInfo)) {
                    dst["_revs_info"] = db.GetRevisionHistory(rev, null).Select(x =>
                    {
                        string status = "available";
                        var ancestor = db.GetDocument(rev.DocID, x, true);
                        if(ancestor.Deleted) {
                            status = "deleted";
                        } else if(ancestor.Missing) {
                            status = "missing";
                        }

                        return new Dictionary<string, object> {
                            { "rev", x.ToString() },
                            { "status", status }
                        };
                    });
                }

                if (options.HasFlag(DocumentContentOptions.IncludeConflicts)) {
                    RevisionList revs = db.Storage.GetAllDocumentRevisions(rev.DocID, true, false);
                    if (revs.Count > 1) {
                        dst["_conflicts"] = from r in revs
                                            where !r.Equals(rev) && !r.Deleted
                                            select r.RevID.ToString();
                    }
                }

                if(options.HasFlag(DocumentContentOptions.IncludeExpiration)) {
                    var expirationTime = db.Storage?.GetDocumentExpiration(rev.DocID);
                    if(expirationTime.HasValue) {
                        dst["_exp"] = expirationTime;
                    }
                }

                RevisionInternal nuRev = new RevisionInternal(dst);
                if (options.HasFlag(DocumentContentOptions.IncludeAttachments)) {
                    bool attEncodingInfo = context != null && context.GetQueryParam<bool>("att_encoding_info", bool.TryParse, false);
                    db.ExpandAttachments(nuRev, 0, false, !attEncodingInfo);
                }

                rev = nuRev;
            }

            return rev;
        }
Пример #15
0
        // Perform a document operation on the specified database
        private static CouchbaseLiteResponse UpdateDb(ICouchbaseListenerContext context, Database db, string docId, Body body, bool deleting)
        {
            var response = context.CreateResponse();
            if (docId != null) {
                // On PUT/DELETE, get revision ID from either ?rev= query, If-Match: header, or doc body:
                string revParam = context.GetQueryParam("rev");
                string ifMatch = context.RequestHeaders["If-Match"];
                if (ifMatch != null) {
                    if (revParam == null) {
                        revParam = ifMatch;
                    } else if (!revParam.Equals(ifMatch)) {
                        return context.CreateResponse(StatusCode.BadRequest);
                    }
                }

                if (revParam != null && body != null) {
                    var revProp = body.GetPropertyForKey("_rev");
                    if (revProp == null) {
                        // No _rev property in body, so use ?rev= query param instead:
                        var props = body.GetProperties();
                        props["_rev"] = revParam;
                        body = new Body(props);
                    } else if (!revProp.Equals(revParam)) {
                        return context.CreateResponse(StatusCode.BadRequest); // mismatch between _rev and rev
                    }
                }
            }

            RevisionInternal rev;
            StatusCode status = UpdateDocument(context, db, docId, body, deleting, false, out rev);
            if ((int)status < 300) {
                context.CacheWithEtag(rev.GetRevId()); // set ETag
                if (!deleting) {
                    var url = context.RequestUrl;
                    if (docId != null) {
                        response["Location"] = url.AbsoluteUri;
                    }
                }

                response.JsonBody = new Body(new Dictionary<string, object> {
                    { "ok", true },
                    { "id", rev.GetDocId() },
                    { "rev", rev.GetRevId() }
                });
            }

            response.InternalStatus = status;
            return response;
        }
Пример #16
0
        // Perform a document operation on the specified database
        private static CouchbaseLiteResponse UpdateDb(ICouchbaseListenerContext context, Database db, string docId, Body body, bool deleting)
        {
            var response = context.CreateResponse();

            if (docId != null)
            {
                // On PUT/DELETE, get revision ID from either ?rev= query, If-Match: header, or doc body:
                string revParam = context.GetQueryParam("rev");
                string ifMatch  = context.RequestHeaders["If-Match"];
                if (ifMatch != null)
                {
                    if (revParam == null)
                    {
                        revParam = ifMatch;
                    }
                    else if (!revParam.Equals(ifMatch))
                    {
                        return(context.CreateResponse(StatusCode.BadRequest));
                    }
                }

                if (revParam != null && body != null)
                {
                    var revProp = body.GetPropertyForKey("_rev");
                    if (revProp == null)
                    {
                        // No _rev property in body, so use ?rev= query param instead:
                        var props = body.GetProperties();
                        props.SetRevID(revParam);
                        body = new Body(props);
                    }
                    else if (!revProp.Equals(revParam))
                    {
                        return(context.CreateResponse(StatusCode.BadRequest)); // mismatch between _rev and rev
                    }
                }
            }

            RevisionInternal rev;
            StatusCode       status = UpdateDocument(context, db, docId, body, deleting, false, out rev);

            if ((int)status < 300)
            {
                context.CacheWithEtag(rev.RevID.ToString()); // set ETag
                if (!deleting)
                {
                    var url = context.RequestUrl;
                    if (docId != null)
                    {
                        response["Location"] = url.AbsoluteUri;
                    }
                }

                response.JsonBody = new Body(new Dictionary <string, object> {
                    { "ok", true },
                    { "id", rev.DocID },
                    { "rev", rev.RevID }
                });
            }

            response.InternalStatus = status;
            return(response);
        }
        /// <summary>
        /// Returns a sorted list of changes made to documents in the database, in time order of application.
        /// </summary>
        /// <returns>The response state for further HTTP processing</returns>
        /// <param name="context">The context of the Couchbase Lite HTTP request</param>
        /// <remarks>
        /// http://docs.couchdb.org/en/latest/api/database/changes.html#get--db-_changes
        /// </remarks>
        public static ICouchbaseResponseState GetChanges(ICouchbaseListenerContext context)
        {
            DBMonitorCouchbaseResponseState responseState = new DBMonitorCouchbaseResponseState();

            var responseObject = PerformLogicWithDatabase(context, true, db =>
            {
                var response = context.CreateResponse();
                responseState.Response = response;
                if (context.ChangesFeedMode < ChangesFeedMode.Continuous) {
                    if(context.CacheWithEtag(db.GetLastSequenceNumber().ToString())) {
                        response.InternalStatus = StatusCode.NotModified;
                        return response;
                    }
                }
                    
                var options = ChangesOptions.Default;
                responseState.Db = db;
                responseState.ContentOptions = context.ContentOptions;
                responseState.ChangesFeedMode = context.ChangesFeedMode;
                responseState.ChangesIncludeDocs = context.GetQueryParam<bool>("include_docs", bool.TryParse, false);
                options.IncludeDocs = responseState.ChangesIncludeDocs;
                responseState.ChangesIncludeConflicts = context.GetQueryParam("style") == "all_docs";
                options.IncludeConflicts = responseState.ChangesIncludeConflicts;
                options.ContentOptions = context.ContentOptions;
                options.SortBySequence = !options.IncludeConflicts;
                options.Limit = context.GetQueryParam<int>("limit", int.TryParse, options.Limit);
                int since = context.GetQueryParam<int>("since", int.TryParse, 0);

                string filterName = context.GetQueryParam("filter");
                if(filterName != null) {
                    Status status = new Status();
                    responseState.ChangesFilter = db.GetFilter(filterName, status);
                    if(responseState.ChangesFilter == null) {
                        return context.CreateResponse(status.Code);
                    }

                    responseState.FilterParams = context.GetQueryParams();
                }


                RevisionList changes = db.ChangesSince(since, options, responseState.ChangesFilter, responseState.FilterParams);
                if((context.ChangesFeedMode >= ChangesFeedMode.Continuous) || 
                    (context.ChangesFeedMode == ChangesFeedMode.LongPoll && changes.Count == 0)) {
                    // Response is going to stay open (continuous, or hanging GET):
                    response.Chunked = true;
                    if(context.ChangesFeedMode == ChangesFeedMode.EventSource) {
                        response["Content-Type"] = "text/event-stream; charset=utf-8";
                    }

                    if(context.ChangesFeedMode >= ChangesFeedMode.Continuous) {
                        response.WriteHeaders();
                        foreach(var rev in changes) {
                            var success = response.SendContinuousLine(ChangesDictForRev(rev, responseState), context.ChangesFeedMode);
                            if(!success) {
                                return context.CreateResponse(StatusCode.BadRequest);
                            }
                        }
                    }

                    responseState.SubscribeToDatabase(db);
                    string heartbeatParam = context.GetQueryParam("heartbeat");
                    if(heartbeatParam != null) {
                        int heartbeat;
                        if(!int.TryParse(heartbeatParam, out heartbeat) || heartbeat <= 0) {
                            responseState.IsAsync = false;
                            return context.CreateResponse(StatusCode.BadParam);
                        }

                        var heartbeatSpan = TimeSpan.FromMilliseconds(heartbeat);
                        if(heartbeatSpan < MinHeartbeat) {
                            heartbeatSpan = MinHeartbeat;
                        }

                        string heartbeatResponse = context.ChangesFeedMode == ChangesFeedMode.EventSource ? "\n\n" : "\r\n";
                        responseState.StartHeartbeat(heartbeatResponse, heartbeatSpan);
                    }

                    return response;
                } else {
                    if(responseState.ChangesIncludeConflicts) {
                        response.JsonBody = new Body(ResponseBodyForChanges(changes, since, options.Limit, responseState));
                    } else {
                        response.JsonBody = new Body(ResponseBodyForChanges(changes, since, responseState));
                    }

                    return response;
                }
            });

            responseState.Response = responseObject;
            return responseState;
        }
Пример #18
0
        /// <summary>
        /// Returns document by the specified docid from the specified db. Unless you request a
        /// specific revision, the latest revision of the document will always be returned.
        /// </summary>
        /// <returns>The response state for further HTTP processing</returns>
        /// <param name="context">The context of the Couchbase Lite HTTP request</param>
        /// <remarks>
        /// http://docs.couchdb.org/en/latest/api/document/common.html#get--db-docid
        /// </remarks>
        public static ICouchbaseResponseState GetDocument(ICouchbaseListenerContext context)
        {
            return(DatabaseMethods.PerformLogicWithDatabase(context, true, db => {
                var response = context.CreateResponse();
                string docId = context.DocumentName;
                bool isLocalDoc = docId.StartsWith("_local", StringComparison.InvariantCulture);

                DocumentContentOptions options = context.ContentOptions;
                string openRevsParam = context.GetQueryParam("open_revs");
                bool mustSendJson = context.ExplicitlyAcceptsType("application/json");
                Status status = new Status();
                if (openRevsParam == null || isLocalDoc)
                {
                    //Regular GET:
                    var revId = context.GetQueryParam("rev").AsRevID(); //often null
                    RevisionInternal rev;
                    bool includeAttachments = false, sendMultipart = false;
                    if (isLocalDoc)
                    {
                        rev = db.Storage.GetLocalDocument(docId, revId);
                    }
                    else
                    {
                        includeAttachments = options.HasFlag(DocumentContentOptions.IncludeAttachments);
                        if (includeAttachments)
                        {
                            sendMultipart = !mustSendJson;
                            options &= ~DocumentContentOptions.IncludeAttachments;
                        }


                        rev = db.GetDocument(docId, revId, true, status);
                        if (rev != null)
                        {
                            rev = ApplyOptions(options, rev, context, db, status);
                        }

                        if (rev == null)
                        {
                            response.StatusReason = status.Code == StatusCode.Deleted ? "deleted" : "missing";
                            response.InternalStatus = StatusCode.NotFound;
                            return response;
                        }
                    }

                    if (rev == null)
                    {
                        response.InternalStatus = StatusCode.NotFound;
                        return response;
                    }

                    if (context.CacheWithEtag(rev.RevID?.ToString()))
                    {
                        response.InternalStatus = StatusCode.NotModified;
                        return response;
                    }

                    if (!isLocalDoc && includeAttachments)
                    {
                        int minRevPos = 1;
                        var attsSince = context.GetJsonQueryParam("atts_since")?.AsList <string>()?.AsRevIDs();
                        var ancestorId = db.Storage.FindCommonAncestor(rev, attsSince);
                        if (ancestorId != null)
                        {
                            minRevPos = ancestorId.Generation + 1;
                        }

                        bool attEncodingInfo = context.GetQueryParam <bool>("att_encoding_info", bool.TryParse, false);
                        db.ExpandAttachments(rev, minRevPos, sendMultipart, attEncodingInfo);
                    }

                    if (sendMultipart)
                    {
                        response.MultipartWriter = MultipartWriterForRev(db, rev, "multipart/related");
                    }
                    else
                    {
                        response.JsonBody = rev.GetBody();
                    }
                }
                else
                {
                    // open_revs query:
                    IList <IDictionary <string, object> > result;
                    if (openRevsParam.Equals("all"))
                    {
                        // ?open_revs=all returns all current/leaf revisions:
                        bool includeDeleted = context.GetQueryParam <bool>("include_deleted", bool.TryParse, false);
                        RevisionList allRevs = db.Storage.GetAllDocumentRevisions(docId, true, includeDeleted);

                        result = new List <IDictionary <string, object> >();
                        foreach (var rev in allRevs)
                        {
                            if (!includeDeleted && rev.Deleted)
                            {
                                continue;
                            }

                            var loadedRev = db.RevisionByLoadingBody(rev, status);
                            if (loadedRev != null)
                            {
                                ApplyOptions(options, loadedRev, context, db, status);
                            }

                            if (loadedRev != null)
                            {
                                result.Add(new Dictionary <string, object> {
                                    { "ok", loadedRev.GetProperties() }
                                });
                            }
                            else if (status.Code <= StatusCode.InternalServerError)
                            {
                                result.Add(new Dictionary <string, object> {
                                    { "missing", rev.RevID }
                                });
                            }
                            else
                            {
                                response.InternalStatus = status.Code;
                                return response;
                            }
                        }
                    }
                    else
                    {
                        // ?open_revs=[...] returns an array of specific revisions of the document:
                        var openRevs = context.GetJsonQueryParam("open_revs").AsList <object>();
                        if (openRevs == null)
                        {
                            response.InternalStatus = StatusCode.BadParam;
                            return response;
                        }

                        result = new List <IDictionary <string, object> >();
                        foreach (var revIDObj in openRevs)
                        {
                            var revID = revIDObj as string;
                            if (revID == null)
                            {
                                response.InternalStatus = StatusCode.BadId;
                                return response;
                            }

                            var rev = db.GetDocument(docId, revID.AsRevID(), true);
                            if (rev != null)
                            {
                                rev = ApplyOptions(options, rev, context, db, status);
                            }

                            if (rev != null)
                            {
                                result.Add(new Dictionary <string, object> {
                                    { "ok", rev.GetProperties() }
                                });
                            }
                            else
                            {
                                result.Add(new Dictionary <string, object> {
                                    { "missing", revID }
                                });
                            }
                        }
                    }

                    if (mustSendJson)
                    {
                        response["Content-Type"] = "application/json";
                        response.JsonBody = new Body(result.Cast <object>().ToList());
                    }
                    else
                    {
                        response.SetMultipartBody(result.Cast <object>().ToList(), "multipart/mixed");
                    }
                }

                return response;
            }).AsDefaultState());
        }
Пример #19
0
        // Apply the options in the URL query to the specified revision and create a new revision object
        private static RevisionInternal ApplyOptions(DocumentContentOptions options, RevisionInternal rev, ICouchbaseListenerContext context,
                                                     Database db, Status outStatus)
        {
            if ((options & (DocumentContentOptions.IncludeRevs | DocumentContentOptions.IncludeRevsInfo | DocumentContentOptions.IncludeConflicts |
                            DocumentContentOptions.IncludeAttachments | DocumentContentOptions.IncludeLocalSeq)) != 0)
            {
                var dst = rev.GetProperties();
                if (options.HasFlag(DocumentContentOptions.IncludeLocalSeq))
                {
                    dst["_local_seq"] = rev.GetSequence();
                }

                if (options.HasFlag(DocumentContentOptions.IncludeRevs))
                {
                    dst["_revisions"] = db.GetRevisionHistoryDict(rev);
                }

                if (options.HasFlag(DocumentContentOptions.IncludeRevsInfo))
                {
                    dst["_revs_info"] = db.GetRevisionHistory(rev).Select(x =>
                    {
                        string status = "available";
                        if (x.IsDeleted())
                        {
                            status = "deleted";
                        }
                        else if (x.IsMissing())
                        {
                            status = "missing";
                        }

                        return(new Dictionary <string, object> {
                            { "rev", x.GetRevId() },
                            { "status", status }
                        });
                    });
                }

                if (options.HasFlag(DocumentContentOptions.IncludeConflicts))
                {
                    RevisionList revs = db.GetAllRevisionsOfDocumentID(rev.GetDocId(), true);
                    if (revs.Count > 1)
                    {
                        dst["_conflicts"] = revs.Select(x =>
                        {
                            return(x.Equals(rev) || x.IsDeleted() ? null : x.GetRevId());
                        });
                    }
                }

                RevisionInternal nuRev = new RevisionInternal(dst);
                if (options.HasFlag(DocumentContentOptions.IncludeAttachments))
                {
                    bool attEncodingInfo = context.GetQueryParam <bool>("att_encoding_info", bool.TryParse, false);
                    if (!db.ExpandAttachments(nuRev, 0, false, !attEncodingInfo, outStatus))
                    {
                        return(null);
                    }
                }

                rev = nuRev;
            }

            return(rev);
        }
Пример #20
0
        /// <summary>
        /// Returns a sorted list of changes made to documents in the database, in time order of application.
        /// </summary>
        /// <returns>The response state for further HTTP processing</returns>
        /// <param name="context">The context of the Couchbase Lite HTTP request</param>
        /// <remarks>
        /// http://docs.couchdb.org/en/latest/api/database/changes.html#get--db-_changes
        /// <remarks>
        public static ICouchbaseResponseState GetChanges(ICouchbaseListenerContext context)
        {
            DBMonitorCouchbaseResponseState responseState = new DBMonitorCouchbaseResponseState();

            var responseObject = PerformLogicWithDatabase(context, true, db =>
            {
                var response           = context.CreateResponse();
                responseState.Response = response;
                if (context.ChangesFeedMode < ChangesFeedMode.Continuous)
                {
                    if (context.CacheWithEtag(db.LastSequenceNumber.ToString()))
                    {
                        response.InternalStatus = StatusCode.NotModified;
                        return(response);
                    }
                }

                var options                           = ChangesOptions.Default;
                responseState.Db                      = db;
                responseState.ContentOptions          = context.ContentOptions;
                responseState.ChangesFeedMode         = context.ChangesFeedMode;
                responseState.ChangesIncludeDocs      = context.GetQueryParam <bool>("include_docs", bool.TryParse, false);
                options.IncludeDocs                   = responseState.ChangesIncludeDocs;
                responseState.ChangesIncludeConflicts = context.GetQueryParam("style") == "all_docs";
                options.IncludeConflicts              = responseState.ChangesIncludeConflicts;
                options.ContentOptions                = context.ContentOptions;
                options.SortBySequence                = !options.IncludeConflicts;
                options.Limit                         = context.GetQueryParam <int>("limit", int.TryParse, options.Limit);
                int since = context.GetQueryParam <int>("since", int.TryParse, 0);

                string filterName = context.GetQueryParam("filter");
                if (filterName != null)
                {
                    Status status = new Status();
                    responseState.ChangesFilter = db.GetFilter(filterName, status);
                    if (responseState.ChangesFilter == null)
                    {
                        return(context.CreateResponse(status.Code));
                    }

                    responseState.FilterParams = context.GetQueryParams();
                }


                RevisionList changes = db.ChangesSince(since, options, responseState.ChangesFilter, responseState.FilterParams);
                if ((context.ChangesFeedMode >= ChangesFeedMode.Continuous) ||
                    (context.ChangesFeedMode == ChangesFeedMode.LongPoll && changes.Count == 0))
                {
                    // Response is going to stay open (continuous, or hanging GET):
                    response.Chunked = true;
                    if (context.ChangesFeedMode == ChangesFeedMode.EventSource)
                    {
                        response["Content-Type"] = "text/event-stream; charset=utf-8";
                    }

                    if (context.ChangesFeedMode >= ChangesFeedMode.Continuous)
                    {
                        response.WriteHeaders();
                        foreach (var rev in changes)
                        {
                            response.SendContinuousLine(ChangesDictForRev(rev, responseState), context.ChangesFeedMode);
                        }
                    }

                    responseState.SubscribeToDatabase(db);
                    string heartbeatParam = context.GetQueryParam("heartbeat");
                    if (heartbeatParam != null)
                    {
                        int heartbeat;
                        if (!int.TryParse(heartbeatParam, out heartbeat) || heartbeat <= 0)
                        {
                            responseState.IsAsync = false;
                            return(context.CreateResponse(StatusCode.BadParam));
                        }

                        heartbeat = Math.Min(heartbeat, MIN_HEARTBEAT);
                        string heartbeatResponse = context.ChangesFeedMode == ChangesFeedMode.EventSource ? "\n\n" : "\r\n";
                        responseState.StartHeartbeat(heartbeatResponse, heartbeat);
                    }

                    return(context.CreateResponse());
                }
                else
                {
                    if (responseState.ChangesIncludeConflicts)
                    {
                        response.JsonBody = new Body(ResponseBodyForChanges(changes, since, options.Limit, responseState));
                    }
                    else
                    {
                        response.JsonBody = new Body(ResponseBodyForChanges(changes, since, responseState));
                    }

                    return(response);
                }
            });

            responseState.Response = responseObject;
            return(responseState);
        }
Пример #21
0
        /// <summary>
        /// Returns a sorted list of changes made to documents in the database, in time order of application.
        /// </summary>
        /// <returns>The response state for further HTTP processing</returns>
        /// <param name="context">The context of the Couchbase Lite HTTP request</param>
        /// <remarks>
        /// http://docs.couchdb.org/en/latest/api/database/changes.html#get--db-_changes
        /// </remarks>
        public static ICouchbaseResponseState GetChanges(ICouchbaseListenerContext context)
        {
            DBMonitorCouchbaseResponseState responseState = new DBMonitorCouchbaseResponseState();

            var responseObject = PerformLogicWithDatabase(context, true, db =>
            {
                var response           = context.CreateResponse();
                responseState.Response = response;
                if (context.ChangesFeedMode < ChangesFeedMode.Continuous)
                {
                    if (context.CacheWithEtag(db.GetLastSequenceNumber().ToString()))
                    {
                        response.InternalStatus = StatusCode.NotModified;
                        return(response);
                    }
                }

                var options                           = ChangesOptions.Default;
                responseState.Db                      = db;
                responseState.ContentOptions          = context.ContentOptions;
                responseState.ChangesFeedMode         = context.ChangesFeedMode;
                responseState.ChangesIncludeDocs      = context.GetQueryParam <bool>("include_docs", bool.TryParse, false);
                options.IncludeDocs                   = responseState.ChangesIncludeDocs;
                responseState.ChangesIncludeConflicts = context.GetQueryParam("style") == "all_docs";
                options.IncludeConflicts              = responseState.ChangesIncludeConflicts;
                options.ContentOptions                = context.ContentOptions;
                options.SortBySequence                = !options.IncludeConflicts;
                options.Limit                         = context.GetQueryParam <int>("limit", int.TryParse, options.Limit);
                int since = context.GetQueryParam <int>("since", int.TryParse, 0);

                string filterName = context.GetQueryParam("filter");
                if (filterName != null)
                {
                    Status status = new Status();
                    responseState.ChangesFilter = db.GetFilter(filterName, status);
                    if (responseState.ChangesFilter == null)
                    {
                        var code = Database.FilterCompiler == null ? StatusCode.NotImplemented : StatusCode.NotFound;
                        return(context.CreateResponse(code));
                    }

                    responseState.FilterParams = context.GetQueryParams();
                }

                string heartbeatParam = context.GetQueryParam("heartbeat");
                int heartbeat         = 0;
                if (heartbeatParam != null && (!Int32.TryParse(heartbeatParam, out heartbeat) || heartbeat <= 0))
                {
                    return(context.CreateResponse(StatusCode.BadParam));
                }

                var changes = db.ChangesSinceStreaming(since, options, responseState.ChangesFilter, responseState.FilterParams);
                if ((context.ChangesFeedMode >= ChangesFeedMode.Continuous) ||
                    (context.ChangesFeedMode == ChangesFeedMode.LongPoll && !changes.Any()))
                {
                    // Response is going to stay open (continuous, or hanging GET):
                    response.Chunked = true;
                    if (context.ChangesFeedMode == ChangesFeedMode.EventSource)
                    {
                        response["Content-Type"] = "text/event-stream; charset=utf-8";
                    }

                    if (context.ChangesFeedMode >= ChangesFeedMode.Continuous)
                    {
                        response.WriteHeaders();
                        foreach (var rev in changes)
                        {
                            var success = response.SendContinuousLine(ChangesDictForRev(rev, responseState), context.ChangesFeedMode);
                            if (!success)
                            {
                                return(context.CreateResponse(StatusCode.BadRequest));
                            }
                        }
                    }

                    var subscribed = responseState.SubscribeToDatabase(db, since, options);
                    if (!subscribed)
                    {
                        return(response);
                    }

                    if (heartbeat > 0)
                    {
                        var heartbeatSpan = TimeSpan.FromMilliseconds(heartbeat);
                        if (heartbeatSpan < MinHeartbeat)
                        {
                            heartbeatSpan = MinHeartbeat;
                        }

                        string heartbeatResponse = context.ChangesFeedMode == ChangesFeedMode.EventSource ? "\n\n" : "\r\n";
                        responseState.StartHeartbeat(heartbeatResponse, heartbeatSpan);
                    }

                    return(response);
                }
                else
                {
                    response.Chunked = true;
                    response.Headers["Content-Type"] = "application/json";
                    response.WriteBodyCallback       = WriteChangesBodyJson;
                    if (responseState.ChangesIncludeConflicts)
                    {
                        response.WriteBodyContext = new WriteChangesContext {
                            IncludeConflicts = true,
                            Since            = since,
                            Limit            = options.Limit,
                            Changes          = changes,
                            ResponseState    = responseState
                        };
                    }
                    else
                    {
                        response.WriteBodyContext = new WriteChangesContext {
                            Since         = since,
                            Changes       = changes,
                            ResponseState = responseState
                        };
                    }
                }

                return(response);
            });

            responseState.Response = responseObject;
            return(responseState);
        }
        /// <summary>
        /// Requests one or more Universally Unique Identifiers (UUIDs) from the Couchbase Lite instance.
        /// </summary>
        /// <returns>The response state for further HTTP processing</returns>
        /// <param name="context">The context of the Couchbase Lite HTTP request</param>
        /// <remarks>
        /// http://docs.couchdb.org/en/latest/api/server/common.html#get--_uuids
        /// <remarks>
        public static ICouchbaseResponseState GetUUIDs(ICouchbaseListenerContext context)
        {
            int count = context.GetQueryParam<int>("count", int.TryParse, 1);
            if (count > 1000) {
                return context.CreateResponse(StatusCode.Forbidden).AsDefaultState();
            }

            var uuidList = new List<object>();
            for (int i = 0; i < count; i++) {
                uuidList.Add(Guid.NewGuid());
            }

            var couchResponse = context.CreateResponse();
            couchResponse.JsonBody = new Body(uuidList);
            return couchResponse.AsDefaultState();
        }
Пример #23
0
        // Apply the options in the URL query to the specified revision and create a new revision object
        internal static RevisionInternal ApplyOptions(DocumentContentOptions options, RevisionInternal rev, ICouchbaseListenerContext context,
                                                      Database db, Status outStatus)
        {
            if ((options & (DocumentContentOptions.IncludeRevs | DocumentContentOptions.IncludeRevsInfo | DocumentContentOptions.IncludeConflicts |
                            DocumentContentOptions.IncludeAttachments | DocumentContentOptions.IncludeLocalSeq)
                 | DocumentContentOptions.IncludeExpiration) != 0)
            {
                var dst = rev.GetProperties() ?? new Dictionary <string, object>();
                if (options.HasFlag(DocumentContentOptions.IncludeLocalSeq))
                {
                    dst["_local_seq"] = rev.Sequence;
                }

                if (options.HasFlag(DocumentContentOptions.IncludeRevs))
                {
                    var revs = db.GetRevisionHistory(rev, null);
                    dst["_revisions"] = TreeRevisionID.MakeRevisionHistoryDict(revs);
                }

                if (options.HasFlag(DocumentContentOptions.IncludeRevsInfo))
                {
                    dst["_revs_info"] = db.GetRevisionHistory(rev, null).Select(x =>
                    {
                        string status = "available";
                        var ancestor  = db.GetDocument(rev.DocID, x, true);
                        if (ancestor.Deleted)
                        {
                            status = "deleted";
                        }
                        else if (ancestor.Missing)
                        {
                            status = "missing";
                        }

                        return(new Dictionary <string, object> {
                            { "rev", x.ToString() },
                            { "status", status }
                        });
                    });
                }

                if (options.HasFlag(DocumentContentOptions.IncludeConflicts))
                {
                    RevisionList revs = db.Storage.GetAllDocumentRevisions(rev.DocID, true, false);
                    if (revs.Count > 1)
                    {
                        dst["_conflicts"] = from r in revs
                                            where !r.Equals(rev) && !r.Deleted
                                            select r.RevID.ToString();
                    }
                }

                if (options.HasFlag(DocumentContentOptions.IncludeExpiration))
                {
                    var expirationTime = db.Storage?.GetDocumentExpiration(rev.DocID);
                    if (expirationTime.HasValue)
                    {
                        dst["_exp"] = expirationTime;
                    }
                }

                RevisionInternal nuRev = new RevisionInternal(dst);
                if (options.HasFlag(DocumentContentOptions.IncludeAttachments))
                {
                    bool attEncodingInfo = context != null && context.GetQueryParam <bool>("att_encoding_info", bool.TryParse, false);
                    db.ExpandAttachments(nuRev, 0, false, !attEncodingInfo);
                }

                rev = nuRev;
            }

            return(rev);
        }
Пример #24
0
        /// <summary>
        /// Attempt to update a document based on the information in the HTTP request
        /// </summary>
        /// <returns>The resulting status of the operation</returns>
        /// <param name="context">The request context</param>
        /// <param name="db">The database in which the document exists</param>
        /// <param name="docId">The ID of the document being updated</param>
        /// <param name="body">The new document body</param>
        /// <param name="deleting">Whether or not the document is being deleted</param>
        /// <param name="allowConflict">Whether or not to allow a conflict to be inserted</param>
        /// <param name="outRev">The resulting revision of the document</param>
        public static StatusCode UpdateDocument(ICouchbaseListenerContext context, Database db, string docId, Body body, bool deleting, 
            bool allowConflict, out RevisionInternal outRev)
        {
            outRev = null;
            if (body != null && !body.IsValidJSON()) {
                return StatusCode.BadJson;
            }

            string prevRevId;
            if (!deleting) {
                var properties = body.GetProperties();
                deleting = properties.GetCast<bool>("_deleted");
                if (docId == null) {
                    // POST's doc ID may come from the _id field of the JSON body.
                    docId = properties.GetCast<string>("_id");
                    if (docId == null && deleting) {
                        return StatusCode.BadId;
                    }
                }

                // PUT's revision ID comes from the JSON body.
                prevRevId = properties.GetCast<string>("_rev");
            } else {
                // DELETE's revision ID comes from the ?rev= query param
                prevRevId = context.GetQueryParam("rev");
            }

            // A backup source of revision ID is an If-Match header:
            if (prevRevId == null) {
                prevRevId = context.IfMatch();
            }

            if (docId == null && deleting) {
                return StatusCode.BadId;
            }

            RevisionInternal rev = new RevisionInternal(docId, null, deleting);
            rev.SetBody(body);

            StatusCode status = StatusCode.Created;
            try {
                if (docId != null && docId.StartsWith("_local")) {
                    outRev = db.Storage.PutLocalRevision(rev, prevRevId, true); //TODO: Doesn't match iOS
                } else {
                    Status retStatus = new Status();
                    outRev = db.PutRevision(rev, prevRevId, allowConflict, retStatus);
                    status = retStatus.Code;
                }
            } catch(CouchbaseLiteException e) {
                status = e.Code;
            }

            return status;
        }
Пример #25
0
        /// <summary>
        /// Returns the file attachment associated with the document. The raw data of the associated attachment is returned 
        /// (just as if you were accessing a static file. The returned Content-Type will be the same as the content type 
        /// set when the document attachment was submitted into the database.
        /// </summary>
        /// <returns>The response state for further HTTP processing</returns>
        /// <param name="context">The context of the Couchbase Lite HTTP request</param>
        /// <remarks>
        /// http://docs.couchdb.org/en/latest/api/document/attachments.html#get--db-docid-attname
        /// <remarks>
        public static ICouchbaseResponseState GetAttachment(ICouchbaseListenerContext context)
        {
            return DatabaseMethods.PerformLogicWithDatabase(context, true, db =>
            {
                Status status = new Status();
                var rev = db.GetDocument(context.DocumentName, context.GetQueryParam("rev"), false, 
                    status);
                    
                if(rev ==null) {
                    return context.CreateResponse(status.Code);
                }
                if(context.CacheWithEtag(rev.GetRevId())) {
                    return context.CreateResponse(StatusCode.NotModified);
                }

                string acceptEncoding = context.RequestHeaders["Accept-Encoding"];
                bool acceptEncoded = acceptEncoding != null && acceptEncoding.Contains("gzip") &&
                    context.RequestHeaders["Range"] == null;

                var attachment = db.GetAttachmentForRevision(rev, context.AttachmentName, status);
                if(attachment == null) {
                    return context.CreateResponse(status.Code);
                }

                var response = context.CreateResponse();
                if(context.Method.Equals(HttpMethod.Head)) {
                    var length = attachment.Length;
                    if(acceptEncoded && attachment.Encoding == AttachmentEncoding.GZIP &&
                        attachment.EncodedLength > 0) {
                        length = attachment.EncodedLength;
                    }

                    response["Content-Length"] = length.ToString();
                } else {
                    var contents = acceptEncoded ? attachment.EncodedContent : attachment.Content;
                    if(contents == null) {
                        response.InternalStatus = StatusCode.NotFound;
                        return response;
                    }

                    response.BinaryBody = contents;
                }

                response["Content-Type"] = attachment.ContentType;
                if(acceptEncoded && attachment.Encoding == AttachmentEncoding.GZIP) {
                    response["Content-Encoding"] = "gzip";
                }

                return response;
            }).AsDefaultState();
        }
Пример #26
0
        // Apply the options in the URL query to the specified revision and create a new revision object
        internal static RevisionInternal ApplyOptions(DocumentContentOptions options, RevisionInternal rev, ICouchbaseListenerContext context,
            Database db, Status outStatus)
        {
            if ((options & (DocumentContentOptions.IncludeRevs | DocumentContentOptions.IncludeRevsInfo | DocumentContentOptions.IncludeConflicts |
                DocumentContentOptions.IncludeAttachments | DocumentContentOptions.IncludeLocalSeq)) != 0) {
                var dst = rev.GetProperties(); 
                if (options.HasFlag(DocumentContentOptions.IncludeLocalSeq)) {
                    dst["_local_seq"] = rev.GetSequence();
                }

                if (options.HasFlag(DocumentContentOptions.IncludeRevs)) {
                    var revs = db.GetRevisionHistory(rev, null);
                    dst["_revisions"] = Database.MakeRevisionHistoryDict(revs);
                }

                if (options.HasFlag(DocumentContentOptions.IncludeRevsInfo)) {
                    dst["_revs_info"] = db.Storage.GetRevisionHistory(rev, null).Select(x =>
                    {
                        string status = "available";
                        if(x.IsDeleted()) {
                            status = "deleted";
                        } else if(x.IsMissing()) {
                            status = "missing";
                        }

                        return new Dictionary<string, object> {
                            { "rev", x.GetRevId() },
                            { "status", status }
                        };
                    });
                }

                if (options.HasFlag(DocumentContentOptions.IncludeConflicts)) {
                    RevisionList revs = db.Storage.GetAllDocumentRevisions(rev.GetDocId(), true);
                    if (revs.Count > 1) {
                        dst["_conflicts"] = revs.Select(x =>
                        {
                            return x.Equals(rev) || x.IsDeleted() ? null : x.GetRevId();
                        });
                    }
                }

                RevisionInternal nuRev = new RevisionInternal(dst);
                if (options.HasFlag(DocumentContentOptions.IncludeAttachments)) {
                    bool attEncodingInfo = context != null && context.GetQueryParam<bool>("att_encoding_info", bool.TryParse, false);
                    if(!db.ExpandAttachments(nuRev, 0, false, !attEncodingInfo, outStatus)) {
                        return null;
                    }
                }

                rev = nuRev;
            }

            return rev;
        }
        /// <summary>
        /// Deletes the specified database, and all the documents and attachments contained within it.
        /// </summary>
        /// <returns>The response state for further HTTP processing</returns>
        /// <param name="context">The context of the Couchbase Lite HTTP request</param>
        /// <remarks>
        /// http://docs.couchdb.org/en/latest/api/database/common.html#delete--db
        /// </remarks>
        public static ICouchbaseResponseState DeleteConfiguration(ICouchbaseListenerContext context) 
        {
            return PerformLogicWithDatabase(context, false, db =>
            {
                if(context.GetQueryParam("rev") != null) {
                    // CouchDB checks for this; probably meant to be a document deletion
                    return context.CreateResponse(StatusCode.BadId);
                }

                try {
                    db.Delete();
                } catch (CouchbaseLiteException) {
                    return context.CreateResponse(StatusCode.InternalServerError);
                }

                return context.CreateResponse();
            }).AsDefaultState();
        }
        /// <summary>
        /// Attempt to update a document based on the information in the HTTP request
        /// </summary>
        /// <returns>The resulting status of the operation</returns>
        /// <param name="context">The request context</param>
        /// <param name="db">The database in which the document exists</param>
        /// <param name="docId">The ID of the document being updated</param>
        /// <param name="body">The new document body</param>
        /// <param name="deleting">Whether or not the document is being deleted</param>
        /// <param name="allowConflict">Whether or not to allow a conflict to be inserted</param>
        /// <param name="outRev">The resulting revision of the document</param>
        public static StatusCode UpdateDocument(ICouchbaseListenerContext context, Database db, string docId, Body body, bool deleting, 
            bool allowConflict, out RevisionInternal outRev)
        {
            outRev = null;
            if (body != null && !body.IsValidJSON()) {
                return StatusCode.BadJson;
            }

            string prevRevId;
            if (!deleting) {
                var properties = body.GetProperties();
                deleting = properties.GetCast<bool>("_deleted");
                if (docId == null) {
                    // POST's doc ID may come from the _id field of the JSON body.
                    docId = properties.CblID();
                    if (docId == null && deleting) {
                        return StatusCode.BadId;
                    }
                }

                // PUT's revision ID comes from the JSON body.
                prevRevId = properties.GetCast<string>("_rev");
            } else {
                // DELETE's revision ID comes from the ?rev= query param
                prevRevId = context.GetQueryParam("rev");
            }

            // A backup source of revision ID is an If-Match header:
            if (prevRevId == null) {
                prevRevId = context.IfMatch();
            }

            if (docId == null && deleting) {
                return StatusCode.BadId;
            }

            RevisionInternal rev = new RevisionInternal(docId, null, deleting);
            rev.SetBody(body);

            // Check for doc expiration
            var expirationTime = default(DateTime?);
            var tmp = default(object);
            var props = rev.GetProperties();
            var hasValue = false;
            if(props != null && props.TryGetValue("_exp", out tmp)) {
                hasValue = true;
                if(tmp != null) {
                    try {
                        expirationTime = Convert.ToDateTime(tmp);
                    } catch(Exception) {
                        try {
                            var num = Convert.ToInt64(tmp);
                            expirationTime = Misc.OffsetFromEpoch(TimeSpan.FromSeconds(num));
                        } catch(Exception) {
                            Log.To.Router.E(TAG, "Invalid value for _exp: {0}", tmp);
                            return StatusCode.BadRequest;
                        }

                    }
                }
            
                props.Remove("_exp");
                rev.SetProperties(props);
            }

            var castContext = context as ICouchbaseListenerContext2;
            var source = castContext != null && !castContext.IsLoopbackRequest ? castContext.Sender : null;
            StatusCode status = deleting ? StatusCode.Ok : StatusCode.Created;
            try {
                if(docId != null && docId.StartsWith("_local")) {
                    if(expirationTime.HasValue) {
                        return StatusCode.BadRequest;
                    }

                    outRev = db.Storage.PutLocalRevision(rev, prevRevId.AsRevID(), true); //TODO: Doesn't match iOS
                } else {
                    outRev = db.PutRevision(rev, prevRevId.AsRevID(), allowConflict, source);
                    if(hasValue) {
                        db.Storage?.SetDocumentExpiration(rev.DocID, expirationTime);
                    }
                }
            } catch(CouchbaseLiteException e) {
                status = e.Code;
            }

            return status;
        }