| 1 | #!/usr/bin/env bash |
| 2 | # |
| 3 | # Copyright (c) 2015 Henry Bubert |
| 4 | # MIT Licensed; see the LICENSE file in this repository. |
| 5 | # |
| 6 | |
| 7 | test_description="Test object command" |
| 8 | |
| 9 | . lib/test-lib.sh |
| 10 | |
| 11 | test_init_ipfs |
| 12 | |
| 13 | test_patch_create_path() { |
| 14 | root=$1 |
| 15 | name=$2 |
| 16 | target=$3 |
| 17 | |
| 18 | test_expect_success "object patch --create works" ' |
| 19 | PCOUT=$(ipfs object patch $root add-link --create $name $target) |
| 20 | ' |
| 21 | |
| 22 | test_expect_success "output looks good" ' |
| 23 | ipfs cat "$PCOUT/$name" >tpcp_out && |
| 24 | ipfs cat "$target" >tpcp_exp && |
| 25 | test_cmp tpcp_exp tpcp_out |
| 26 | ' |
| 27 | } |
| 28 | |
| 29 | test_object_cmd() { |
| 30 | # Bare dag-pb node with no UnixFS metadata (0 bytes of protobuf data) |
| 31 | EMPTY_DIR=$(echo '{"Links":[]}' | ipfs dag put --store-codec dag-pb) |
| 32 | # Empty UnixFS directory (equivalent to QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn) |
| 33 | EMPTY_UNIXFS_DIR=$(echo '{"Data":{"/":{"bytes":"CAE"}},"Links":[]}' | ipfs dag put --store-codec dag-pb) |
| 34 | # Empty UnixFS file (QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH) |
| 35 | EMPTY_UNIXFS_FILE=$(echo -n | ipfs add -q) |
| 36 | # Empty HAMTShard (Type=HAMTShard, HashType=0x22, Fanout=256) |
| 37 | EMPTY_HAMT=$(echo '{"Data":{"/":{"bytes":"CAUoIjCAAg"}},"Links":[]}' | ipfs dag put --store-codec dag-pb) |
| 38 | |
| 39 | # --- UnixFS validation for 'object patch add-link' --- |
| 40 | # 'object patch' operates at the dag-pb level via dagutils.Editor, which |
| 41 | # only manipulates ProtoNode links without updating UnixFS metadata. |
| 42 | # Only plain UnixFS Directory nodes are safe to mutate this way. |
| 43 | # https://specs.ipfs.tech/unixfs/#pbnode-links-name |
| 44 | # https://github.com/ipfs/kubo/issues/7190 |
| 45 | # |
| 46 | # Four root node types tested below: |
| 47 | # 1) bare dag-pb (no UnixFS data) -- rejected |
| 48 | # 2) UnixFS File -- rejected (prevents data loss) |
| 49 | # 3) HAMTShard -- rejected (corrupts HAMT bitfield) |
| 50 | # 4) UnixFS Directory -- allowed |
| 51 | |
| 52 | # Reproduce https://github.com/ipfs/kubo/issues/7190: |
| 53 | # adding a named link to a File node must be rejected to prevent data loss. |
| 54 | test_expect_success "'ipfs object patch add-link' prevents data loss on File nodes (#7190)" ' |
| 55 | echo "original content" > original.txt && |
| 56 | ORIGINAL_CID=$(ipfs add -q original.txt) && |
| 57 | CHILD_CID=$(echo "child" | ipfs add -q) && |
| 58 | test_expect_code 1 ipfs object patch $ORIGINAL_CID add-link "child.txt" $CHILD_CID 2>patch_7190_err && |
| 59 | echo "Error: cannot add named links to a UnixFS File node, only Directory nodes support link addition at the dag-pb level (see https://specs.ipfs.tech/unixfs/)" >patch_7190_expected && |
| 60 | test_cmp patch_7190_expected patch_7190_err && |
| 61 | # verify the original file is still intact |
| 62 | ipfs cat $ORIGINAL_CID > original_readback.txt && |
| 63 | test_cmp original.txt original_readback.txt |
| 64 | ' |
| 65 | |
| 66 | # 1) Bare dag-pb (no UnixFS data): rejected by default |
| 67 | test_expect_success "'ipfs object patch add-link' rejects non-UnixFS dag-pb nodes" ' |
| 68 | test_expect_code 1 ipfs object patch $EMPTY_DIR add-link foo $EMPTY_UNIXFS_DIR 2>patch_dagpb_err |
| 69 | ' |
| 70 | |
| 71 | test_expect_success "add-link error for non-UnixFS dag-pb has expected message" ' |
| 72 | echo "Error: cannot add named links to a non-UnixFS dag-pb node; pass --allow-non-unixfs to skip validation" >patch_dagpb_expected && |
| 73 | test_cmp patch_dagpb_expected patch_dagpb_err |
| 74 | ' |
| 75 | |
| 76 | test_expect_success "'ipfs object patch add-link --allow-non-unixfs' works on dag-pb nodes" ' |
| 77 | OUTPUT=$(ipfs object patch $EMPTY_DIR add-link --allow-non-unixfs foo $EMPTY_UNIXFS_DIR) && |
| 78 | ipfs dag stat $OUTPUT |
| 79 | ' |
| 80 | |
| 81 | # 2) UnixFS File (QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH): rejected by default |
| 82 | test_expect_success "'ipfs object patch add-link' rejects UnixFS File nodes" ' |
| 83 | test_expect_code 1 ipfs object patch $EMPTY_UNIXFS_FILE add-link foo $EMPTY_UNIXFS_DIR 2>patch_file_err |
| 84 | ' |
| 85 | |
| 86 | test_expect_success "add-link error for UnixFS File has expected message" ' |
| 87 | echo "Error: cannot add named links to a UnixFS File node, only Directory nodes support link addition at the dag-pb level (see https://specs.ipfs.tech/unixfs/)" >patch_file_expected && |
| 88 | test_cmp patch_file_expected patch_file_err |
| 89 | ' |
| 90 | |
| 91 | test_expect_success "'ipfs object patch add-link --allow-non-unixfs' bypasses check on File nodes" ' |
| 92 | ipfs object patch $EMPTY_UNIXFS_FILE add-link --allow-non-unixfs foo $EMPTY_UNIXFS_DIR |
| 93 | ' |
| 94 | |
| 95 | # 3) HAMTShard: rejected (dag-pb level mutation corrupts HAMT bitfield) |
| 96 | test_expect_success "'ipfs object patch add-link' rejects HAMTShard nodes" ' |
| 97 | test_expect_code 1 ipfs object patch $EMPTY_HAMT add-link foo $EMPTY_UNIXFS_DIR 2>patch_hamt_err |
| 98 | ' |
| 99 | |
| 100 | test_expect_success "add-link error for HAMTShard has expected message" ' |
| 101 | echo "Error: cannot add links to a HAMTShard at the dag-pb level (would corrupt the HAMT bitfield); use '"'"'ipfs files'"'"' commands instead, or pass --allow-non-unixfs to override" >patch_hamt_expected && |
| 102 | test_cmp patch_hamt_expected patch_hamt_err |
| 103 | ' |
| 104 | |
| 105 | test_expect_success "'ipfs object patch add-link --allow-non-unixfs' bypasses check on HAMTShard" ' |
| 106 | ipfs object patch $EMPTY_HAMT add-link --allow-non-unixfs foo $EMPTY_UNIXFS_DIR |
| 107 | ' |
| 108 | |
| 109 | # 4) UnixFS Directory (QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn): allowed |
| 110 | test_expect_success "'ipfs object patch add-link' works on UnixFS Directory nodes" ' |
| 111 | OUTPUT=$(ipfs object patch $EMPTY_UNIXFS_DIR add-link foo $EMPTY_UNIXFS_DIR) && |
| 112 | ipfs dag stat $OUTPUT |
| 113 | ' |
| 114 | |
| 115 | test_expect_success "'ipfs object patch' check output block size" ' |
| 116 | DIR=$EMPTY_UNIXFS_DIR |
| 117 | for i in {1..14} |
| 118 | do |
| 119 | DIR=$(ipfs object patch "$DIR" add-link "$DIR.jpg" "$DIR") |
| 120 | done |
| 121 | # Fail when new block goes over the BS limit of 2MiB, but allow manual override |
| 122 | test_expect_code 1 ipfs object patch "$DIR" add-link "$DIR.jpg" "$DIR" >patch_out 2>&1 |
| 123 | ' |
| 124 | |
| 125 | test_expect_success "ipfs object patch add-link output has the correct error" ' |
| 126 | grep "produced block is over 2MiB" patch_out |
| 127 | ' |
| 128 | |
| 129 | test_expect_success "ipfs object patch --allow-big-block=true add-link works" ' |
| 130 | test_expect_code 0 ipfs object patch --allow-big-block=true "$DIR" add-link "$DIR.jpg" "$DIR" |
| 131 | ' |
| 132 | |
| 133 | test_expect_success "'ipfs object patch add-link' should work with paths" ' |
| 134 | N1=$(ipfs object patch $EMPTY_UNIXFS_DIR add-link baz $EMPTY_UNIXFS_DIR) && |
| 135 | N2=$(ipfs object patch $EMPTY_UNIXFS_DIR add-link bar $N1) && |
| 136 | N3=$(ipfs object patch $EMPTY_UNIXFS_DIR add-link foo /ipfs/$N2/bar) && |
| 137 | ipfs dag stat /ipfs/$N3 > /dev/null && |
| 138 | ipfs dag stat $N3/foo > /dev/null && |
| 139 | ipfs dag stat /ipfs/$N3/foo/baz > /dev/null |
| 140 | ' |
| 141 | |
| 142 | test_expect_success "'ipfs object patch add-link' allow linking IPLD objects" ' |
| 143 | OBJ=$(echo "123" | ipfs dag put) && |
| 144 | N1=$(ipfs object patch $EMPTY_UNIXFS_DIR add-link foo $OBJ) && |
| 145 | |
| 146 | ipfs dag stat /ipfs/$N1 > /dev/null && |
| 147 | ipfs resolve /ipfs/$N1/foo > actual && |
| 148 | echo /ipfs/$OBJ > expected && |
| 149 | |
| 150 | test_cmp expected actual |
| 151 | ' |
| 152 | |
| 153 | test_expect_success "object patch creation looks right" ' |
| 154 | echo "bafybeiakusqwohnt7bs75kx6jhmt4oi47l634bmudxfv4qxhpco6xuvgna" > hash_exp && |
| 155 | echo $N3 > hash_actual && |
| 156 | test_cmp hash_exp hash_actual |
| 157 | ' |
| 158 | |
| 159 | test_expect_success "multilayer ipfs patch works" ' |
| 160 | echo "hello world" > hwfile && |
| 161 | FILE=$(ipfs add -q hwfile) && |
| 162 | EMPTY=$EMPTY_UNIXFS_DIR && |
| 163 | ONE=$(ipfs object patch $EMPTY add-link b $EMPTY) && |
| 164 | TWO=$(ipfs object patch $EMPTY add-link a $ONE) && |
| 165 | ipfs object patch $TWO add-link a/b/c $FILE > multi_patch |
| 166 | ' |
| 167 | |
| 168 | test_expect_success "output looks good" ' |
| 169 | ipfs cat $(cat multi_patch)/a/b/c > hwfile_out && |
| 170 | test_cmp hwfile hwfile_out |
| 171 | ' |
| 172 | |
| 173 | test_expect_success "can remove the directory" ' |
| 174 | ipfs object patch $OUTPUT rm-link foo > rmlink_output |
| 175 | ' |
| 176 | |
| 177 | test_expect_success "output should be empty" ' |
| 178 | echo bafybeiczsscdsbs7ffqz55asqdf3smv6klcw3gofszvwlyarci47bgf354 > rmlink_exp && |
| 179 | test_cmp rmlink_exp rmlink_output |
| 180 | ' |
| 181 | |
| 182 | test_expect_success "multilayer rm-link should work" ' |
| 183 | ipfs object patch $(cat multi_patch) rm-link a/b/c > multi_link_rm_out |
| 184 | ' |
| 185 | |
| 186 | test_expect_success "output looks good" ' |
| 187 | echo "bafybeicourxysmtbe5hacxqico4d5hyvh7gqkrwlmqa4ew7zufn3pj3juu" > multi_link_rm_exp && |
| 188 | test_cmp multi_link_rm_exp multi_link_rm_out |
| 189 | ' |
| 190 | |
| 191 | test_patch_create_path $EMPTY a/b/c $FILE |
| 192 | |
| 193 | test_patch_create_path $EMPTY a $FILE |
| 194 | |
| 195 | test_patch_create_path $EMPTY a/b/b/b/b $FILE |
| 196 | |
| 197 | test_expect_success "'ipfs object patch add-link --create' rejects non-UnixFS roots" ' |
| 198 | test_must_fail ipfs object patch $EMPTY_DIR add-link --create a $FILE |
| 199 | ' |
| 200 | |
| 201 | test_expect_success "'ipfs object patch add-link --create --allow-non-unixfs' works on non-UnixFS roots" ' |
| 202 | PCOUT=$(ipfs object patch $EMPTY_DIR add-link --create --allow-non-unixfs a $FILE) && |
| 203 | ipfs cat "$PCOUT/a" >tpcp_out && |
| 204 | ipfs cat "$FILE" >tpcp_exp && |
| 205 | test_cmp tpcp_exp tpcp_out |
| 206 | ' |
| 207 | |
| 208 | test_expect_success "create bad path fails" ' |
| 209 | test_must_fail ipfs object patch $EMPTY add-link --create / $FILE |
| 210 | ' |
| 211 | |
| 212 | # --- UnixFS validation for 'object patch rm-link' --- |
| 213 | # Same rationale as add-link: dagutils.Editor cannot update UnixFS metadata. |
| 214 | |
| 215 | # 1) Bare dag-pb: rejected by default |
| 216 | test_expect_success "'ipfs object patch rm-link' rejects non-UnixFS dag-pb nodes" ' |
| 217 | DAGPB_WITH_LINK=$(ipfs object patch $EMPTY_DIR add-link --allow-non-unixfs foo $EMPTY_UNIXFS_DIR) && |
| 218 | test_expect_code 1 ipfs object patch $DAGPB_WITH_LINK rm-link foo 2>rmlink_dagpb_err |
| 219 | ' |
| 220 | |
| 221 | test_expect_success "rm-link error for non-UnixFS dag-pb has expected message" ' |
| 222 | echo "Error: cannot remove links from a non-UnixFS dag-pb node; pass --allow-non-unixfs to skip validation" >rmlink_dagpb_expected && |
| 223 | test_cmp rmlink_dagpb_expected rmlink_dagpb_err |
| 224 | ' |
| 225 | |
| 226 | test_expect_success "'ipfs object patch rm-link --allow-non-unixfs' works on dag-pb nodes" ' |
| 227 | ipfs object patch $DAGPB_WITH_LINK rm-link --allow-non-unixfs foo |
| 228 | ' |
| 229 | |
| 230 | # 2) UnixFS File: rejected by default |
| 231 | test_expect_success "'ipfs object patch rm-link' rejects UnixFS File nodes" ' |
| 232 | FILE_WITH_LINK=$(ipfs object patch $EMPTY_UNIXFS_FILE add-link --allow-non-unixfs foo $EMPTY_UNIXFS_DIR) && |
| 233 | test_expect_code 1 ipfs object patch $FILE_WITH_LINK rm-link foo 2>rmlink_file_err |
| 234 | ' |
| 235 | |
| 236 | test_expect_success "rm-link error for UnixFS File has expected message" ' |
| 237 | echo "Error: cannot remove links from a UnixFS File node, only Directory nodes support link removal at the dag-pb level (see https://specs.ipfs.tech/unixfs/)" >rmlink_file_expected && |
| 238 | test_cmp rmlink_file_expected rmlink_file_err |
| 239 | ' |
| 240 | |
| 241 | test_expect_success "'ipfs object patch rm-link --allow-non-unixfs' bypasses check on File nodes" ' |
| 242 | ipfs object patch $FILE_WITH_LINK rm-link --allow-non-unixfs foo |
| 243 | ' |
| 244 | |
| 245 | # 3) HAMTShard: rejected by default |
| 246 | test_expect_success "'ipfs object patch rm-link' rejects HAMTShard nodes" ' |
| 247 | HAMT_WITH_LINK=$(ipfs object patch $EMPTY_HAMT add-link --allow-non-unixfs foo $EMPTY_UNIXFS_DIR) && |
| 248 | test_expect_code 1 ipfs object patch $HAMT_WITH_LINK rm-link foo 2>rmlink_hamt_err |
| 249 | ' |
| 250 | |
| 251 | test_expect_success "rm-link error for HAMTShard has expected message" ' |
| 252 | echo "Error: cannot remove links from a HAMTShard at the dag-pb level (would corrupt the HAMT bitfield); use '"'"'ipfs files rm'"'"' instead, or pass --allow-non-unixfs to override" >rmlink_hamt_expected && |
| 253 | test_cmp rmlink_hamt_expected rmlink_hamt_err |
| 254 | ' |
| 255 | |
| 256 | test_expect_success "'ipfs object patch rm-link --allow-non-unixfs' bypasses check on HAMTShard" ' |
| 257 | ipfs object patch $HAMT_WITH_LINK rm-link --allow-non-unixfs foo |
| 258 | ' |
| 259 | |
| 260 | # 4) UnixFS Directory: allowed (already tested above in existing rm-link tests) |
| 261 | } |
| 262 | |
| 263 | # should work offline |
| 264 | test_object_cmd |
| 265 | |
| 266 | # should work online |
| 267 | test_launch_ipfs_daemon |
| 268 | test_object_cmd |
| 269 | test_kill_ipfs_daemon |
| 270 | |
| 271 | test_done |