Merge pull request #1837 from libgit2/ntk/topic/control_stream_write_size odb: Error when streaming in less|more bytes than declared
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
diff --git a/include/git2/odb.h b/include/git2/odb.h
index 3e93a93..e50a2a1 100644
--- a/include/git2/odb.h
+++ b/include/git2/odb.h
@@ -238,6 +238,9 @@ GIT_EXTERN(int) git_odb_open_wstream(git_odb_stream **out, git_odb *db, size_t s
/**
* Write to an odb stream
*
+ * This method will fail as soon as the total number of
+ * received bytes exceeds the size declared with `git_odb_open_wstream()`
+ *
* @param stream the stream
* @param buffer the data to write
* @param len the buffer's length
@@ -251,6 +254,9 @@ GIT_EXTERN(int) git_odb_stream_write(git_odb_stream *stream, const char *buffer,
* The object will take its final name and will be available to the
* odb.
*
+ * This method will fail if the total number of received bytes
+ * differs from the size declared with `git_odb_open_wstream()`
+ *
* @param out pointer to store the resulting object's id
* @param stream the stream
* @return 0 on success; an error code otherwise
diff --git a/include/git2/odb_backend.h b/include/git2/odb_backend.h
index bafeec0..e558bbb 100644
--- a/include/git2/odb_backend.h
+++ b/include/git2/odb_backend.h
@@ -78,6 +78,9 @@ struct git_odb_stream {
unsigned int mode;
void *hash_ctx;
+ size_t declared_size;
+ size_t received_bytes;
+
/**
* Write at most `len` bytes into `buffer` and advance the
* stream.
@@ -93,9 +96,13 @@ struct git_odb_stream {
* Store the contents of the stream as an object with the id
* specified in `oid`.
*
- * This method will *not* be invoked by libgit2 if the object pointed at
- * by `oid` already exists in any backend. Libgit2 will however take care
- * of properly disposing the stream through a call to `free()`.
+ * This method will *not* be invoked by libgit2 when:
+ * - the object pointed at by `oid` already exists in any backend.
+ * - the total number of received bytes differs from the size declared
+ * with `git_odb_open_wstream()`
+ *
+ * Libgit2 will however take care of properly disposing the stream through
+ * a call to `free()`.
*/
int (*finalize_write)(git_odb_stream *stream, const git_oid *oid);
diff --git a/src/odb.c b/src/odb.c
index dfb2521..2e68695 100644
--- a/src/odb.c
+++ b/src/odb.c
@@ -888,17 +888,44 @@ int git_odb_open_wstream(
hash_header(ctx, size, type);
(*stream)->hash_ctx = ctx;
+ (*stream)->declared_size = size;
+ (*stream)->received_bytes = 0;
+
return error;
}
+static int git_odb_stream__invalid_length(
+ const git_odb_stream *stream,
+ const char *action)
+{
+ giterr_set(GITERR_ODB,
+ "Cannot %s - "
+ "Invalid length. %"PRIuZ" was expected. The "
+ "total size of the received chunks amounts to %"PRIuZ".",
+ action, stream->declared_size, stream->received_bytes);
+
+ return -1;
+}
+
int git_odb_stream_write(git_odb_stream *stream, const char *buffer, size_t len)
{
git_hash_update(stream->hash_ctx, buffer, len);
+
+ stream->received_bytes += len;
+
+ if (stream->received_bytes > stream->declared_size)
+ return git_odb_stream__invalid_length(stream,
+ "stream_write()");
+
return stream->write(stream, buffer, len);
}
int git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stream)
{
+ if (stream->received_bytes != stream->declared_size)
+ return git_odb_stream__invalid_length(stream,
+ "stream_finalize_write()");
+
git_hash_final(out, stream->hash_ctx);
if (git_odb_exists(stream->backend->odb, out))
diff --git a/tests-clar/odb/streamwrite.c b/tests-clar/odb/streamwrite.c
new file mode 100644
index 0000000..591a200
--- /dev/null
+++ b/tests-clar/odb/streamwrite.c
@@ -0,0 +1,56 @@
+#include "clar_libgit2.h"
+#include "git2/odb_backend.h"
+
+static git_repository *repo;
+static git_odb *odb;
+static git_odb_stream *stream;
+
+void test_odb_streamwrite__initialize(void)
+{
+ repo = cl_git_sandbox_init("testrepo.git");
+ cl_git_pass(git_repository_odb(&odb, repo));
+
+ cl_git_pass(git_odb_open_wstream(&stream, odb, 14, GIT_OBJ_BLOB));
+ cl_assert_equal_sz(14, stream->declared_size);
+}
+
+void test_odb_streamwrite__cleanup(void)
+{
+ git_odb_stream_free(stream);
+ git_odb_free(odb);
+ cl_git_sandbox_cleanup();
+}
+
+void test_odb_streamwrite__can_accept_chunks(void)
+{
+ git_oid oid;
+
+ cl_git_pass(git_odb_stream_write(stream, "deadbeef", 8));
+ cl_assert_equal_sz(8, stream->received_bytes);
+
+ cl_git_pass(git_odb_stream_write(stream, "deadbeef", 6));
+ cl_assert_equal_sz(8 + 6, stream->received_bytes);
+
+ cl_git_pass(git_odb_stream_finalize_write(&oid, stream));
+}
+
+void test_odb_streamwrite__can_detect_missing_bytes(void)
+{
+ git_oid oid;
+
+ cl_git_pass(git_odb_stream_write(stream, "deadbeef", 8));
+ cl_assert_equal_sz(8, stream->received_bytes);
+
+ cl_git_pass(git_odb_stream_write(stream, "deadbeef", 4));
+ cl_assert_equal_sz(8 + 4, stream->received_bytes);
+
+ cl_git_fail(git_odb_stream_finalize_write(&oid, stream));
+}
+
+void test_odb_streamwrite__can_detect_additional_bytes(void)
+{
+ cl_git_pass(git_odb_stream_write(stream, "deadbeef", 8));
+ cl_assert_equal_sz(8, stream->received_bytes);
+
+ cl_git_fail(git_odb_stream_write(stream, "deadbeef", 7));
+}