public void TestFollowWithRevPos() { var attachInfo = new Dictionary<string, object> { { "content_type", "text/plain" }, { "digest", "md5-DaUdFsLh8FKLbcBIDlU57g==" }, { "follows", true }, { "length", 51200 }, { "revpos", 2 } }; var attachment = default(AttachmentInternal); Assert.DoesNotThrow(() => attachment = new AttachmentInternal("attachment", attachInfo)); var stub = attachment.AsStubDictionary(); var expected = new Dictionary<string, object> { { "content_type", "text/plain" }, { "digest", "sha1-AAAAAAAAAAAAAAAAAAAAAAAAAAA=" }, { "stub", true }, { "length", 51200 }, { "revpos", 2 } }; AssertDictionariesAreEqual(expected, stub); }
/// <summary> /// Given a revision, read its _attachments dictionary (if any), convert each attachment to a /// AttachmentInternal object, and return a dictionary mapping names->CBL_Attachments. /// </summary> /// <remarks> /// Given a revision, read its _attachments dictionary (if any), convert each attachment to a /// AttachmentInternal object, and return a dictionary mapping names->CBL_Attachments. /// </remarks> /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> internal IDictionary<String, AttachmentInternal> GetAttachmentsFromRevision(RevisionInternal rev) { var revAttachments = rev.GetPropertyForKey("_attachments").AsDictionary<string, object>(); if (revAttachments == null || revAttachments.Count == 0 || rev.IsDeleted()) { return new Dictionary<string, AttachmentInternal>(); } var attachments = new Dictionary<string, AttachmentInternal>(); foreach (var name in revAttachments.Keys) { var attachInfo = revAttachments.Get(name).AsDictionary<string, object>(); var contentType = (string)attachInfo.Get("content_type"); var attachment = new AttachmentInternal(name, contentType); var newContentBase64 = (string)attachInfo.Get("data"); if (newContentBase64 != null) { // If there's inline attachment data, decode and store it: byte[] newContents; try { newContents = StringUtils.ConvertFromUnpaddedBase64String (newContentBase64); } catch (IOException e) { throw new CouchbaseLiteException(e, StatusCode.BadEncoding); } attachment.Length = newContents.Length; var outBlobKey = new BlobKey(); var storedBlob = Attachments.StoreBlob(newContents, outBlobKey); attachment.BlobKey = outBlobKey; if (!storedBlob) { throw new CouchbaseLiteException(StatusCode.AttachmentError); } } else { if (attachInfo.ContainsKey("follows") && ((bool)attachInfo.Get("follows"))) { // "follows" means the uploader provided the attachment in a separate MIME part. // This means it's already been registered in _pendingAttachmentsByDigest; // I just need to look it up by its "digest" property and install it into the store: InstallAttachment(attachment, attachInfo); } else { // This item is just a stub; validate and skip it if (((bool)attachInfo.Get("stub")) == false) { throw new CouchbaseLiteException("Expected this attachment to be a stub", StatusCode. BadAttachment); } var revPos = Convert.ToInt64(attachInfo.Get("revpos")); if (revPos <= 0) { throw new CouchbaseLiteException("Invalid revpos: " + revPos, StatusCode.BadAttachment); } continue; } } // Handle encoded attachment: string encodingStr = (string)attachInfo.Get("encoding"); if (encodingStr != null && encodingStr.Length > 0) { if (Runtime.EqualsIgnoreCase(encodingStr, "gzip")) { attachment.Encoding = AttachmentEncoding.GZIP; } else { throw new CouchbaseLiteException("Unnkown encoding: " + encodingStr, StatusCode.BadEncoding ); } attachment.EncodedLength = attachment.Length; if (attachInfo.ContainsKey("length")) { attachment.Length = attachInfo.GetCast<long>("length"); } } if (attachInfo.ContainsKey("revpos")) { var revpos = Convert.ToInt32(attachInfo.Get("revpos")); attachment.RevPos = revpos; } attachments[name] = attachment; } return attachments; }
internal AttachmentInternal AttachmentForDict(IDictionary<string, object> info, string filename, Status status) { if (info == null) { if (status != null) { status.Code = StatusCode.NotFound; } return null; } AttachmentInternal attachment; try { attachment = new AttachmentInternal(filename, info); } catch(CouchbaseLiteException e) { if (status != null) { status.Code = e.CBLStatus.Code; } return null; } attachment.Database = this; return attachment; }
internal bool ProcessAttachmentsForRevision(RevisionInternal rev, string prevRevId, Status status) { if (status == null) { status = new Status(); } status.Code = StatusCode.Ok; var revAttachments = rev.GetAttachments(); if (revAttachments == null) { return true; // no-op: no attachments } // Deletions can't have attachments: if (rev.IsDeleted() || revAttachments.Count == 0) { var body = rev.GetProperties(); body.Remove("_attachments"); rev.SetProperties(body); return true; } int generation = RevisionInternal.GenerationFromRevID(prevRevId) + 1; IDictionary<string, object> parentAttachments = null; return rev.MutateAttachments((name, attachInfo) => { AttachmentInternal attachment = null; try { attachment = new AttachmentInternal(name, attachInfo); } catch(CouchbaseLiteException e) { return null; } if(attachment.EncodedContent != null) { // If there's inline attachment data, decode and store it: BlobKey blobKey = new BlobKey(); if(!Attachments.StoreBlob(attachment.EncodedContent.ToArray(), blobKey)) { status.Code = StatusCode.AttachmentError; return null; } attachment.BlobKey = blobKey; } else if(attachInfo.GetCast<bool>("follows")) { // "follows" means the uploader provided the attachment in a separate MIME part. // This means it's already been registered in _pendingAttachmentsByDigest; // I just need to look it up by its "digest" property and install it into the store: InstallAttachment(attachment, attachInfo); } else if(attachInfo.GetCast<bool>("stub")) { // "stub" on an incoming revision means the attachment is the same as in the parent. if(parentAttachments == null && prevRevId != null) { parentAttachments = GetAttachmentsFromDoc(rev.GetDocId(), prevRevId, status); if(parentAttachments == null) { if(status.Code == StatusCode.Ok || status.Code == StatusCode.NotFound) { status.Code = StatusCode.BadAttachment; } return null; } } var parentAttachment = parentAttachments == null ? null : parentAttachments.Get(name).AsDictionary<string, object>(); if(parentAttachment == null) { status.Code = StatusCode.BadAttachment; return null; } return parentAttachment; } // Set or validate the revpos: if(attachment.RevPos == 0) { attachment.RevPos = generation; } else if(attachment.RevPos >= generation) { status.Code = StatusCode.BadAttachment; return null; } Debug.Assert(attachment.IsValid); return attachment.AsStubDictionary(); }); }
/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> internal void InsertAttachmentForSequence(AttachmentInternal attachment, long sequence) { InsertAttachmentForSequenceWithNameAndType(sequence, attachment.GetName(), attachment.GetContentType(), attachment.GetRevpos(), attachment.GetBlobKey()); }
/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> internal void InstallAttachment(AttachmentInternal attachment, IDictionary<String, Object> attachInfo) { var digest = (string)attachInfo.Get("digest"); if (digest == null) { throw new CouchbaseLiteException(StatusCode.BadAttachment); } if (PendingAttachmentsByDigest != null && PendingAttachmentsByDigest.ContainsKey(digest)) { var writer = PendingAttachmentsByDigest.Get(digest); try { var blobStoreWriter = writer; blobStoreWriter.Install(); attachment.BlobKey = (blobStoreWriter.GetBlobKey()); attachment.Length = blobStoreWriter.GetLength(); } catch (Exception e) { throw new CouchbaseLiteException(e, StatusCode.AttachmentError); } } }
internal AttachmentInternal AttachmentForDict(IDictionary<string, object> info, string filename) { if (info == null) { return null; } AttachmentInternal attachment = new AttachmentInternal(filename, info); attachment.Database = this; return attachment; }
internal bool ProcessAttachmentsForRevision(RevisionInternal rev, IList<string> ancestry) { var revAttachments = rev.GetAttachments(); if (revAttachments == null) { return true; // no-op: no attachments } // Deletions can't have attachments: if (rev.IsDeleted() || revAttachments.Count == 0) { var body = rev.GetProperties(); body.Remove("_attachments"); rev.SetProperties(body); return true; } var prevRevId = ancestry != null && ancestry.Count > 0 ? ancestry[0] : null; int generation = RevisionInternal.GenerationFromRevID(prevRevId) + 1; IDictionary<string, object> parentAttachments = null; return rev.MutateAttachments((name, attachInfo) => { AttachmentInternal attachment = null; try { attachment = new AttachmentInternal(name, attachInfo); } catch(CouchbaseLiteException) { return null; } if(attachment.EncodedContent != null) { // If there's inline attachment data, decode and store it: BlobKey blobKey = new BlobKey(); if(!Attachments.StoreBlob(attachment.EncodedContent.ToArray(), blobKey)) { throw new CouchbaseLiteException( String.Format("Failed to write attachment ' {0}'to disk", name), StatusCode.AttachmentError); } attachment.BlobKey = blobKey; } else if(attachInfo.GetCast<bool>("follows")) { // "follows" means the uploader provided the attachment in a separate MIME part. // This means it's already been registered in _pendingAttachmentsByDigest; // I just need to look it up by its "digest" property and install it into the store: InstallAttachment(attachment); } else if(attachInfo.GetCast<bool>("stub")) { // "stub" on an incoming revision means the attachment is the same as in the parent. if(parentAttachments == null && prevRevId != null) { parentAttachments = GetAttachmentsFromDoc(rev.GetDocId(), prevRevId); if(parentAttachments == null) { if(Attachments.HasBlobForKey(attachment.BlobKey)) { // Parent revision's body isn't known (we are probably pulling a rev along // with its entire history) but it's OK, we have the attachment already return attachInfo; } var ancestorAttachment = FindAttachment(name, attachment.RevPos, rev.GetDocId(), ancestry); if(ancestorAttachment != null) { return ancestorAttachment; } throw new CouchbaseLiteException( String.Format("Unable to find 'stub' attachment {0} in history", name), StatusCode.BadAttachment); } } var parentAttachment = parentAttachments == null ? null : parentAttachments.Get(name).AsDictionary<string, object>(); if(parentAttachment == null) { throw new CouchbaseLiteException( String.Format("Unable to find 'stub' attachment {0} in history", name), StatusCode.BadAttachment); } return parentAttachment; } // Set or validate the revpos: if(attachment.RevPos == 0) { attachment.RevPos = generation; } else if(attachment.RevPos > generation) { throw new CouchbaseLiteException( String.Format("Attachment specifies revision generation {0} but document is only at revision generation {1}", attachment.RevPos, generation), StatusCode.BadAttachment); } Debug.Assert(attachment.IsValid); return attachment.AsStubDictionary(); }); }
internal IDictionary<string, AttachmentInternal> GetAttachmentsFromRevision(RevisionInternal rev) { IDictionary<string, object> revAttachments = (IDictionary<string, object>)rev.GetPropertyForKey ("_attachments"); if (revAttachments == null || revAttachments.Count == 0 || rev.IsDeleted()) { return new Dictionary<string, AttachmentInternal>(); } IDictionary<string, AttachmentInternal> attachments = new Dictionary<string, AttachmentInternal >(); foreach (string name in revAttachments.Keys) { IDictionary<string, object> attachInfo = (IDictionary<string, object>)revAttachments .Get(name); string contentType = (string)attachInfo.Get("content_type"); AttachmentInternal attachment = new AttachmentInternal(name, contentType); string newContentBase64 = (string)attachInfo.Get("data"); if (newContentBase64 != null) { // If there's inline attachment data, decode and store it: byte[] newContents; try { newContents = Base64.Decode(newContentBase64); } catch (IOException e) { throw new CouchbaseLiteException(e, Status.BadEncoding); } attachment.SetLength(newContents.Length); BlobKey outBlobKey = new BlobKey(); bool storedBlob = GetAttachments().StoreBlob(newContents, outBlobKey); attachment.SetBlobKey(outBlobKey); if (!storedBlob) { throw new CouchbaseLiteException(Status.StatusAttachmentError); } } else { if (attachInfo.ContainsKey("follows") && ((bool)attachInfo.Get("follows")) == true) { // "follows" means the uploader provided the attachment in a separate MIME part. // This means it's already been registered in _pendingAttachmentsByDigest; // I just need to look it up by its "digest" property and install it into the store: InstallAttachment(attachment, attachInfo); } else { // This item is just a stub; validate and skip it if (((bool)attachInfo.Get("stub")) == false) { throw new CouchbaseLiteException("Expected this attachment to be a stub", Status. BadAttachment); } int revPos = ((int)attachInfo.Get("revpos")); if (revPos <= 0) { throw new CouchbaseLiteException("Invalid revpos: " + revPos, Status.BadAttachment ); } continue; } } // Handle encoded attachment: string encodingStr = (string)attachInfo.Get("encoding"); if (encodingStr != null && encodingStr.Length > 0) { if (Sharpen.Runtime.EqualsIgnoreCase(encodingStr, "gzip")) { attachment.SetEncoding(AttachmentInternal.AttachmentEncoding.AttachmentEncodingGZIP ); } else { throw new CouchbaseLiteException("Unnkown encoding: " + encodingStr, Status.BadEncoding ); } attachment.SetEncodedLength(attachment.GetLength()); if (attachInfo.ContainsKey("length")) { Number attachmentLength = (Number)attachInfo.Get("length"); attachment.SetLength(attachmentLength); } } if (attachInfo.ContainsKey("revpos")) { attachment.SetRevpos((int)attachInfo.Get("revpos")); } else { attachment.SetRevpos(1); } attachments.Put(name, attachment); } return attachments; }
internal void InstallAttachment(AttachmentInternal attachment, IDictionary<string , object> attachInfo) { string digest = (string)attachInfo.Get("digest"); if (digest == null) { throw new CouchbaseLiteException(Status.BadAttachment); } if (pendingAttachmentsByDigest != null && pendingAttachmentsByDigest.ContainsKey( digest)) { BlobStoreWriter writer = pendingAttachmentsByDigest.Get(digest); try { BlobStoreWriter blobStoreWriter = (BlobStoreWriter)writer; blobStoreWriter.Install(); attachment.SetBlobKey(blobStoreWriter.GetBlobKey()); attachment.SetLength(blobStoreWriter.GetLength()); } catch (Exception e) { throw new CouchbaseLiteException(e, Status.StatusAttachmentError); } } }
public virtual void SetEncoding(AttachmentInternal.AttachmentEncoding encoding) { this.encoding = encoding; }
internal bool ProcessAttachmentsForRevision(RevisionInternal rev, IList<RevisionID> ancestry) { var revAttachments = rev.GetAttachments(); if(revAttachments == null) { return true; // no-op: no attachments } // Deletions can't have attachments: if(rev.Deleted || revAttachments.Count == 0) { var body = rev.GetProperties(); body.Remove("_attachments"); rev.SetProperties(body); return true; } var prevRevId = ancestry != null && ancestry.Count > 0 ? ancestry[0] : null; int generation = rev.Generation; IDictionary<string, object> parentAttachments = null; return rev.MutateAttachments((name, attachInfo) => { AttachmentInternal attachment = null; try { attachment = new AttachmentInternal(name, attachInfo); } catch(CouchbaseLiteException) { Log.To.Database.W(TAG, "Error creating attachment object for '{0}' ('{1}'), " + "returning null", new SecureLogString(name, LogMessageSensitivity.PotentiallyInsecure), new SecureLogJsonString(attachInfo, LogMessageSensitivity.PotentiallyInsecure)); return null; } if(attachment.EncodedContent != null) { // If there's inline attachment data, decode and store it: BlobKey blobKey = new BlobKey(); try { Attachments.StoreBlob(attachment.EncodedContent.ToArray(), blobKey); } catch(CouchbaseLiteException) { Log.To.Database.E(TAG, "Failed to write attachment '{0}' to disk, rethrowing...", name); throw; } catch(Exception e) { throw Misc.CreateExceptionAndLog(Log.To.Database, e, TAG, "Exception during attachment writing '{0}'", new SecureLogString(name, LogMessageSensitivity.PotentiallyInsecure)); } attachment.BlobKey = blobKey; } else if(attachInfo.GetCast<bool>("follows")) { // "follows" means the uploader provided the attachment in a separate MIME part. // This means it's already been registered in _pendingAttachmentsByDigest; // I just need to look it up by its "digest" property and install it into the store: InstallAttachment(attachment); } else if(attachInfo.GetCast<bool>("stub")) { // "stub" on an incoming revision means the attachment is the same as in the parent. if(parentAttachments == null && prevRevId != null) { parentAttachments = GetAttachmentsFromDoc(rev.DocID, prevRevId); if(parentAttachments == null) { if(Attachments.HasBlobForKey(attachment.BlobKey)) { // Parent revision's body isn't known (we are probably pulling a rev along // with its entire history) but it's OK, we have the attachment already return attachInfo; } var ancestorAttachment = FindAttachment(name, attachment.RevPos, rev.DocID, ancestry); if(ancestorAttachment != null) { return ancestorAttachment; } throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.BadAttachment, TAG, "Unable to find 'stub' attachment {0} in history (1)", new SecureLogString(name, LogMessageSensitivity.PotentiallyInsecure)); } } var parentAttachment = parentAttachments == null ? null : parentAttachments.Get(name).AsDictionary<string, object>(); if(parentAttachment == null) { throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.BadAttachment, TAG, "Unable to find 'stub' attachment {0} in history (2)", new SecureLogString(name, LogMessageSensitivity.PotentiallyInsecure)); } return parentAttachment; } // Set or validate the revpos: if(attachment.RevPos == 0) { attachment.RevPos = generation; } else if(attachment.RevPos > generation) { throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.BadAttachment, TAG, "Attachment specifies revision generation {0} but document is only at revision generation {1}", attachment.RevPos, generation); } Debug.Assert(attachment.IsValid); return attachment.AsStubDictionary(); }); }
/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> internal void InstallAttachment(AttachmentInternal attachment) { var digest = attachment.Digest; if(digest == null) { throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.BadAttachment, TAG, "InstallAttachment received an attachment without a digest"); } if(PendingAttachmentsByDigest != null && PendingAttachmentsByDigest.ContainsKey(digest)) { var writer = PendingAttachmentsByDigest.Get(digest); try { var blobStoreWriter = writer; blobStoreWriter.Install(); attachment.BlobKey = (blobStoreWriter.GetBlobKey()); attachment.Length = blobStoreWriter.GetLength(); } catch(CouchbaseLiteException) { Log.To.Database.E(TAG, "Error installing attachment '{0}', rethrowing...", new SecureLogString(attachment.Name, LogMessageSensitivity.PotentiallyInsecure)); } catch(Exception e) { throw Misc.CreateExceptionAndLog(Log.To.Database, e, TAG, "Error installing attachment '{0}'", new SecureLogString(attachment.Name, LogMessageSensitivity.PotentiallyInsecure)); } } }