diff --git a/rhizome_bundle.c b/rhizome_bundle.c index 79d5a95b..5b5189b9 100644 --- a/rhizome_bundle.c +++ b/rhizome_bundle.c @@ -988,6 +988,31 @@ rhizome_manifest_parse_field(rhizome_manifest *m, const char *field_label, size_ return status; } +/* Remove the field with the given label from the manifest. + * + * @author Andrew Bettison + */ +int rhizome_manifest_remove_field(rhizome_manifest *m, const char *field_label, size_t field_label_len) +{ + if (!rhizome_manifest_field_label_is_valid(field_label, field_label_len)) { + if (config.debug.rhizome_manifest) + DEBUGF("Invalid manifest field name: %s", alloca_toprint(100, field_label, field_label_len)); + return 0; + } + const char *label = alloca_strndup(field_label, field_label_len); + struct rhizome_manifest_field_descriptor *desc = NULL; + unsigned i; + for (i = 0; desc == NULL && i < NELS(rhizome_manifest_fields); ++i) + if (strcasecmp(label, rhizome_manifest_fields[i].label) == 0) + desc = &rhizome_manifest_fields[i]; + if (!desc) + return rhizome_manifest_del(m, label); + if (!desc->test(m)) + return 0; + desc->unset(m); + return 1; +} + /* If all essential (transport) fields are present and well formed then sets the m->finalised field * and returns 1, otherwise returns 0. * diff --git a/rhizome_cli.c b/rhizome_cli.c index 7a07a626..9f21e901 100644 --- a/rhizome_cli.c +++ b/rhizome_cli.c @@ -104,7 +104,7 @@ static int app_rhizome_hash_file(const struct cli_parsed *parsed, struct cli_con DEFINE_CMD(app_rhizome_add_file, 0, "Add a file to Rhizome and optionally write its manifest to the given path", - "rhizome","add","file" KEYRING_PIN_OPTIONS,"[--force-new]","","","[]","[]"); + "rhizome","add","file" KEYRING_PIN_OPTIONS,"[--force-new]","","","[]","[]","..."); DEFINE_CMD(app_rhizome_add_file, 0, "Append content to a journal bundle", "rhizome", "journal", "append" KEYRING_PIN_OPTIONS, "", "", "", "[]"); @@ -134,6 +134,42 @@ static int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_cont if (bsktext && str_to_rhizome_bsk_t(&bsk, bsktext) == -1) return WHYF("invalid bsk: \"%s\"", bsktext); + unsigned nfields = (parsed->varargi == -1) ? 0 : parsed->argc - (unsigned)parsed->varargi; + struct field { + const char *label; + size_t labellen; + const char *value; + size_t valuelen; + } + fields[nfields]; + if (nfields) { + assert(parsed->varargi >= 0); + unsigned i; + for (i = 0; i < nfields; ++i) { + struct field *field = &fields[i]; + unsigned n = (unsigned)parsed->varargi + i; + assert(n < parsed->argc); + const char *arg = parsed->args[n]; + size_t arglen = strlen(arg); + const char *eq; + if (arglen > 0 && arg[0] == '!') { + field->label = arg + 1; + field->labellen = arglen - 1; + field->value = NULL; + } else if ((eq = strchr(arg, '='))) { + field->label = arg; + field->labellen = eq - arg; + field->value = eq + 1; + field->valuelen = (arg + arglen) - field->value; + } else + return WHYF("invalid manifest field argument: %s", alloca_str_toprint(arg)); + if (!rhizome_manifest_field_label_is_valid(field->label, field->labellen)) + return WHYF("invalid manifest field label: %s", alloca_toprint(-1, field->label, field->labellen)); + if (field->value && !rhizome_manifest_field_value_is_valid(field->value, field->valuelen)) + return WHYF("invalid manifest field value: %s", alloca_toprint(-1, field->value, field->valuelen)); + } + } + int journal = strcasecmp(parsed->args[1], "journal")==0; if (create_serval_instance_dir() == -1) @@ -194,6 +230,43 @@ static int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_cont goto finish; } + if (nfields) { + unsigned i; + for (i = 0; i != nfields; ++i) { + struct field *field = &fields[i]; + rhizome_manifest_remove_field(m, field->label, field->labellen); + if (field->value) { + const char *label = alloca_strndup(field->label, field->labellen); + enum rhizome_manifest_parse_status status = rhizome_manifest_parse_field(m, field->label, field->labellen, field->value, field->valuelen); + int status_ok = 0; + switch (status) { + case RHIZOME_MANIFEST_ERROR: + ret = WHY("Fatal error while updating manifest field"); + goto finish; + case RHIZOME_MANIFEST_OK: + status_ok = 1; + break; + case RHIZOME_MANIFEST_SYNTAX_ERROR: + ret = WHYF("Manifest syntax error: %s=%s", label, alloca_toprint(-1, field->value, field->valuelen)); + goto finish; + case RHIZOME_MANIFEST_DUPLICATE_FIELD: + abort(); // should not happen, field was removed first + case RHIZOME_MANIFEST_INVALID: + ret = WHYF("Manifest invalid field: %s=%s", label, alloca_toprint(-1, field->value, field->valuelen)); + goto finish; + case RHIZOME_MANIFEST_MALFORMED: + ret = WHYF("Manifest malformed field: %s=%s", label, alloca_toprint(-1, field->value, field->valuelen)); + goto finish; + case RHIZOME_MANIFEST_OVERFLOW: + ret = WHYF("Too many fields in manifest at: %s=%s", label, alloca_toprint(-1, field->value, field->valuelen)); + goto finish; + } + if (!status_ok) + FATALF("status = %d", status); + } + } + } + if (bsktext) { if (m->has_id) { if (!rhizome_apply_bundle_secret(m, &bsk)) { diff --git a/tests/rhizomeops b/tests/rhizomeops index 7cad0e47..b63c97ce 100755 --- a/tests/rhizomeops +++ b/tests/rhizomeops @@ -1,8 +1,8 @@ #!/bin/bash -# Tests for Serval rhizome operations. +# Tests for Serval rhizome command-line operations. # -# Copyright 2012 Serval Project, Inc. +# Copyright 2012-2014 Serval Project, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -107,6 +107,23 @@ test_AddManifestFieldUnsupported() { assert_manifest_fields file1.manifest bogus=one } +doc_AddManifestFieldUnsupportedArgs="Add with unsupported manifest field argument" +setup_AddManifestFieldUnsupportedArgs() { + setup_servald + setup_rhizome + executeOk_servald rhizome list + assert_rhizome_list + echo "A test file" >file1 + echo "Another test file" >file2 +} +test_AddManifestFieldUnsupportedArgs() { + executeOk_servald rhizome add file $SIDB1 file1 file1.manifest '' bogus=two + assert_stdout_add_file file1 + tfw_cat -v file1.manifest + assert_manifest_complete file1.manifest + assert_manifest_fields file1.manifest bogus=two +} + doc_AddNoAuthor="Add with no author makes manifest without BK" setup_AddNoAuthor() { setup_servald @@ -190,6 +207,22 @@ test_AddManifest() { assert_manifest_fields file1.manifest service=file name=wah date=12345 } +doc_AddManifestArgs="Add with minimal manifest from arguments" +setup_AddManifestArgs() { + setup_servald + setup_rhizome + executeOk_servald rhizome list + assert_rhizome_list + echo "A test file" >file1 +} +test_AddManifestArgs() { + executeOk_servald rhizome add file $SIDB1 file1 file1.manifest '' name=wah date=12345 + tfw_cat --stdout --stderr -v file1.manifest + assert_stdout_add_file file1 name=wah + assert_manifest_complete file1.manifest + assert_manifest_fields file1.manifest service=file name=wah date=12345 +} + doc_AddEmpty="Add with empty payload" setup_AddEmpty() { setup_servald @@ -389,7 +422,7 @@ test_LargePayload() { assert diff file1 file1x } -doc_CorruptExternalBlob="A corrupted payload should fail to export" +doc_CorruptExternalBlob="Corrupted payload fails to export" setup_CorruptExternalBlob() { setup_servald setup_rhizome @@ -775,6 +808,25 @@ test_AddUpdateAutoVersion() { assert_rhizome_list --fromhere=1 file1_2 file2 } +doc_AddUpdateArgs="Update existing bundle using manifest args" +setup_AddUpdateArgs() { + setup_AddDeDuplicate + extract_manifest_id BID file1.manifest + cp file1.manifest file1_2.manifest +} +test_AddUpdateArgs() { + sleep 0.001 # Ensure that at least one millisecond has elapsed + executeOk_servald rhizome add file $SIDB1 file1_2 file1_2.manifest '' !version !filesize !filehash !date + assert_stdout_add_file file1_2 name=file1 + assert_manifest_fields file1_2.manifest name=file1 + extract_manifest_id BID2 file1_2.manifest + assert [ $BID = $BID2 ] + assert_manifest_newer file1.manifest file1_2.manifest + # Rhizome store contents reflect new payload. + executeOk_servald rhizome list + assert_rhizome_list --fromhere=1 file1_2 file2 +} + doc_AddServiceInvalid="Add with invalid service fails" setup_AddServiceInvalid() { setup_servald