|
10 | 10 | from multiaddr.multiaddr import Multiaddr |
11 | 11 | from multiaddr.protocols import ( |
12 | 12 | P_DNS, |
| 13 | + P_HTTP_PATH, |
13 | 14 | P_IP4, |
14 | 15 | P_IP6, |
15 | 16 | P_P2P, |
@@ -825,3 +826,158 @@ def test_memory_protocol_properties(): |
825 | 826 | assert proto.code == 777 |
826 | 827 | assert proto.name == "memory" |
827 | 828 | assert proto.codec == "memory" |
| 829 | + |
| 830 | + |
| 831 | +def test_http_path_multiaddr_roundtrip(): |
| 832 | + """Test basic http-path in multiaddr string roundtrip""" |
| 833 | + test_cases = [ |
| 834 | + "/http-path/foo", |
| 835 | + "/http-path/foo%2Fbar", # URL-encoded forward slashes |
| 836 | + "/http-path/api%2Fv1%2Fusers", # URL-encoded forward slashes |
| 837 | + ] |
| 838 | + |
| 839 | + for addr_str in test_cases: |
| 840 | + m = Multiaddr(addr_str) |
| 841 | + assert str(m) == addr_str |
| 842 | + # Verify protocol value extraction |
| 843 | + path_value = m.value_for_protocol(P_HTTP_PATH) |
| 844 | + expected_path = addr_str.replace("/http-path/", "") |
| 845 | + assert path_value == expected_path |
| 846 | + |
| 847 | + |
| 848 | +def test_http_path_url_encoding(): |
| 849 | + """Test special characters and URL encoding behavior""" |
| 850 | + test_cases = [ |
| 851 | + ("/foo%20bar", "/foo%20bar"), # Already URL-encoded input |
| 852 | + ( |
| 853 | + "/path%2Fwith%2Fspecial%21%40%23", |
| 854 | + "/path%2Fwith%2Fspecial%21%40%23", |
| 855 | + ), # Already URL-encoded input |
| 856 | + ( |
| 857 | + "/%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF", |
| 858 | + "/%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF", |
| 859 | + ), # Already URL-encoded input |
| 860 | + ("/tmp%2Fbar", "/tmp%2Fbar"), # Already URL-encoded input |
| 861 | + ] |
| 862 | + |
| 863 | + for input_path, expected_encoded in test_cases: |
| 864 | + addr_str = f"/http-path{input_path}" |
| 865 | + m = Multiaddr(addr_str) |
| 866 | + # The string representation should show URL-encoded path |
| 867 | + assert str(m) == f"/http-path{expected_encoded}" |
| 868 | + |
| 869 | + |
| 870 | +def test_http_path_in_complex_multiaddr(): |
| 871 | + """Test http-path as part of larger multiaddr chains""" |
| 872 | + test_cases = [ |
| 873 | + ("/ip4/127.0.0.1/tcp/443/tls/http/http-path/api%2Fv1", "api%2Fv1"), |
| 874 | + ("/ip4/127.0.0.1/tcp/80/http/http-path/static%2Fcss", "static%2Fcss"), |
| 875 | + ("/dns/example.com/tcp/443/tls/http/http-path/docs", "docs"), |
| 876 | + ] |
| 877 | + |
| 878 | + for addr_str, expected_path in test_cases: |
| 879 | + m = Multiaddr(addr_str) |
| 880 | + assert str(m) == addr_str |
| 881 | + |
| 882 | + # Extract the http-path value |
| 883 | + path_value = m.value_for_protocol(P_HTTP_PATH) |
| 884 | + assert path_value == expected_path |
| 885 | + |
| 886 | + |
| 887 | +def test_http_path_error_cases(): |
| 888 | + """Test error handling for invalid http-path values""" |
| 889 | + |
| 890 | + # Empty path should raise error |
| 891 | + with pytest.raises(StringParseError): |
| 892 | + Multiaddr("/http-path/") |
| 893 | + |
| 894 | + # Missing path value should raise error |
| 895 | + with pytest.raises(StringParseError): |
| 896 | + Multiaddr("/http-path") |
| 897 | + |
| 898 | + # Invalid URL encoding should raise error |
| 899 | + with pytest.raises(StringParseError): |
| 900 | + Multiaddr("/http-path/invalid%zz") |
| 901 | + |
| 902 | + |
| 903 | +def test_http_path_value_extraction(): |
| 904 | + """Test extracting http-path values from multiaddr""" |
| 905 | + test_cases = [ |
| 906 | + ("/http-path/foo", "foo"), |
| 907 | + ("/http-path/foo%2Fbar", "foo%2Fbar"), |
| 908 | + ("/http-path/api%2Fv1%2Fusers", "api%2Fv1%2Fusers"), |
| 909 | + ("/ip4/127.0.0.1/tcp/80/http/http-path/docs", "docs"), |
| 910 | + ] |
| 911 | + |
| 912 | + for addr_str, expected_path in test_cases: |
| 913 | + m = Multiaddr(addr_str) |
| 914 | + path_value = m.value_for_protocol(P_HTTP_PATH) |
| 915 | + assert path_value == expected_path |
| 916 | + |
| 917 | + |
| 918 | +def test_http_path_edge_cases(): |
| 919 | + """Test edge cases and special character handling""" |
| 920 | + |
| 921 | + # Test with various special characters (URL-encoded input) |
| 922 | + special_paths = [ |
| 923 | + "path%20with%20spaces", |
| 924 | + "path%2Fwith%2Fmultiple%2Fslashes", |
| 925 | + "path%2Fwith%2Funicode%2F%E6%B5%8B%E8%AF%95", |
| 926 | + "path%2Fwith%2Fsymbols%21%40%23%24%25%5E%26%2A%28%29", |
| 927 | + ] |
| 928 | + |
| 929 | + for path in special_paths: |
| 930 | + addr_str = f"/http-path/{path}" |
| 931 | + m = Multiaddr(addr_str) |
| 932 | + # Should handle encoding properly |
| 933 | + assert m.value_for_protocol(P_HTTP_PATH) == path |
| 934 | + |
| 935 | + |
| 936 | +def test_http_path_only_reads_http_path_part(): |
| 937 | + """Test that http-path only reads its own part, not subsequent protocols""" |
| 938 | + # This test verifies that when we have /http-path/tmp%2Fbar/p2p-circuit, |
| 939 | + # the ValueForProtocol only returns the http-path part (tmp%2Fbar) |
| 940 | + # and doesn't include the /p2p-circuit part |
| 941 | + addr_str = "/http-path/tmp%2Fbar/p2p-circuit" |
| 942 | + m = Multiaddr(addr_str) |
| 943 | + |
| 944 | + # Should only return the http-path part, not the p2p-circuit part |
| 945 | + http_path_value = m.value_for_protocol(P_HTTP_PATH) |
| 946 | + assert http_path_value == "tmp%2Fbar" |
| 947 | + |
| 948 | + # The full string should still include both parts |
| 949 | + assert str(m) == addr_str |
| 950 | + |
| 951 | + |
| 952 | +def test_http_path_malformed_percent_escape(): |
| 953 | + """Test that malformed percent-escapes are properly rejected""" |
| 954 | + # This tests the specific case from Go: /http-path/thisIsMissingAfullByte%f |
| 955 | + # The %f is an incomplete percent-escape and should be rejected |
| 956 | + bad_addr = "/http-path/thisIsMissingAfullByte%f" |
| 957 | + |
| 958 | + with pytest.raises(StringParseError, match="Invalid percent-escape"): |
| 959 | + Multiaddr(bad_addr) |
| 960 | + |
| 961 | + |
| 962 | +def test_http_path_raw_value_access(): |
| 963 | + """Test accessing raw unescaped values from http-path components""" |
| 964 | + # This test demonstrates how to get the raw unescaped value |
| 965 | + # similar to Go's SplitLast and RawValue functionality |
| 966 | + addr_str = "/http-path/tmp%2Fbar" |
| 967 | + m = Multiaddr(addr_str) |
| 968 | + |
| 969 | + # Get the URL-encoded value (what ValueForProtocol returns) |
| 970 | + encoded_value = m.value_for_protocol(P_HTTP_PATH) |
| 971 | + assert encoded_value == "tmp%2Fbar" |
| 972 | + |
| 973 | + # Get the raw unescaped value by accessing the component directly |
| 974 | + # This is similar to Go's component.RawValue() |
| 975 | + from urllib.parse import unquote |
| 976 | + |
| 977 | + raw_value = unquote(encoded_value) |
| 978 | + assert raw_value == "tmp/bar" |
| 979 | + |
| 980 | + # Verify the roundtrip |
| 981 | + from urllib.parse import quote |
| 982 | + |
| 983 | + assert quote(raw_value, safe="") == encoded_value |
0 commit comments