| 1 | #!/usr/bin/env bash |
| 2 | |
| 3 | test_description="Test http proxy over p2p" |
| 4 | |
| 5 | . lib/test-lib.sh |
| 6 | |
| 7 | if ! test_have_prereq SOCAT; then |
| 8 | skip_all="skipping '$test_description': socat is not available" |
| 9 | test_done |
| 10 | fi |
| 11 | |
| 12 | WEB_SERVE_PORT=5099 |
| 13 | IPFS_GATEWAY_PORT=5199 |
| 14 | SENDER_GATEWAY="http://127.0.0.1:$IPFS_GATEWAY_PORT" |
| 15 | |
| 16 | function show_logs() { |
| 17 | |
| 18 | echo "*****************" |
| 19 | echo " RECEIVER LOG " |
| 20 | echo "*****************" |
| 21 | iptb logs 1 |
| 22 | echo "*****************" |
| 23 | echo " SENDER LOG " |
| 24 | echo "*****************" |
| 25 | iptb logs 0 |
| 26 | echo "*****************" |
| 27 | echo "REMOTE_SERVER LOG" |
| 28 | echo $REMOTE_SERVER_LOG |
| 29 | echo "*****************" |
| 30 | cat $REMOTE_SERVER_LOG |
| 31 | } |
| 32 | |
| 33 | function start_http_server() { |
| 34 | REMOTE_SERVER_LOG="server.log" |
| 35 | rm -f $REMOTE_SERVER_LOG |
| 36 | |
| 37 | touch response |
| 38 | socat tcp-listen:$WEB_SERVE_PORT,fork,bind=127.0.0.1,reuseaddr 'SYSTEM:cat response'!!CREATE:$REMOTE_SERVER_LOG & |
| 39 | REMOTE_SERVER_PID=$! |
| 40 | |
| 41 | socat /dev/null tcp:127.0.01:$WEB_SERVE_PORT,retry=10 |
| 42 | return $? |
| 43 | } |
| 44 | |
| 45 | function teardown_remote_server() { |
| 46 | exec 7<&- |
| 47 | kill $REMOTE_SERVER_PID > /dev/null 2>&1 |
| 48 | wait $REMOTE_SERVER_PID || true |
| 49 | } |
| 50 | |
| 51 | function serve_content() { |
| 52 | local body=$1 |
| 53 | local status_code=${2:-"200 OK"} |
| 54 | local length=$((1 + ${#body})) |
| 55 | echo -e "HTTP/1.1 $status_code\nContent-length: $length\n\n$body" > response |
| 56 | } |
| 57 | |
| 58 | function curl_check_response_code() { |
| 59 | local expected_status_code=$1 |
| 60 | local path_stub=${2:-p2p/$RECEIVER_ID/http/index.txt} |
| 61 | local status_code=$(curl -s --write-out %{http_code} --output /dev/null $SENDER_GATEWAY/$path_stub) |
| 62 | |
| 63 | if [[ "$status_code" -ne "$expected_status_code" ]]; |
| 64 | then |
| 65 | echo "Found status-code "$status_code", expected "$expected_status_code |
| 66 | return 1 |
| 67 | fi |
| 68 | |
| 69 | return 0 |
| 70 | } |
| 71 | |
| 72 | function curl_send_proxy_request_and_check_response() { |
| 73 | local expected_status_code=$1 |
| 74 | local expected_content=$2 |
| 75 | |
| 76 | # |
| 77 | # make a request to SENDER_IPFS via the proxy endpoint |
| 78 | # |
| 79 | CONTENT_PATH="retrieved-file" |
| 80 | STATUS_CODE="$(curl -s -o $CONTENT_PATH --write-out %{http_code} $SENDER_GATEWAY/p2p/$RECEIVER_ID/http/index.txt)" |
| 81 | |
| 82 | # |
| 83 | # check status code |
| 84 | # |
| 85 | if [[ "$STATUS_CODE" -ne "$expected_status_code" ]]; |
| 86 | then |
| 87 | echo -e "Found status-code "$STATUS_CODE", expected "$expected_status_code |
| 88 | show_logs |
| 89 | return 1 |
| 90 | fi |
| 91 | |
| 92 | # |
| 93 | # check content |
| 94 | # |
| 95 | RESPONSE_CONTENT="$(tail -n 1 $CONTENT_PATH)" |
| 96 | if [[ "$RESPONSE_CONTENT" == "$expected_content" ]]; |
| 97 | then |
| 98 | return 0 |
| 99 | else |
| 100 | echo -e "Found response content:\n'"$RESPONSE_CONTENT"'\nthat differs from expected content:\n'"$expected_content"'" |
| 101 | return 1 |
| 102 | fi |
| 103 | } |
| 104 | |
| 105 | function curl_send_multipart_form_request() { |
| 106 | local expected_status_code=$1 |
| 107 | local FILE_PATH="uploaded-file" |
| 108 | FILE_CONTENT="curl will send a multipart-form POST request when sending a file which is handy" |
| 109 | echo $FILE_CONTENT > $FILE_PATH |
| 110 | # |
| 111 | # send multipart form request |
| 112 | # |
| 113 | STATUS_CODE="$(curl -o /dev/null -s -F file=@$FILE_PATH --write-out %{http_code} $SENDER_GATEWAY/p2p/$RECEIVER_ID/http/index.txt)" |
| 114 | # |
| 115 | # check status code |
| 116 | # |
| 117 | if [[ "$STATUS_CODE" -ne "$expected_status_code" ]]; |
| 118 | then |
| 119 | echo -e "Found status-code "$STATUS_CODE", expected "$expected_status_code |
| 120 | return 1 |
| 121 | fi |
| 122 | # |
| 123 | # check request method |
| 124 | # |
| 125 | if ! grep "POST /index.txt" $REMOTE_SERVER_LOG > /dev/null; |
| 126 | then |
| 127 | echo "Remote server request method/resource path was incorrect" |
| 128 | show_logs |
| 129 | return 1 |
| 130 | fi |
| 131 | # |
| 132 | # check request is multipart-form |
| 133 | # |
| 134 | if ! grep "Content-Type: multipart/form-data;" $REMOTE_SERVER_LOG > /dev/null; |
| 135 | then |
| 136 | echo "Request content-type was not multipart/form-data" |
| 137 | show_logs |
| 138 | return 1 |
| 139 | fi |
| 140 | return 0 |
| 141 | } |
| 142 | |
| 143 | test_expect_success 'configure nodes' ' |
| 144 | iptb testbed create -type localipfs -count 2 -force -init && |
| 145 | iptb run -- ipfs config --json "Routing.LoopbackAddressesOnLanDHT" true && |
| 146 | ipfsi 0 config --json Experimental.Libp2pStreamMounting true && |
| 147 | ipfsi 1 config --json Experimental.Libp2pStreamMounting true && |
| 148 | ipfsi 0 config --json Experimental.P2pHttpProxy true && |
| 149 | ipfsi 0 config --json Addresses.Gateway "[\"/ip4/127.0.0.1/tcp/$IPFS_GATEWAY_PORT\"]" |
| 150 | ' |
| 151 | |
| 152 | test_expect_success 'configure a subdomain gateway with /p2p/ path whitelisted' " |
| 153 | ipfsi 0 config --json Gateway.PublicGateways '{ |
| 154 | \"example.com\": { |
| 155 | \"UseSubdomains\": true, |
| 156 | \"Paths\": [\"/p2p/\"] |
| 157 | } |
| 158 | }' |
| 159 | " |
| 160 | |
| 161 | test_expect_success 'start and connect nodes' ' |
| 162 | iptb start -wait && iptb connect 0 1 |
| 163 | ' |
| 164 | |
| 165 | test_expect_success 'setup p2p listener on the receiver' ' |
| 166 | ipfsi 1 p2p listen --allow-custom-protocol /http /ip4/127.0.0.1/tcp/$WEB_SERVE_PORT && |
| 167 | ipfsi 1 p2p listen /x/custom/http /ip4/127.0.0.1/tcp/$WEB_SERVE_PORT |
| 168 | ' |
| 169 | |
| 170 | test_expect_success 'setup environment' ' |
| 171 | RECEIVER_ID=$(ipfsi 1 id -f="<id>" --peerid-base=b58mh) |
| 172 | RECEIVER_ID_CIDv1=$(ipfsi 1 id -f="<id>" --peerid-base=base36) |
| 173 | ' |
| 174 | |
| 175 | test_expect_success 'handle proxy http request sends bad-gateway when remote server not available ' ' |
| 176 | curl_send_proxy_request_and_check_response 502 "" |
| 177 | ' |
| 178 | |
| 179 | test_expect_success 'start http server' ' |
| 180 | start_http_server |
| 181 | ' |
| 182 | |
| 183 | test_expect_success 'handle proxy http request propagates error response from remote' ' |
| 184 | serve_content "SORRY GUYS, I LOST IT" "404 Not Found" && |
| 185 | curl_send_proxy_request_and_check_response 404 "SORRY GUYS, I LOST IT" |
| 186 | ' |
| 187 | |
| 188 | test_expect_success 'handle proxy http request ' ' |
| 189 | serve_content "THE WOODS ARE LOVELY DARK AND DEEP" && |
| 190 | curl_send_proxy_request_and_check_response 200 "THE WOODS ARE LOVELY DARK AND DEEP" |
| 191 | ' |
| 192 | |
| 193 | test_expect_success 'handle proxy http request invalid request' ' |
| 194 | curl_check_response_code 400 p2p/DERPDERPDERP |
| 195 | ' |
| 196 | |
| 197 | test_expect_success 'handle proxy http request unknown proxy peer ' ' |
| 198 | UNKNOWN_PEER="k51qzi5uqu5dlmbel1sd8rs4emr3bfosk9bm4eb42514r4lakt4oxw3a3fa2tm" && |
| 199 | curl_check_response_code 502 p2p/$UNKNOWN_PEER/http/index.txt |
| 200 | ' |
| 201 | |
| 202 | test_expect_success 'handle proxy http request to invalid proxy peer ' ' |
| 203 | curl_check_response_code 400 p2p/invalid_peer/http/index.txt |
| 204 | ' |
| 205 | |
| 206 | test_expect_success 'handle proxy http request to custom protocol' ' |
| 207 | serve_content "THE WOODS ARE LOVELY DARK AND DEEP" && |
| 208 | curl_check_response_code 200 p2p/$RECEIVER_ID/x/custom/http/index.txt |
| 209 | ' |
| 210 | |
| 211 | test_expect_success 'handle proxy http request to missing protocol' ' |
| 212 | serve_content "THE WOODS ARE LOVELY DARK AND DEEP" && |
| 213 | curl_check_response_code 502 p2p/$RECEIVER_ID/x/missing/http/index.txt |
| 214 | ' |
| 215 | |
| 216 | test_expect_success 'handle proxy http request missing the /http' ' |
| 217 | curl_check_response_code 400 p2p/$RECEIVER_ID/x/custom/index.txt |
| 218 | ' |
| 219 | |
| 220 | test_expect_success 'handle multipart/form-data http request' ' |
| 221 | serve_content "OK" && |
| 222 | curl_send_multipart_form_request 200 |
| 223 | ' |
| 224 | |
| 225 | # OK: $peerid.p2p.example.com/http/index.txt |
| 226 | test_expect_success "handle http request to a subdomain gateway" ' |
| 227 | serve_content "SUBDOMAIN PROVIDES ORIGIN ISOLATION PER RECEIVER_ID" && |
| 228 | curl -H "Host: $RECEIVER_ID_CIDv1.p2p.example.com" -sD - $SENDER_GATEWAY/http/index.txt > p2p_response && |
| 229 | test_should_contain "SUBDOMAIN PROVIDES ORIGIN ISOLATION PER RECEIVER_ID" p2p_response |
| 230 | ' |
| 231 | |
| 232 | # FAIL: $peerid.p2p.example.com/p2p/$peerid/http/index.txt |
| 233 | test_expect_success "handle invalid http request to a subdomain gateway" ' |
| 234 | serve_content "SUBDOMAIN DOES NOT SUPPORT FULL /p2p/ PATH" && |
| 235 | curl -H "Host: $RECEIVER_ID_CIDv1.p2p.example.com" -sD - $SENDER_GATEWAY/p2p/$RECEIVER_ID/http/index.txt > p2p_response && |
| 236 | test_should_contain "400 Bad Request" p2p_response |
| 237 | ' |
| 238 | |
| 239 | # REDIRECT: example.com/p2p/$peerid/http/index.txt → $peerid.p2p.example.com/http/index.txt |
| 240 | test_expect_success "redirect http path request to subdomain gateway" ' |
| 241 | serve_content "SUBDOMAIN ROOT REDIRECTS /p2p/ PATH TO SUBDOMAIN" && |
| 242 | curl -H "Host: example.com" -sD - $SENDER_GATEWAY/p2p/$RECEIVER_ID/http/index.txt > p2p_response && |
| 243 | test_should_contain "Location: http://$RECEIVER_ID_CIDv1.p2p.example.com/http/index.txt" p2p_response |
| 244 | ' |
| 245 | |
| 246 | test_expect_success 'stop http server' ' |
| 247 | teardown_remote_server |
| 248 | ' |
| 249 | |
| 250 | test_expect_success 'stop nodes' ' |
| 251 | iptb stop |
| 252 | ' |
| 253 | |
| 254 | test_done |