diff --git a/.github/workflows/build-test-rust.yml b/.github/workflows/build-test-rust.yml new file mode 100644 index 0000000..6da58aa --- /dev/null +++ b/.github/workflows/build-test-rust.yml @@ -0,0 +1,59 @@ +name: BuildTest + +on: + push: + tags: + - 'bt_*' + +jobs: + test-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Check format + run: cargo fmt --check + - name: Build + run: cargo build --release --verbose + - name: Run tests + run: cargo test --verbose + - name: Install WASM32 + run: rustup target add wasm32-unknown-unknown + - name: Check wasm32 + run: cargo check --target wasm32-unknown-unknown -p icann-rdap-client + + build: + name: Release build + needs: [test-build] + if: startsWith(github.ref, 'refs/tags/') + strategy: + matrix: + include: + - target: x86_64-unknown-linux-gnu + OS: ubuntu-latest + - target: aarch64-unknown-linux-gnu + OS: ubuntu-latest + - target: x86_64-apple-darwin + OS: macos-latest + - target: aarch64-apple-darwin + OS: macos-latest + - target: x86_64-pc-windows-msvc + OS: windows-latest + runs-on: ${{ matrix.OS }} + steps: + - name: Checkout + uses: actions/checkout@v1 + - name: Install rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + override: true + target: ${{ matrix.target }} + + - name: Build target + uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --release --target ${{ matrix.target }} + diff --git a/.github/workflows/release-rust.yml b/.github/workflows/release-rust.yml new file mode 100644 index 0000000..291c133 --- /dev/null +++ b/.github/workflows/release-rust.yml @@ -0,0 +1,75 @@ +name: Release + +on: + pull_request: + push: + branches: + - main + tags: + - 'v*.*.*' + +jobs: + test-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Check format + run: cargo fmt --check + - name: Build + run: cargo build --release --verbose + - name: Run tests + run: cargo test --verbose + - name: Install WASM32 + run: rustup target add wasm32-unknown-unknown + - name: Check wasm32 + run: cargo check --target wasm32-unknown-unknown -p icann-rdap-client + + build: + name: Release build + needs: [test-build] + if: startsWith(github.ref, 'refs/tags/') + strategy: + matrix: + include: + - target: x86_64-unknown-linux-gnu + OS: ubuntu-latest + - target: aarch64-unknown-linux-gnu + OS: ubuntu-latest + - target: x86_64-apple-darwin + OS: macos-latest + - target: aarch64-apple-darwin + OS: macos-latest + - target: x86_64-pc-windows-msvc + OS: windows-latest + runs-on: ${{ matrix.OS }} + steps: + - name: Checkout + uses: actions/checkout@v1 + - name: Install rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + override: true + target: ${{ matrix.target }} + + - name: Build target + uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --release --target ${{ matrix.target }} + + - name: Package + shell: bash + run: | + cd target/${{ matrix.target }}/release + tar czvf ../../../icann-rdap-${{ matrix.target }}.tar.gz rdap rdap-test rdap-srv rdap-srv-data rdap-srv-store rdap-srv-test-data + cd - + - name: Publish + uses: softprops/action-gh-release@v1 + with: + files: 'icann-rdap-*' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fedaa2b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +.env diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..edad8d8 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4463 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ab-radix-trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1996a0a7d153953579280bca89e620b9bc0a609727984e53f1f4073776746866" +dependencies = [ + "env_logger", + "log", + "rand", + "serde", + "serde_json", +] + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom 0.2.15", + "once_cell", + "version_check", + "zerocopy 0.7.35", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "anyhow" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" + +[[package]] +name = "assert_cmd" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" +dependencies = [ + "anstyle", + "bstr", + "doc-comment", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower 0.5.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-client-ip" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e7c467bdcd2bd982ce5c8742a1a178aba7b03db399fd18f5d5d438f5aa91cb4" +dependencies = [ + "axum", + "forwarded-header-value", + "serde", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-extra" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c794b30c904f0a1c2fb7740f7df7f7972dfaa14ef6f57cb6178dc63e5dca2f04" +dependencies = [ + "axum", + "axum-core", + "bytes", + "fastrand", + "futures-util", + "headers", + "http", + "http-body", + "http-body-util", + "mime", + "multer", + "pin-project-lite", + "serde", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +dependencies = [ + "serde", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +dependencies = [ + "memchr", + "regex-automata 0.4.9", + "serde", +] + +[[package]] +name = "btree-range-map" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1be5c9672446d3800bcbcaabaeba121fe22f1fb25700c4562b22faf76d377c33" +dependencies = [ + "btree-slab", + "cc-traits", + "range-traits", + "slab", +] + +[[package]] +name = "btree-slab" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2b56d3029f075c4fa892428a098425b86cef5c89ae54073137ece416aef13c" +dependencies = [ + "cc-traits", + "slab", + "smallvec", +] + +[[package]] +name = "buildstructor" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3907aac66c65520545ae3cb3c195306e20d5ed5c90bfbb992e061cf12a104d0" +dependencies = [ + "lazy_static", + "proc-macro2", + "quote", + "str_inflector", + "syn 2.0.100", + "thiserror 1.0.69", + "try_match", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" +dependencies = [ + "shlex", +] + +[[package]] +name = "cc-traits" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "060303ef31ef4a522737e1b1ab68c67916f2a787bb2f4f54f383279adba962b5" +dependencies = [ + "slab", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "cidr" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd1b64030216239a2e7c364b13cd96a2097ebf0dfe5025f2dedee14a23f2ab60" + +[[package]] +name = "cidr-utils" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2315f7119b7146d6a883de6acd63ddf96071b5f79d9d98d2adaa84d749f6abf1" +dependencies = [ + "debug-helper", + "num-bigint", + "num-traits", + "once_cell", + "regex", +] + +[[package]] +name = "clap" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const_format" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "coolor" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "691defa50318376447a73ced869862baecfab35f6aabaa91a4cd726b315bfe1a" +dependencies = [ + "crossterm 0.28.1", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crokey" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520e83558f4c008ac06fa6a86e5c1d4357be6f994cce7434463ebcdaadf47bb1" +dependencies = [ + "crokey-proc_macros", + "crossterm 0.28.1", + "once_cell", + "serde", + "strict", +] + +[[package]] +name = "crokey-proc_macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "370956e708a1ce65fe4ac5bb7185791e0ece7485087f17736d54a23a0895049f" +dependencies = [ + "crossterm 0.28.1", + "proc-macro2", + "quote", + "strict", + "syn 1.0.109", +] + +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio 0.8.11", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "mio 1.0.3", + "parking_lot", + "rustix 0.38.44", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" + +[[package]] +name = "debug-helper" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "envmnt" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d73999a2b8871e74c8b8bc23759ee9f3d85011b24fafc91a4b3b5c8cc8185501" +dependencies = [ + "fsio", + "indexmap 1.9.3", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "forwarded-header-value" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9" +dependencies = [ + "nonempty", + "thiserror 1.0.69", +] + +[[package]] +name = "fsio" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4944f16eb6a05b4b2b79986b4786867bb275f52882adea798f17cc2588f25b2" +dependencies = [ + "dunce", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "h2" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.8.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "headers" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hickory-client" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "156579a5cd8d1fc6f0df87cc21b6ee870db978a163a1ba484acd98a4eff5a6de" +dependencies = [ + "cfg-if", + "data-encoding", + "futures-channel", + "futures-util", + "hickory-proto", + "once_cell", + "radix_trie", + "rand", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "hickory-proto" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand", + "thiserror 1.0.69", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls 0.23.25", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icann-rdap-cli" +version = "0.0.22" +dependencies = [ + "anyhow", + "assert_cmd", + "chrono", + "cidr-utils", + "clap", + "const_format", + "directories", + "dotenv", + "hickory-client", + "icann-rdap-client", + "icann-rdap-common", + "icann-rdap-srv", + "minus", + "pct-str", + "prefix-trie", + "reqwest", + "rstest", + "serde", + "serde_json", + "serial_test", + "strum", + "strum_macros", + "termimad", + "test_dir", + "thiserror 1.0.69", + "tokio", + "tracing", + "tracing-subscriber", + "url", +] + +[[package]] +name = "icann-rdap-client" +version = "0.0.22" +dependencies = [ + "buildstructor", + "chrono", + "cidr", + "const_format", + "icann-rdap-common", + "idna", + "ipnet", + "jsonpath-rust", + "jsonpath_lib", + "pct-str", + "regex", + "reqwest", + "rstest", + "serde", + "serde_json", + "strum", + "strum_macros", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "icann-rdap-common" +version = "0.0.22" +dependencies = [ + "buildstructor", + "chrono", + "cidr", + "const_format", + "idna", + "ipnet", + "prefix-trie", + "rstest", + "serde", + "serde_json", + "strum", + "strum_macros", + "thiserror 1.0.69", +] + +[[package]] +name = "icann-rdap-srv" +version = "0.0.22" +dependencies = [ + "ab-radix-trie", + "assert_cmd", + "async-trait", + "axum", + "axum-client-ip", + "axum-extra", + "axum-macros", + "btree-range-map", + "buildstructor", + "chrono", + "cidr", + "clap", + "dotenv", + "envmnt", + "headers", + "http", + "hyper", + "icann-rdap-client", + "icann-rdap-common", + "idna", + "ipnet", + "pct-str", + "prefix-trie", + "regex", + "reqwest", + "rstest", + "serde", + "serde_json", + "sqlx", + "strum", + "strum_macros", + "test_dir", + "thiserror 1.0.69", + "tokio", + "tower 0.4.13", + "tower-http", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +dependencies = [ + "schemars", + "serde", +] + +[[package]] +name = "is-terminal" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "jsonpath-rust" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0268078319393f8430e850ee9d4706aeced256d34cf104d216bb496777137162" +dependencies = [ + "lazy_static", + "once_cell", + "pest", + "pest_derive", + "regex", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonpath_lib" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" +dependencies = [ + "log", + "serde", + "serde_json", +] + +[[package]] +name = "lazy-regex" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60c7310b93682b36b98fa7ea4de998d3463ccbebd94d935d6b48ba5b6ffa7126" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba01db5ef81e17eb10a5e0f2109d1b3a3e29bac3070fdbd7d156bf7dbd206a1" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.100", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" + +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimad" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c5d708226d186590a7b6d4a9780e2bdda5f689e0d58cd17012a298efd745d2" +dependencies = [ + "once_cell", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +dependencies = [ + "adler2", +] + +[[package]] +name = "minus" +version = "5.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093bd0520d2a37943566a73750e6d44094dac75d66a978d1f0d97ffc78686832" +dependencies = [ + "crossbeam-channel", + "crossterm 0.27.0", + "once_cell", + "parking_lot", + "regex", + "textwrap", + "thiserror 1.0.69", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.48.0", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "memchr", + "mime", + "spin", + "version_check", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nonempty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" +dependencies = [ + "parking_lot_core", +] + +[[package]] +name = "openssl" +version = "0.10.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-src" +version = "300.4.2+3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168ce4e058f975fe43e89d9ccf78ca668601887ae736090aacc23ae353c298e2" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pct-str" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77d207ec8d182c2fef45f028b9b9507770df19e89b3e14827ccd95d4a23f6003" +dependencies = [ + "utf8-decode", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +dependencies = [ + "memchr", + "thiserror 2.0.12", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "pest_meta" +version = "2.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy 0.8.24", +] + +[[package]] +name = "predicates" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +dependencies = [ + "anstyle", + "difflib", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" + +[[package]] +name = "predicates-tree" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "prefix-trie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85fe48f29e6e6fcf123d0d03d63028dbe4c4a738023d35d525df4882f4929418" +dependencies = [ + "ipnet", + "num-traits", +] + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "range-traits" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d20581732dd76fa913c7dff1a2412b714afe3573e94d41c34719de73337cc8ab" + +[[package]] +name = "redox_syscall" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 2.2.0", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower 0.5.2", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "windows-registry", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rstest" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de1bb486a691878cd320c2f0d319ba91eeaa2e894066d8b5f8f117c000e9d962" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290ca1a1c8ca7edb7c3283bd44dc35dd54fdec6253a3912e201ba1072018fca8" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", + "unicode-ident", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.9.3", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.103.0", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa4eeac2588ffff23e9d7a7e9b3f971c5fb5b7ebc9452745e0c232c64f83b2f" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.100", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "indexmap 2.8.0", + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serial_test" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" +dependencies = [ + "dashmap", + "futures", + "lazy_static", + "log", + "parking_lot", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio 0.8.11", + "mio 1.0.3", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" +dependencies = [ + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" +dependencies = [ + "ahash", + "atoi", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap 2.8.0", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tracing", + "url", + "webpki-roots", +] + +[[package]] +name = "sqlx-macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" +dependencies = [ + "dotenvy", + "either", + "heck 0.4.1", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" +dependencies = [ + "atoi", + "base64 0.21.7", + "bitflags", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 1.0.69", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" +dependencies = [ + "atoi", + "base64 0.21.7", + "bitflags", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 1.0.69", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "tracing", + "url", + "urlencoding", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "str_inflector" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0b848d5a7695b33ad1be00f84a3c079fe85c9278a325ff9159e6c99cef4ef7" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "strict" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f42444fea5b87a39db4218d9422087e66a85d0e7a0963a439b07bcdf91804006" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +dependencies = [ + "fastrand", + "getrandom 0.3.2", + "once_cell", + "rustix 1.0.3", + "windows-sys 0.59.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "termimad" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8e19c6dbf107bec01d0e216bb8219485795b7d75328e4fa5ef2756c1be4f8dc" +dependencies = [ + "coolor", + "crokey", + "crossbeam", + "lazy-regex", + "minimad", + "serde", + "thiserror 1.0.69", + "unicode-width 0.1.14", +] + +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + +[[package]] +name = "test_dir" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc19daf9fc57fadcf740c4abaaa0cd08d9ce22a2a0629aaf6cbd9ae4b80683a" +dependencies = [ + "rand", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "unicode-width 0.2.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.44.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio 1.0.3", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls 0.23.25", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +dependencies = [ + "bitflags", + "bytes", + "http", + "http-body", + "http-body-util", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "try_match" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b065c869a3f832418e279aa4c1d7088f9d5d323bde15a60a08e20c2cd4549082" +dependencies = [ + "try_match_inner", +] + +[[package]] +name = "try_match_inner" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9c81686f7ab4065ccac3df7a910c4249f8c0f3fb70421d6ddec19b9311f63f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8-decode" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca61eb27fa339aa08826a29f03e87b99b4d8f0fc2255306fd266bb1b6a9de498" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.100", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "whoami" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +dependencies = [ + "redox_syscall", + "wasite", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.53.0", +] + +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +dependencies = [ + "zerocopy-derive 0.8.24", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9ad4de8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,155 @@ +[workspace] +members = [ + "icann-rdap-cli", + "icann-rdap-client", + "icann-rdap-common", + "icann-rdap-srv" +] +resolver = "2" + +[workspace.package] +version = "0.0.22" +edition = "2021" +license = "MIT OR Apache-2.0" +repository = "https://github.com/icann/icann-rdap" +keywords = ["whois", "rdap"] + +[workspace.dependencies] + +# for suffix string searchs +ab-radix-trie = "0.2.1" + +# easy error handling +anyhow = "1.0" + +# async traits +async-trait = "0.1" + +# axum (web server) +axum = { version = "0.7" } +axum-extra = { version = "0.9", features = [ "typed-header" ] } +axum-macros = "0.4" + +# client IP address extractor +axum-client-ip = "0.5" + +# b-tree with ranges +btree-range-map = "0.7.2" + +# macros for the builder pattern +buildstructor = "0.5" + +# CIDR utilities +cidr = "0.3.0" + +# command line options parser +clap = { version = "4.4", features = [ "std", "derive", "env", "unstable-styles" ] } + +# chrono (time and date library) +chrono = { version = "0.4", features = ["alloc", "std", "clock", "serde"], default-features = false } + +# compile time constants formatting +const_format = "0.2" + +# cross-platform application directories +directories = "5.0" + +# loads environment variables from the a file +dotenv = "0.15.0" + +# environment variable utilities +envmnt = "0.10.4" + +# futures +futures = "0.3" + +# futures-utils +futures-util = "0.3" + +# macros to get the git version +git-version = "0.3" + +# headers (http headers) +headers = "0.4" + +# Hickory DNS client +hickory-client = "0.24" + +# http constructs +http = "1.0" + +# hyper (http implementation used by axum) +hyper = { version = "1.0", features = ["full"] } + +# JSONPath +jsonpath-rust = "=0.5.0" +jsonpath_lib = "0.3.0" + +# internationalized domain names for applications +idna = "1.0" + +# for use prefixmap +ipnet = { version = "2.9", features = ["json"] } + +# embedded pager +minus = {version = "5.5", features = ["dynamic_output", "search"] } + +# percent encoding +pct-str = "1.2" + +# ip address trie +prefix-trie = "0.2.4" + +# regular expresions +regex = "1.10" + +# http client library +reqwest = {version = "0.12", features = ["json", "stream", "native-tls-vendored"]} + +# serialization / deserialization library +serde = { version = "1.0", features = [ "derive" ] } + +# json serializer +serde_json = "1.0" + +# sqlx (async db) +sqlx = { version = "0.7", features = [ + "runtime-tokio-rustls", + "postgres", + "chrono", + "macros", + "json", +] } + +# enum utilities +strum = "0.24" +strum_macros = "0.24" + +# terminal markdown generator +termimad = "0.31" + +# error macros +thiserror = "1.0" + +# tokio async runtime +tokio = { version = "1.35", features = [ "full" ] } + +# tower (tokio/axum middleware) +tower = { version = "0.4", features = ["timeout", "util"] } +tower-http = { version = "0.5", features = [ + "add-extension", + "trace", + "cors", +] } + +# tracing (logging) +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +# url +url = "2.5" + + +[profile.release] +codegen-units = 1 +lto = true diff --git a/Cross.toml b/Cross.toml new file mode 100644 index 0000000..601ecf9 --- /dev/null +++ b/Cross.toml @@ -0,0 +1,35 @@ +[target.x86_64-unknown-linux-gnu] +pre-build = [ + "dpkg --add-architecture $CROSS_DEB_ARCH", + "apt-get update && apt-get install --assume-yes libssl-dev:$CROSS_DEB_ARCH" +] + +[target.x86_64-unknown-linux-musl] +pre-build = [ + "dpkg --add-architecture $CROSS_DEB_ARCH", + "apt-get update && apt-get install --assume-yes libssl-dev:$CROSS_DEB_ARCH" +] + +[target.aarch64-unknown-linux-gnu] +pre-build = [ + "dpkg --add-architecture $CROSS_DEB_ARCH", + "apt-get update && apt-get install --assume-yes libssl-dev:$CROSS_DEB_ARCH" +] + +[target.aarch64-unknown-linux-musl] +pre-build = [ + "dpkg --add-architecture $CROSS_DEB_ARCH", + "apt-get update && apt-get install --assume-yes libssl-dev:$CROSS_DEB_ARCH" +] + +[target.armv7-unknown-linux-gnueabihf] +pre-build = [ + "dpkg --add-architecture $CROSS_DEB_ARCH", + "apt-get update && apt-get install --assume-yes libssl-dev:$CROSS_DEB_ARCH" +] + +[target.armv7-unknown-linux-musleabihf] +pre-build = [ + "dpkg --add-architecture $CROSS_DEB_ARCH", + "apt-get update && apt-get install --assume-yes libssl-dev:$CROSS_DEB_ARCH" +] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..f1be6b7 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014-2016 Ning Sun and tojson_macros contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0cd73cf --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +ICANN RDAP +========== + +This repository contains open source code written by the Internet Corporation for Assigned Names and Numbers [(ICANN)](https://www.icann.org). +for use with the Registry Data Access Protocol (RDAP). RDAP is standard of the [IETF](https://ietf.org/), and extensions +to RDAP are a current work activity of the IETF's [REGEXT working group](https://datatracker.ietf.org/wg/regext/documents/). +More information on ICANN's role in RDAP can be found [here](https://www.icann.org/rdap). +General information on RDAP can be found [here](https://rdap.rcode3.com/). + +About +----- + +This repository hosts 4 separate packages (i.e. Rust crates): + +* [icann-rdap-cli](icann-rdap-cli/README.md) is the Command Line Interface client and testing tool. +* [icann-rdap-client](icann-rdap-client/README.md) is a Rust library handling making RDAP requests. +* [icann-rdap-common](icann-rdap-common/README.md) is a Rust library of RDAP structures. +* [icann-rdap-srv](icann-rdap-srv/README.md) is a simple, in-memory RDAP server. This package produces multiple executable binaries. + +![Example of rdap command](https://github.com/icann/icann-rdap/wiki/images/rdap_command.png) + +Installation and Usage +---------------------- + +See the [project wiki](https://github.com/icann/icann-rdap/wiki) for information on installation +and usage of this software. + +License +------- + +Licensed under either of +* Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) +* MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT) at your option. + +Contribution +------------ + +Unless you explicitly state otherwise, any contribution, as defined in the Apache-2.0 license, +intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, +shall be dual licensed pursuant to the Apache License, Version 2.0 or the MIT License referenced +as above, at ICANN’s option, without any additional terms or conditions. + +How To Contribute +----------------- + +Before working on a Pull Request (PR), seek advice from the maintainers regarding the acceptance +of the PR. To do this, submit an issue outlining the idea for the PR. If the maintainers agree +that the contribution would be welcome, they will assign the issue to you. + +When submitting the PR, submit it against the 'dev' branch (not the 'main' branch). + diff --git a/icann-rdap-cli/Cargo.toml b/icann-rdap-cli/Cargo.toml new file mode 100644 index 0000000..b7572f1 --- /dev/null +++ b/icann-rdap-cli/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "icann-rdap-cli" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +description = """ +An RDAP Command Line Interface client. +""" + +[dependencies] + +icann-rdap-client = { version = "0.0.22", path = "../icann-rdap-client" } +icann-rdap-common = { version = "0.0.22", path = "../icann-rdap-common" } + +anyhow.workspace = true +clap.workspace = true +chrono.workspace = true +const_format.workspace = true +directories.workspace = true +dotenv.workspace = true +hickory-client.workspace = true +minus.workspace = true +pct-str.workspace = true +prefix-trie.workspace = true +reqwest.workspace = true +serde.workspace = true +serde_json.workspace = true +strum.workspace = true +strum_macros.workspace = true +termimad.workspace = true +thiserror.workspace = true +tokio.workspace = true +tracing.workspace = true +tracing-subscriber.workspace = true +url.workspace = true + +[dev-dependencies] + +icann-rdap-srv = { path = "../icann-rdap-srv" } + +# cli assertions +assert_cmd = "2.0.11" + +# CIDR utilities +cidr-utils = "0.5" + +# fixture testings +rstest = "0.17.0" + +# serial testings +serial_test = "2.0.0" + +# test directories +test_dir = "0.2.0" + +# tracing +tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/icann-rdap-cli/README.md b/icann-rdap-cli/README.md new file mode 100644 index 0000000..4a43e92 --- /dev/null +++ b/icann-rdap-cli/README.md @@ -0,0 +1,37 @@ +ICANN RDAP CLI +============== + +This package consists of the following commands: +* The [`rdap`](https://github.com/icann/icann-rdap/wiki/RDAP-command) command is a general-purpose RDAP command line client. +* The [`rdap-test`](https://github.com/icann/icann-rdap/wiki/RDAP-TEST-command) command is a testing tool for RDAP. + +![Example of rdap command](https://github.com/icann/icann-rdap/wiki/images/rdap_command.png) + +This is a command-line interface (CLI) client for the Registration Data Access Protocol (RDAP) written and sponsored +by the Internet Corporation for Assigned Names and Numbers [(ICANN)](https://www.icann.org). +RDAP is standard of the [IETF](https://ietf.org/), and extensions +to RDAP are a current work activity of the IETF's [REGEXT working group](https://datatracker.ietf.org/wg/regext/documents/). +More information on ICANN's role in RDAP can be found [here](https://www.icann.org/rdap). +General information on RDAP can be found [here](https://rdap.rcode3.com/). + +Installation and Usage +---------------------- + +See the [project wiki](https://github.com/icann/icann-rdap/wiki) for information on installation +and usage of this software. + + +License +------- + +Licensed under either of +* Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) +* MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT) at your option. + +Contribution +------------ + +Unless you explicitly state otherwise, any contribution, as defined in the Apache-2.0 license, +intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, +shall be dual licensed pursuant to the Apache License, Version 2.0 or the MIT License referenced +as above, at ICANN’s option, without any additional terms or conditions. diff --git a/icann-rdap-cli/src/bin/rdap-test/error.rs b/icann-rdap-cli/src/bin/rdap-test/error.rs new file mode 100644 index 0000000..e9da611 --- /dev/null +++ b/icann-rdap-cli/src/bin/rdap-test/error.rs @@ -0,0 +1,98 @@ +use std::process::{ExitCode, Termination}; + +use { + icann_rdap_cli::rt::exec::TestExecutionError, + icann_rdap_client::{iana::IanaResponseError, RdapClientError}, + thiserror::Error, +}; + +#[derive(Debug, Error)] +pub enum RdapTestError { + #[error("No errors encountered")] + Success, + #[error("Tests completed with execution errors.")] + TestsCompletedExecutionErrors, + #[error("Tests completed, warning checks found.")] + TestsCompletedWarningsFound, + #[error("Tests completed, error checks found.")] + TestsCompletedErrorsFound, + #[error(transparent)] + RdapClient(#[from] RdapClientError), + #[error(transparent)] + TestExecutionError(#[from] TestExecutionError), + #[error(transparent)] + Termimad(#[from] termimad::Error), + #[error(transparent)] + IoError(#[from] std::io::Error), + #[error("Unknown output type")] + UnknownOutputType, + #[error(transparent)] + Json(#[from] serde_json::Error), + #[error(transparent)] + Iana(#[from] IanaResponseError), + #[error("Invalid IANA bootsrap file")] + InvalidBootstrap, + #[error("Bootstrap not found")] + BootstrapNotFound, + #[error("No registrar found")] + NoRegistrarFound, + #[error("No registry found")] + NoRegistryFound, +} + +impl Termination for RdapTestError { + fn report(self) -> std::process::ExitCode { + let exit_code: u8 = match self { + // Success + Self::Success => 0, + Self::TestsCompletedExecutionErrors => 1, + Self::TestsCompletedWarningsFound => 2, + Self::TestsCompletedErrorsFound => 3, + + // Internal Errors + Self::Termimad(_) => 10, + + // I/O Errors + Self::IoError(_) => 40, + Self::TestExecutionError(_) => 40, + + // RDAP Errors + Self::Json(_) => 100, + Self::Iana(_) => 101, + Self::InvalidBootstrap => 102, + Self::BootstrapNotFound => 103, + Self::NoRegistrarFound => 104, + Self::NoRegistryFound => 105, + + // User Errors + Self::UnknownOutputType => 200, + + // RDAP Client Errrors + Self::RdapClient(e) => match e { + // I/O Errors + RdapClientError::Client(_) => 42, + RdapClientError::IoError(_) => 43, + + // RDAP Server Errors + RdapClientError::Response(_) => 60, + RdapClientError::ParsingError(_) => 62, + RdapClientError::Json(_) => 63, + + // Bootstrap Errors + RdapClientError::BootstrapUnavailable => 70, + RdapClientError::BootstrapError(_) => 71, + RdapClientError::IanaResponse(_) => 72, + + // User Errors + RdapClientError::InvalidQueryValue => 202, + RdapClientError::AmbiquousQueryType => 203, + RdapClientError::DomainNameError(_) => 204, + + // Internal Errors + RdapClientError::Poison => 250, + // _ => 255, + }, + }; + ExitCode::from(exit_code) + } +} diff --git a/icann-rdap-cli/src/bin/rdap-test/main.rs b/icann-rdap-cli/src/bin/rdap-test/main.rs new file mode 100644 index 0000000..4507acf --- /dev/null +++ b/icann-rdap-cli/src/bin/rdap-test/main.rs @@ -0,0 +1,573 @@ +use std::{io::stdout, str::FromStr}; + +#[cfg(debug_assertions)] +use tracing::warn; +use { + clap::builder::{styling::AnsiColor, Styles}, + error::RdapTestError, + icann_rdap_cli::{ + dirs, + dirs::fcbs::FileCacheBootstrapStore, + rt::{ + exec::{execute_tests, ExtensionGroup, TestOptions}, + results::{RunOutcome, TestResults}, + }, + }, + icann_rdap_client::{http::ClientConfig, md::MdOptions, rdap::QueryType}, + icann_rdap_common::check::{traverse_checks, CheckClass}, + termimad::{crossterm::style::Color::*, Alignment, MadSkin}, + tracing::info, + tracing_subscriber::filter::LevelFilter, +}; + +use { + clap::{Parser, ValueEnum}, + icann_rdap_common::VERSION, +}; + +pub mod error; + +struct CliStyles; + +impl CliStyles { + fn cli_styles() -> Styles { + Styles::styled() + .header(AnsiColor::Yellow.on_default()) + .usage(AnsiColor::Green.on_default()) + .literal(AnsiColor::Green.on_default()) + .placeholder(AnsiColor::Green.on_default()) + } +} + +#[derive(Parser, Debug)] +#[command(author, version = VERSION, about, long_about, styles = CliStyles::cli_styles())] +/// This program aids in the troubleshooting of issues with RDAP servers. +struct Cli { + /// Value to be queried in RDAP. + /// + /// This is the value to query. For example, a domain name or IP address. + #[arg()] + query_value: String, + + /// Output format. + /// + /// This option determines the format of the result. + #[arg( + short = 'O', + long, + required = false, + env = "RDAP_TEST_OUTPUT", + value_enum, + default_value_t = OtypeArg::RenderedMarkdown, + )] + output_type: OtypeArg, + + /// Check type. + /// + /// Specifies the type of checks to conduct on the RDAP + /// responses. These are RDAP specific checks and not + /// JSON validation which is done automatically. This + /// argument may be specified multiple times to include + /// multiple check types. + #[arg(short = 'C', long, required = false, value_enum)] + check_type: Vec, + + /// Log level. + /// + /// This option determines the level of logging. + #[arg( + short = 'L', + long, + required = false, + env = "RDAP_TEST_LOG", + value_enum, + default_value_t = LogLevel::Info + )] + log_level: LogLevel, + + /// DNS Resolver + /// + /// Specifies the address and port of the DNS resolver to query. + #[arg( + long, + required = false, + env = "RDAP_TEST_DNS_RESOLVER", + default_value = "8.8.8.8:53" + )] + dns_resolver: String, + + /// Allow HTTP connections. + /// + /// When given, allows connections to RDAP servers using HTTP. + /// Otherwise, only HTTPS is allowed. + #[arg(short = 'T', long, required = false, env = "RDAP_TEST_ALLOW_HTTP")] + allow_http: bool, + + /// Allow invalid host names. + /// + /// When given, allows HTTPS connections to servers where the host name does + /// not match the certificate's host name. + #[arg( + short = 'K', + long, + required = false, + env = "RDAP_TEST_ALLOW_INVALID_HOST_NAMES" + )] + allow_invalid_host_names: bool, + + /// Allow invalid certificates. + /// + /// When given, allows HTTPS connections to servers where the TLS certificates + /// are invalid. + #[arg( + short = 'I', + long, + required = false, + env = "RDAP_TEST_ALLOW_INVALID_CERTIFICATES" + )] + allow_invalid_certificates: bool, + + /// Maximum retry wait time. + /// + /// Sets the maximum number of seconds to wait before retrying a query when + /// a server has sent an HTTP 429 status code with a retry-after value. + /// That is, the value to used is no greater than this setting. + #[arg( + long, + required = false, + env = "RDAP_TEST_MAX_RETRY_SECS", + default_value = "120" + )] + max_retry_secs: u32, + + /// Default retry wait time. + /// + /// Sets the number of seconds to wait before retrying a query when + /// a server has sent an HTTP 429 status code without a retry-after value + /// or when the retry-after value does not make sense. + #[arg( + long, + required = false, + env = "RDAP_TEST_DEF_RETRY_SECS", + default_value = "60" + )] + def_retry_secs: u32, + + /// Maximum number of retries. + /// + /// This sets the maximum number of retries when a server signals too many + /// requests have been sent using an HTTP 429 status code. + #[arg( + long, + required = false, + env = "RDAP_TEST_MAX_RETRIES", + default_value = "1" + )] + max_retries: u16, + + /// Set the query timeout. + /// + /// This values specifies, in seconds, the total time to connect and read all + /// the data from a connection. + #[arg( + long, + required = false, + env = "RDAP_TEST_TIMEOUT_SECS", + default_value = "60" + )] + timeout_secs: u64, + + /// Skip v4. + /// + /// Skip testing of IPv4 connections. + #[arg(long, required = false, env = "RDAP_TEST_SKIP_v4")] + skip_v4: bool, + + /// Skip v6. + /// + /// Skip testing of IPv6 connections. + #[arg(long, required = false, env = "RDAP_TEST_SKIP_V6")] + skip_v6: bool, + + /// Skip origin tests. + /// + /// Skip testing with the HTTP origin header. + #[arg(long, required = false, env = "RDAP_TEST_SKIP_ORIGIN")] + skip_origin: bool, + + /// Only test one address. + /// + /// Only test one address per address family. + #[arg(long, required = false, env = "RDAP_TEST_ONE_ADDR")] + one_addr: bool, + + /// Origin header value. + /// + /// Specifies the origin header value. + /// This value is not used if the 'skip-origin' option is used. + #[arg( + long, + required = false, + env = "RDAP_TEST_ORIGIN_VALUE", + default_value = "https://example.com" + )] + origin_value: String, + + /// Follow redirects. + /// + /// When set, follows HTTP redirects. + #[arg( + short = 'R', + long, + required = false, + env = "RDAP_TEST_FOLLOW_REDIRECTS" + )] + follow_redirects: bool, + + /// Chase a referral. + /// + /// Get a referral in the first response and use that for testing. This is useful + /// for testing registrars by using the normal bootstrapping process to get the + /// referral to the registrar from the registry. + #[arg(short = 'r', long, required = false)] + referral: bool, + + /// Expect extension. + /// + /// Expect the RDAP response to contain a specific extension ID. + /// If a response does not contain the expected RDAP extension ID, + /// it will be added as an failed check. This parameter may also + /// take the form of "foo1|foo2" to be mean either expect "foo1" or + /// "foo2". + /// + /// This value may be repeated more than once. + #[arg( + short = 'e', + long, + required = false, + env = "RDAP_TEST_EXPECT_EXTENSIONS" + )] + expect_extensions: Vec, + + /// Expect extension group. + /// + /// Extension groups are known sets of extensions. + /// + /// This value may be repeated more than once. + #[arg( + short = 'g', + long, + required = false, + value_enum, + env = "RDAP_TEST_EXPECT_EXTENSION_GROUP" + )] + expect_group: Vec, + + /// Allow unregistered extensions. + /// + /// Do not flag unregistered extensions. + #[arg( + short = 'E', + long, + required = false, + env = "RDAP_TEST_ALLOW_UNREGISTERED_EXTENSIONS" + )] + allow_unregistered_extensions: bool, +} + +/// Represents the output type possibilities. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +enum OtypeArg { + /// Results are rendered as Markdown in the terminal using ANSI terminal capabilities. + RenderedMarkdown, + + /// Results are rendered as Markdown in plain text. + Markdown, + + /// Results are output as RDAP JSON. + Json, + + /// Results are output as Pretty RDAP JSON. + PrettyJson, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +enum CheckTypeArg { + /// All checks. + All, + + /// Informational items. + Info, + + /// Specification Notes + SpecNote, + + /// Checks for STD 95 warnings. + StdWarn, + + /// Checks for STD 95 errors. + StdError, + + /// Cidr0 errors. + Cidr0Error, + + /// ICANN Profile errors. + IcannError, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +enum ExtensionGroupArg { + /// The gTLD RDAP profiles. + Gtld, + + /// The base NRO profiles. + Nro, + + /// The NRO ASN profiles including the base profile. + NroAsn, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +enum LogLevel { + /// No logging. + Off, + + /// Log errors. + Error, + + /// Log errors and warnings. + Warn, + + /// Log informational messages, errors, and warnings. + Info, + + /// Log debug messages, informational messages, errors and warnings. + Debug, + + /// Log messages appropriate for software development. + Trace, +} + +impl From<&LogLevel> for LevelFilter { + fn from(log_level: &LogLevel) -> Self { + match log_level { + LogLevel::Off => Self::OFF, + LogLevel::Error => Self::ERROR, + LogLevel::Warn => Self::WARN, + LogLevel::Info => Self::INFO, + LogLevel::Debug => Self::DEBUG, + LogLevel::Trace => Self::TRACE, + } + } +} + +#[tokio::main] +pub async fn main() -> RdapTestError { + if let Err(e) = wrapped_main().await { + eprintln!("\n{e}\n"); + return e; + } else { + return RdapTestError::Success; + } +} + +pub async fn wrapped_main() -> Result<(), RdapTestError> { + dirs::init()?; + dotenv::from_path(dirs::config_path()).ok(); + let cli = Cli::parse(); + + let level = LevelFilter::from(&cli.log_level); + tracing_subscriber::fmt() + .with_max_level(level) + .with_writer(std::io::stderr) + .init(); + + info!("ICANN RDAP {} Testing Tool", VERSION); + + #[cfg(debug_assertions)] + warn!("This is a development build of this software."); + + let query_type = QueryType::from_str(&cli.query_value)?; + + let check_classes = if cli.check_type.is_empty() { + vec![ + CheckClass::StdWarning, + CheckClass::StdError, + CheckClass::Cidr0Error, + CheckClass::IcannError, + ] + } else if cli.check_type.contains(&CheckTypeArg::All) { + vec![ + CheckClass::Informational, + CheckClass::SpecificationNote, + CheckClass::StdWarning, + CheckClass::StdError, + CheckClass::Cidr0Error, + CheckClass::IcannError, + ] + } else { + cli.check_type + .iter() + .map(|c| match c { + CheckTypeArg::Info => CheckClass::Informational, + CheckTypeArg::SpecNote => CheckClass::SpecificationNote, + CheckTypeArg::StdWarn => CheckClass::StdWarning, + CheckTypeArg::StdError => CheckClass::StdError, + CheckTypeArg::Cidr0Error => CheckClass::Cidr0Error, + CheckTypeArg::IcannError => CheckClass::IcannError, + CheckTypeArg::All => panic!("check type for all should have been handled."), + }) + .collect::>() + }; + + let mut expect_groups = vec![]; + for g in cli.expect_group { + match g { + ExtensionGroupArg::Gtld => expect_groups.push(ExtensionGroup::Gtld), + ExtensionGroupArg::Nro => expect_groups.push(ExtensionGroup::Nro), + ExtensionGroupArg::NroAsn => expect_groups.push(ExtensionGroup::NroAsn), + } + } + + let bs = FileCacheBootstrapStore; + + let options = TestOptions { + skip_v4: cli.skip_v4, + skip_v6: cli.skip_v6, + skip_origin: cli.skip_origin, + origin_value: cli.origin_value, + chase_referral: cli.referral, + expect_extensions: cli.expect_extensions, + expect_groups, + allow_unregistered_extensions: cli.allow_unregistered_extensions, + one_addr: cli.one_addr, + dns_resolver: Some(cli.dns_resolver), + }; + + let client_config = ClientConfig::builder() + .user_agent_suffix("RT") + .https_only(!cli.allow_http) + .accept_invalid_host_names(cli.allow_invalid_host_names) + .accept_invalid_certificates(cli.allow_invalid_certificates) + .follow_redirects(cli.follow_redirects) + .timeout_secs(cli.timeout_secs) + .max_retry_secs(cli.max_retry_secs) + .def_retry_secs(cli.def_retry_secs) + .max_retries(cli.max_retries) + .build(); + + // execute tests + let test_results = execute_tests(&bs, &query_type, &options, &client_config).await?; + + // output results + let md_options = MdOptions::default(); + match cli.output_type { + OtypeArg::RenderedMarkdown => { + let mut skin = MadSkin::default_dark(); + skin.set_headers_fg(Yellow); + skin.headers[1].align = Alignment::Center; + skin.headers[2].align = Alignment::Center; + skin.headers[3].align = Alignment::Center; + skin.headers[4].compound_style.set_fg(DarkGreen); + skin.headers[5].compound_style.set_fg(Magenta); + skin.headers[6].compound_style.set_fg(Cyan); + skin.headers[7].compound_style.set_fg(Red); + skin.bold.set_fg(DarkBlue); + skin.italic.set_fg(Red); + skin.quote_mark.set_fg(DarkBlue); + skin.table.set_fg(DarkGreen); + skin.table.align = Alignment::Center; + skin.inline_code.set_fgbg(Cyan, Reset); + skin.write_text_on( + &mut stdout(), + &test_results.to_md(&md_options, &check_classes), + )?; + } + OtypeArg::Markdown => { + println!("{}", test_results.to_md(&md_options, &check_classes)); + } + OtypeArg::Json => { + println!("{}", serde_json::to_string(&test_results).unwrap()); + } + OtypeArg::PrettyJson => { + println!("{}", serde_json::to_string_pretty(&test_results).unwrap()); + } + } + + // if some tests could not execute + // + let execution_errors = test_results + .test_runs + .iter() + .filter(|r| !matches!(r.outcome, RunOutcome::Tested | RunOutcome::Skipped)) + .count(); + if execution_errors != 0 { + return Err(RdapTestError::TestsCompletedExecutionErrors); + } + + // if tests had check errors + // + // get the error classes but only if they were specified. + let error_classes = check_classes + .iter() + .filter(|c| { + matches!( + c, + CheckClass::StdError | CheckClass::Cidr0Error | CheckClass::IcannError + ) + }) + .copied() + .collect::>(); + // return proper exit code if errors found + if are_there_checks(error_classes, &test_results) { + return Err(RdapTestError::TestsCompletedErrorsFound); + } + + // if tests had check warnings + // + // get the warning classes but only if they were specified. + let warning_classes = check_classes + .iter() + .filter(|c| matches!(c, CheckClass::StdWarning)) + .copied() + .collect::>(); + // return proper exit code if errors found + if are_there_checks(warning_classes, &test_results) { + return Err(RdapTestError::TestsCompletedWarningsFound); + } + + Ok(()) +} + +fn are_there_checks(classes: Vec, test_results: &TestResults) -> bool { + // see if there are any checks in the test runs + let run_count = test_results + .test_runs + .iter() + .filter(|r| { + if let Some(checks) = &r.checks { + traverse_checks(checks, &classes, None, &mut |_, _| {}) + } else { + false + } + }) + .count(); + // see if there are any classes in the service checks + let service_count = test_results + .service_checks + .iter() + .filter(|c| classes.contains(&c.check_class)) + .count(); + run_count + service_count != 0 +} + +#[cfg(test)] +mod tests { + use crate::Cli; + + #[test] + fn cli_debug_assert_test() { + use clap::CommandFactory; + Cli::command().debug_assert() + } +} diff --git a/icann-rdap-cli/src/bin/rdap/after_long_help.txt b/icann-rdap-cli/src/bin/rdap/after_long_help.txt new file mode 100644 index 0000000..4a1b82d --- /dev/null +++ b/icann-rdap-cli/src/bin/rdap/after_long_help.txt @@ -0,0 +1,27 @@ +Configuration: + +Configuration of this program may also be set using an environment variables configuration file in the configuration directory of this program. An example is automatically written to the configuration directory. This configuraiton file may be customized by uncommenting out the provided environment variable settings. + +The location of the configuration file is platform dependent. + +On Linux, this file is located at $XDG_CONFIG_HOME/rdap/rdap.env or +$HOME/.config/rdap/rdap.env. + +On macOS, this file is located at +$HOME/Library/Application Support/org.ICANN.rdap/rdap.env. + +On Windows, this file is located at +{FOLDERID_RoamingAppData}\rdap\config\rdap.env. + +Caches: + +Cache data used by this program is kept in a location dependent on the platform: + +On Linux, these files are located at $XDG_CACHE_HOME/rdap/ or +$HOME/.cache/rdap/. + +On macOS, these files are located at +$HOME/Library/Caches/org.ICANN.rdap/. + +On Windows, this file is located at +{FOLDERID_LocalAppData}\rdap\. diff --git a/icann-rdap-cli/src/bin/rdap/before_long_help.txt b/icann-rdap-cli/src/bin/rdap/before_long_help.txt new file mode 100644 index 0000000..12f6bc7 --- /dev/null +++ b/icann-rdap-cli/src/bin/rdap/before_long_help.txt @@ -0,0 +1,4 @@ +Copyright (C) 2023 Internet Corporation for Assigned Names and Numbers +This software is dual licensed using Apache License 2.0 and MIT License. +Information on this software may be found at https://github.com/icann/icann-rdap +Information on ICANN's RDAP program may be found at https://www.icann.org/rdap diff --git a/icann-rdap-cli/src/bin/rdap/bootstrap.rs b/icann-rdap-cli/src/bin/rdap/bootstrap.rs new file mode 100644 index 0000000..add263f --- /dev/null +++ b/icann-rdap-cli/src/bin/rdap/bootstrap.rs @@ -0,0 +1,105 @@ +use { + crate::error::RdapCliError, + icann_rdap_cli::dirs::fcbs::FileCacheBootstrapStore, + icann_rdap_client::{ + http::Client, + iana::{fetch_bootstrap, qtype_to_bootstrap_url, BootstrapStore, PreferredUrl}, + rdap::QueryType, + }, + icann_rdap_common::iana::IanaRegistryType, + tracing::debug, +}; + +/// Defines the type of bootstrapping to use. +pub(crate) enum BootstrapType { + /// Use RFC 9224 bootstrapping. + /// + /// This is the typical bootstrapping for RDAP as defined by RFC 9224. + Rfc9224, + + /// Use the supplied URL. + /// + /// Essentially, this means no bootstrapping as the client is being given + /// a full URL. + Url(String), + + /// Use a hint. + /// + /// This will try to find an authoritative server by cycling through the various + /// bootstrap registries in the following order: object tags, TLDs, IP addresses, + /// ASNs. + Hint(String), +} + +pub(crate) async fn get_base_url( + bootstrap_type: &BootstrapType, + client: &Client, + query_type: &QueryType, +) -> Result { + if let QueryType::Url(url) = query_type { + // this is ultimately ignored without this logic a bootstrap not found error is thrown + // which is wrong for URL queries. + return Ok(url.to_owned()); + } + + let store = FileCacheBootstrapStore; + + match bootstrap_type { + BootstrapType::Rfc9224 => Ok(qtype_to_bootstrap_url(client, &store, query_type, |reg| { + debug!("Fetching IANA registry {}", reg.url()) + }) + .await?), + BootstrapType::Url(url) => Ok(url.to_owned()), + BootstrapType::Hint(hint) => { + fetch_bootstrap(&IanaRegistryType::RdapObjectTags, client, &store, |_reg| { + debug!("Fetching IANA RDAP Object Tag Registry") + }) + .await?; + if let Ok(urls) = store.get_tag_urls(hint) { + Ok(urls.preferred_url()?) + } else { + fetch_bootstrap( + &IanaRegistryType::RdapBootstrapDns, + client, + &store, + |_reg| debug!("Fetching IANA RDAP DNS Registry"), + ) + .await?; + if let Ok(urls) = store.get_dns_urls(hint) { + Ok(urls.preferred_url()?) + } else { + fetch_bootstrap( + &IanaRegistryType::RdapBootstrapIpv4, + client, + &store, + |_reg| debug!("Fetching IANA RDAP IPv4 Registry"), + ) + .await?; + if let Ok(urls) = store.get_ipv4_urls(hint) { + Ok(urls.preferred_url()?) + } else { + fetch_bootstrap( + &IanaRegistryType::RdapBootstrapIpv6, + client, + &store, + |_reg| debug!("Fetching IANA RDAP IPv6 Registry"), + ) + .await?; + if let Ok(urls) = store.get_ipv6_urls(hint) { + Ok(urls.preferred_url()?) + } else { + fetch_bootstrap( + &IanaRegistryType::RdapBootstrapAsn, + client, + &store, + |_reg| debug!("Fetching IANA RDAP ASN Registry"), + ) + .await?; + Ok(store.get_asn_urls(hint)?.preferred_url()?) + } + } + } + } + } + } +} diff --git a/icann-rdap-cli/src/bin/rdap/error.rs b/icann-rdap-cli/src/bin/rdap/error.rs new file mode 100644 index 0000000..6285b4b --- /dev/null +++ b/icann-rdap-cli/src/bin/rdap/error.rs @@ -0,0 +1,108 @@ +use std::process::{ExitCode, Termination}; + +use { + icann_rdap_client::{iana::IanaResponseError, RdapClientError}, + minus::MinusError, + thiserror::Error, + tracing::error, +}; + +#[derive(Debug, Error)] +pub enum RdapCliError { + #[error("No errors encountered")] + Success, + #[error(transparent)] + RdapClient(#[from] RdapClientError), + #[error(transparent)] + Termimad(#[from] termimad::Error), + #[error(transparent)] + IoError(#[from] std::io::Error), + #[error(transparent)] + Minus(#[from] MinusError), + #[error("Unknown output type")] + UnknownOutputType, + #[error("RDAP response failed checks.")] + ErrorOnChecks, + #[error(transparent)] + Json(#[from] serde_json::Error), + #[error(transparent)] + Iana(#[from] IanaResponseError), + #[error("Invalid IANA bootsrap file")] + InvalidBootstrap, + #[error("Bootstrap not found")] + BootstrapNotFound, + #[error("No registrar found")] + NoRegistrarFound, + #[error("No registry found")] + NoRegistryFound, +} + +impl RdapCliError { + pub(crate) fn exit_code(&self) -> u8 { + match self { + // Success + Self::Success => 0, + + // Internal Errors + Self::Termimad(_) => 10, + Self::Minus(_) => 11, + + // I/O Errors + Self::IoError(_) => 40, + + // RDAP Errors + Self::Json(_) => 100, + Self::Iana(_) => 101, + Self::InvalidBootstrap => 102, + Self::BootstrapNotFound => 103, + Self::NoRegistrarFound => 104, + Self::NoRegistryFound => 105, + + // User Errors + Self::UnknownOutputType => 200, + Self::ErrorOnChecks => 201, + + // RDAP Client Errrors + Self::RdapClient(e) => match e { + // I/O Errors + RdapClientError::Client(ce) => { + if ce.is_builder() { + match ce.url() { + Some(url) if url.scheme() == "http" => 202, + _ => 42, + } + } else { + 42 + } + } + RdapClientError::IoError(_) => 43, + + // RDAP Server Errors + RdapClientError::Response(_) => 60, + RdapClientError::ParsingError(_) => 62, + RdapClientError::Json(_) => 63, + + // Bootstrap Errors + RdapClientError::BootstrapUnavailable => 70, + RdapClientError::BootstrapError(_) => 71, + RdapClientError::IanaResponse(_) => 72, + + // User Errors + RdapClientError::InvalidQueryValue => 202, + RdapClientError::AmbiquousQueryType => 203, + RdapClientError::DomainNameError(_) => 204, + + // Internal Errors + RdapClientError::Poison => 250, + // _ => 255, + }, + } + } +} + +impl Termination for RdapCliError { + fn report(self) -> std::process::ExitCode { + let exit_code = self.exit_code(); + ExitCode::from(exit_code) + } +} diff --git a/icann-rdap-cli/src/bin/rdap/main.rs b/icann-rdap-cli/src/bin/rdap/main.rs new file mode 100644 index 0000000..de02bd0 --- /dev/null +++ b/icann-rdap-cli/src/bin/rdap/main.rs @@ -0,0 +1,732 @@ +#[cfg(debug_assertions)] +use tracing::warn; +use { + bootstrap::BootstrapType, + clap::builder::{styling::AnsiColor, Styles}, + error::RdapCliError, + icann_rdap_cli::dirs, + icann_rdap_client::http::{create_client, Client, ClientConfig}, + icann_rdap_common::check::CheckClass, + query::{InrBackupBootstrap, ProcessType, ProcessingParams, TldLookup}, + std::{io::IsTerminal, str::FromStr}, + tracing::{error, info}, + tracing_subscriber::filter::LevelFilter, + write::{FmtWrite, PagerWrite}, +}; + +use { + clap::{ArgGroup, Parser, ValueEnum}, + icann_rdap_client::rdap::QueryType, + icann_rdap_common::VERSION, + query::OutputType, + tokio::{join, task::spawn_blocking}, +}; + +use crate::query::do_query; + +pub mod bootstrap; +pub mod error; +pub mod query; +pub mod request; +pub mod write; + +const BEFORE_LONG_HELP: &str = include_str!("before_long_help.txt"); +const AFTER_LONG_HELP: &str = include_str!("after_long_help.txt"); + +struct CliStyles; + +impl CliStyles { + fn cli_styles() -> Styles { + Styles::styled() + .header(AnsiColor::Yellow.on_default()) + .usage(AnsiColor::Green.on_default()) + .literal(AnsiColor::Green.on_default()) + .placeholder(AnsiColor::Green.on_default()) + } +} + +#[derive(Parser, Debug)] +#[command(author, version = VERSION, about, long_about, styles = CliStyles::cli_styles())] +#[command(group( + ArgGroup::new("input") + .required(true) + .args(["query_value", "server_help", "reset"]), + ))] +#[command(group( + ArgGroup::new("base_specify") + .args(["base", "base_url"]), + ))] +#[command(before_long_help(BEFORE_LONG_HELP))] +#[command(after_long_help(AFTER_LONG_HELP))] +/// This program queries network registry information from domain name registries and registrars +/// and Internet number registries (i.e. Regional Internet Registries) using the Registry Data +/// Access Protocol (RDAP). +struct Cli { + /// Value to be queried in RDAP. + /// + /// This is the value to query. For example, a domain name or IP address. + #[arg()] + query_value: Option, + + /// Type of the query when using a query value. + /// + /// Without this option, the query type will be inferred based on the query value. + /// To supress the infererence and explicitly specifty the query type, use this + /// option. + #[arg( + short = 't', + long, + requires = "query_value", + required = false, + value_enum + )] + query_type: Option, + + /// Get an RDAP server's help information. + /// + /// Ask for a server's help information. + #[arg(short = 'S', long, conflicts_with = "query_type")] + server_help: bool, + + /// An RDAP base signifier. + /// + /// This option gets a base URL from the RDAP bootstrap registries maintained + /// by IANA. For example, using "com" will get the base URL for the .com + /// registry, and "arin" will get the base URL for the RDAP tags registry, + /// which points to the ARIN RIR. This option checks the bootstrap registries + /// in the following order: object tags, TLDs, IPv4, IPv6, ASN. + #[arg(short = 'b', long, required = false, env = "RDAP_BASE")] + base: Option, + + /// An RDAP base URL for a specific RDAP server. + /// + /// Use this option to explicitly give an RDAP base URL when issuing queries. + /// If not specified, the base URL will come from the RDAP boostrap process + /// outlined in RFC 9224. + #[arg(short = 'B', long, required = false, env = "RDAP_BASE_URL")] + base_url: Option, + + /// Specify where to send TLD queries. + /// + /// Defaults to IANA. + #[arg( + long, + required = false, + env = "RDAP_TLD_LOOKUP", + value_enum, + default_value_t = TldLookupArg::Iana, + )] + tld_lookup: TldLookupArg, + + /// Specify a backup INR bootstrap. + /// + /// This is used as a backup when the bootstrapping process cannot find an authoritative + /// server for IP addresses and Autonomous System Numbers. Defaults to ARIN. + #[arg( + long, + required = false, + env = "RDAP_INR_BACKUP_BOOTSTRAP", + value_enum, + default_value_t = InrBackupBootstrapArg::Arin, + )] + inr_backup_bootstrap: InrBackupBootstrapArg, + + /// Output format. + /// + /// This option determines the format of the result. + #[arg( + short = 'O', + long, + required = false, + env = "RDAP_OUTPUT", + value_enum, + default_value_t = OtypeArg::Auto, + )] + output_type: OtypeArg, + + /// Check type. + /// + /// Specifies the type of checks to conduct on the RDAP + /// responses. These are RDAP specific checks and not + /// JSON validation which is done automatically. This + /// argument may be specified multiple times to include + /// multiple check types. + #[arg(short = 'C', long, required = false, value_enum)] + check_type: Vec, + + /// Error if RDAP checks found. + /// + /// The program will log error messages for non-info + /// checks found in the RDAP response(s) and exit with a + /// non-zero status. + #[arg(long, env = "RDAP_ERROR_ON_CHECK")] + error_on_checks: bool, + + /// Process Type + /// + /// Specifies a process for handling the data. + #[arg( + short = 'p', + long, + required = false, + env = "RDAP_PROCESS_TYPE", + value_enum + )] + process_type: Option, + + /// Pager Usage. + /// + /// Determines how to handle paging output. + /// When using the embedded pager, all log messages will be sent to the + /// pager as well. Otherwise, log messages are sent to stderr. + #[arg( + short = 'P', + long, + required = false, + env = "RDAP_PAGING", + value_enum, + default_value_t = PagerType::None, + )] + page_output: PagerType, + + /// Log level. + /// + /// This option determines the level of logging. + #[arg( + short = 'L', + long, + required = false, + env = "RDAP_LOG", + value_enum, + default_value_t = LogLevel::Info + )] + log_level: LogLevel, + + /// Do not use the cache. + /// + /// When given, the cache will be neither read from nor written to. + #[arg(short = 'N', long, required = false, env = "RDAP_NO_CACHE")] + no_cache: bool, + + /// Max cache age. + /// + /// Specifies the maximum age in seconds of an item in the cache. + #[arg( + long, + required = false, + env = "RDAP_MAX_CACHE_AGE", + default_value = "86400" + )] + max_cache_age: u32, + + /// Allow HTTP connections. + /// + /// When given, allows connections to RDAP servers using HTTP. + /// Otherwise, only HTTPS is allowed. + #[arg(short = 'T', long, required = false, env = "RDAP_ALLOW_HTTP")] + allow_http: bool, + + /// Allow invalid host names. + /// + /// When given, allows HTTPS connections to servers where the host name does + /// not match the certificate's host name. + #[arg( + short = 'K', + long, + required = false, + env = "RDAP_ALLOW_INVALID_HOST_NAMES" + )] + allow_invalid_host_names: bool, + + /// Allow invalid certificates. + /// + /// When given, allows HTTPS connections to servers where the TLS certificates + /// are invalid. + #[arg( + short = 'I', + long, + required = false, + env = "RDAP_ALLOW_INVALID_CERTIFICATES" + )] + allow_invalid_certificates: bool, + + /// Set the query timeout. + /// + /// This values specifies, in seconds, the total time to connect and read all + /// the data from a connection. + #[arg( + long, + required = false, + env = "RDAP_TIMEOUT_SECS", + default_value = "60" + )] + timeout_secs: u64, + + /// Maximum retry wait time. + /// + /// Sets the maximum number of seconds to wait before retrying a query when + /// a server has sent an HTTP 429 status code with a retry-after value. + /// That is, the value to used is no greater than this setting. + #[arg( + long, + required = false, + env = "RDAP_MAX_RETRY_SECS", + default_value = "120" + )] + max_retry_secs: u32, + + /// Default retry wait time. + /// + /// Sets the number of seconds to wait before retrying a query when + /// a server has sent an HTTP 429 status code without a retry-after value + /// or when the retry-after value does not make sense. + #[arg( + long, + required = false, + env = "RDAP_DEF_RETRY_SECS", + default_value = "60" + )] + def_retry_secs: u32, + + /// Maximum number of retries. + /// + /// This sets the maximum number of retries when a server signals too many + /// requests have been sent using an HTTP 429 status code. + #[arg(long, required = false, env = "RDAP_MAX_RETRIES", default_value = "1")] + max_retries: u16, + + /// Reset. + /// + /// Removes the cache files and resets the config file. + #[arg(long, required = false)] + reset: bool, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +enum QtypeArg { + /// Ipv4 Address Lookup + V4, + + /// Ipv6 Address Lookup + V6, + + /// Ipv4 CIDR Lookup + V4Cidr, + + /// Ipv6 CIDR Lookup + V6Cidr, + + /// Autonomous System Number Lookup + Autnum, + + /// Domain Lookup + Domain, + + /// A-Label Domain Lookup + ALabel, + + /// Entity Lookup + Entity, + + /// Nameserver Lookup + Ns, + + /// Entity Name Search + EntityName, + + /// Entity Handle Search + EntityHandle, + + /// Domain Name Search + DomainName, + + /// Domain Nameserver Name Search + DomainNsName, + + /// Domain Nameserver IP Address Search + DomainNsIp, + + /// Nameserver Name Search + NsName, + + /// Nameserver IP Address Search + NsIp, + + /// RDAP URL + Url, +} + +/// Represents the output type possibilities. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +enum OtypeArg { + /// Results are rendered as Markdown in the terminal using ANSI terminal capabilities. + RenderedMarkdown, + + /// Results are rendered as Markdown in plain text. + Markdown, + + /// Results are output as RDAP JSON. + Json, + + /// Results are output as Pretty RDAP JSON. + PrettyJson, + + /// RDAP JSON with extra information. + JsonExtra, + + /// Global Top Level Domain Output + GtldWhois, + + /// URL of RDAP servers. + Url, + + /// Automatically determine the output type. + Auto, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +enum CheckTypeArg { + /// All checks. + All, + + /// Informational items. + Info, + + /// Specification Notes + SpecNote, + + /// Checks for STD 95 warnings. + StdWarn, + + /// Checks for STD 95 errors. + StdError, + + /// Cidr0 errors. + Cidr0Error, + + /// ICANN Profile errors. + IcannError, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +enum LogLevel { + /// No logging. + Off, + + /// Log errors. + Error, + + /// Log errors and warnings. + Warn, + + /// Log informational messages, errors, and warnings. + Info, + + /// Log debug messages, informational messages, errors and warnings. + Debug, + + /// Log messages appropriate for software development. + Trace, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +enum ProcTypeArg { + /// Only display the data from the domain registrar. + Registrar, + + /// Only display the data from the domain registry. + Registry, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +enum PagerType { + /// Use the embedded pager. + Embedded, + + /// Use no pager. + None, + + /// Automatically determine pager use. + Auto, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +enum TldLookupArg { + /// Use IANA for TLD lookups. + Iana, + + /// No TLD specific lookups. + None, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +enum InrBackupBootstrapArg { + /// Use ARIN when no INR bootstrap can be found. + Arin, + + /// No backup for INR bootstraps. + None, +} + +impl From<&LogLevel> for LevelFilter { + fn from(log_level: &LogLevel) -> Self { + match log_level { + LogLevel::Off => Self::OFF, + LogLevel::Error => Self::ERROR, + LogLevel::Warn => Self::WARN, + LogLevel::Info => Self::INFO, + LogLevel::Debug => Self::DEBUG, + LogLevel::Trace => Self::TRACE, + } + } +} + +#[tokio::main] +pub async fn main() -> RdapCliError { + if let Err(e) = wrapped_main().await { + let ec = e.exit_code(); + match ec { + 202 => error!("Use -T or --allow-http to allow insecure HTTP connections."), + _ => eprintln!("\n{e}\n"), + }; + return e; + } else { + return RdapCliError::Success; + } +} + +pub async fn wrapped_main() -> Result<(), RdapCliError> { + dirs::init()?; + dotenv::from_path(dirs::config_path()).ok(); + let cli = Cli::parse(); + + if cli.reset { + dirs::reset()?; + return Ok(()); + } + + let level = LevelFilter::from(&cli.log_level); + + let query_type = query_type_from_cli(&cli)?; + + let use_pager = match cli.page_output { + PagerType::Embedded => true, + PagerType::None => false, + PagerType::Auto => std::io::stdout().is_terminal(), + }; + + let output_type = match cli.output_type { + OtypeArg::Auto => { + if std::io::stdout().is_terminal() { + OutputType::RenderedMarkdown + } else { + OutputType::Json + } + } + OtypeArg::RenderedMarkdown => OutputType::RenderedMarkdown, + OtypeArg::Markdown => OutputType::Markdown, + OtypeArg::Json => OutputType::Json, + OtypeArg::PrettyJson => OutputType::PrettyJson, + OtypeArg::JsonExtra => OutputType::JsonExtra, + OtypeArg::GtldWhois => OutputType::GtldWhois, + OtypeArg::Url => OutputType::Url, + }; + + let process_type = match cli.process_type { + Some(p) => match p { + ProcTypeArg::Registrar => ProcessType::Registrar, + ProcTypeArg::Registry => ProcessType::Registry, + }, + None => ProcessType::Standard, + }; + + let check_types = if cli.check_type.is_empty() { + vec![ + CheckClass::Informational, + CheckClass::StdWarning, + CheckClass::StdError, + CheckClass::Cidr0Error, + CheckClass::IcannError, + ] + } else if cli.check_type.contains(&CheckTypeArg::All) { + vec![ + CheckClass::Informational, + CheckClass::SpecificationNote, + CheckClass::StdWarning, + CheckClass::StdError, + CheckClass::Cidr0Error, + CheckClass::IcannError, + ] + } else { + cli.check_type + .iter() + .map(|c| match c { + CheckTypeArg::Info => CheckClass::Informational, + CheckTypeArg::SpecNote => CheckClass::SpecificationNote, + CheckTypeArg::StdWarn => CheckClass::StdWarning, + CheckTypeArg::StdError => CheckClass::StdError, + CheckTypeArg::Cidr0Error => CheckClass::Cidr0Error, + CheckTypeArg::IcannError => CheckClass::IcannError, + CheckTypeArg::All => panic!("check type for all should have been handled."), + }) + .collect::>() + }; + + let bootstrap_type = if let Some(ref tag) = cli.base { + BootstrapType::Hint(tag.to_string()) + } else if let Some(ref base_url) = cli.base_url { + BootstrapType::Url(base_url.to_string()) + } else { + BootstrapType::Rfc9224 + }; + + let tld_lookup = match cli.tld_lookup { + TldLookupArg::Iana => TldLookup::Iana, + TldLookupArg::None => TldLookup::None, + }; + + let inr_backup_bootstrap = match cli.inr_backup_bootstrap { + InrBackupBootstrapArg::Arin => InrBackupBootstrap::Arin, + InrBackupBootstrapArg::None => InrBackupBootstrap::None, + }; + + let processing_params = ProcessingParams { + bootstrap_type, + output_type, + check_types, + process_type, + tld_lookup, + inr_backup_bootstrap, + error_on_checks: cli.error_on_checks, + no_cache: cli.no_cache, + max_cache_age: cli.max_cache_age, + }; + + let client_config = ClientConfig::builder() + .user_agent_suffix("CLI") + .https_only(!cli.allow_http) + .accept_invalid_host_names(cli.allow_invalid_host_names) + .accept_invalid_certificates(cli.allow_invalid_certificates) + .timeout_secs(cli.timeout_secs) + .max_retry_secs(cli.max_retry_secs) + .def_retry_secs(cli.def_retry_secs) + .max_retries(cli.max_retries) + .build(); + let rdap_client = create_client(&client_config); + if let Ok(client) = rdap_client { + if !use_pager { + tracing_subscriber::fmt() + .with_max_level(level) + .with_writer(std::io::stderr) + .init(); + let output = &mut std::io::stdout(); + let res1 = join!(exec( + cli.query_value, + &query_type, + &processing_params, + &client, + output, + )); + res1.0?; + } else { + let pager = minus::Pager::new(); + pager + .set_prompt(format!( + "{query_type} - Q to quit, j/k or pgup/pgdn to scroll" + )) + .expect("unable to set prompt"); + let output = FmtWrite(pager.clone()); + let pager2 = pager.clone(); + + tracing_subscriber::fmt() + .with_max_level(level) + .with_writer(move || -> Box { + Box::new(PagerWrite(pager2.clone())) + }) + .init(); + let pager = pager.clone(); + let (res1, res2) = join!( + spawn_blocking(move || minus::dynamic_paging(pager)), + exec( + cli.query_value, + &query_type, + &processing_params, + &client, + output + ) + ); + res1.unwrap()?; + res2?; + } + } else { + error!("{}", rdap_client.err().unwrap()) + }; + Ok(()) +} + +async fn exec( + query_value: Option, + query_type: &QueryType, + processing_params: &ProcessingParams, + client: &Client, + mut output: W, +) -> Result<(), RdapCliError> { + info!("ICANN RDAP {} Command Line Interface", VERSION); + + #[cfg(debug_assertions)] + warn!("This is a development build of this software."); + + if let Some(query_value) = query_value { + info!("query type is {query_type} for value '{}'", query_value); + } else { + info!("query is {query_type}"); + } + let result = do_query(query_type, processing_params, client, &mut output).await; + match result { + Ok(_) => Ok(()), + Err(error) => { + error!("{}", error); + Err(error) + } + } +} + +fn query_type_from_cli(cli: &Cli) -> Result { + let Some(query_value) = cli.query_value.clone() else { + return Ok(QueryType::Help); + }; + let Some(query_type) = cli.query_type else { + return Ok(QueryType::from_str(&query_value)?); + }; + let q = match query_type { + QtypeArg::V4 => QueryType::ipv4(&query_value)?, + QtypeArg::V6 => QueryType::ipv6(&query_value)?, + QtypeArg::V4Cidr => QueryType::ipv4cidr(&query_value)?, + QtypeArg::V6Cidr => QueryType::ipv6cidr(&query_value)?, + QtypeArg::Autnum => QueryType::autnum(&query_value)?, + QtypeArg::Domain => QueryType::domain(&query_value)?, + QtypeArg::ALabel => QueryType::alabel(&query_value)?, + QtypeArg::Entity => QueryType::Entity(query_value), + QtypeArg::Ns => QueryType::ns(&query_value)?, + QtypeArg::EntityName => QueryType::EntityNameSearch(query_value), + QtypeArg::EntityHandle => QueryType::EntityHandleSearch(query_value), + QtypeArg::DomainName => QueryType::DomainNameSearch(query_value), + QtypeArg::DomainNsName => QueryType::DomainNsNameSearch(query_value), + QtypeArg::DomainNsIp => QueryType::domain_ns_ip_search(&query_value)?, + QtypeArg::NsName => QueryType::NameserverNameSearch(query_value), + QtypeArg::NsIp => QueryType::ns_ip_search(&query_value)?, + QtypeArg::Url => QueryType::Url(query_value), + }; + Ok(q) +} + +#[cfg(test)] +mod tests { + use crate::Cli; + + #[test] + fn cli_debug_assert_test() { + use clap::CommandFactory; + Cli::command().debug_assert() + } +} diff --git a/icann-rdap-cli/src/bin/rdap/query.rs b/icann-rdap-cli/src/bin/rdap/query.rs new file mode 100644 index 0000000..9d5e3fe --- /dev/null +++ b/icann-rdap-cli/src/bin/rdap/query.rs @@ -0,0 +1,479 @@ +use { + icann_rdap_client::http::Client, + icann_rdap_common::{ + check::{traverse_checks, CheckClass, CheckParams, Checks, GetChecks}, + response::get_related_links, + }, + tracing::{debug, error, info}, +}; + +use { + icann_rdap_client::{ + gtld::{GtldParams, ToGtldWhois}, + md::{redacted::replace_redacted_items, MdOptions, MdParams, ToMd}, + rdap::{ + QueryType, RequestData, RequestResponse, RequestResponses, ResponseData, SourceType, + }, + }, + termimad::{crossterm::style::Color::*, Alignment, MadSkin}, +}; + +use crate::{ + bootstrap::{get_base_url, BootstrapType}, + error::RdapCliError, + request::do_request, +}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) enum OutputType { + /// Results are rendered as Markdown in the terminal using ANSI terminal capabilities. + RenderedMarkdown, + + /// Results are rendered as Markdown in plain text. + Markdown, + + /// Results are output as RDAP JSON. + Json, + + /// Results are output as Pretty RDAP JSON. + PrettyJson, + + /// Global Top Level Domain Output + GtldWhois, + + /// RDAP JSON with extra information. + JsonExtra, + + /// URL + Url, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) enum ProcessType { + /// Standard data processing. + Standard, + + /// Process data specifically from a registrar. + Registrar, + + /// Process data specifically from a registry. + Registry, +} + +/// Used for doing TLD Lookups. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) enum TldLookup { + /// Use IANA for TLD lookups. + Iana, + + /// No TLD specific lookups. + None, +} + +/// Used for doing TLD Lookups. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) enum InrBackupBootstrap { + /// Use ARIN if no bootstraps can be found for INR queries. + Arin, + + /// No INR bootstrap backup. + None, +} + +pub(crate) struct ProcessingParams { + pub bootstrap_type: BootstrapType, + pub output_type: OutputType, + pub check_types: Vec, + pub process_type: ProcessType, + pub tld_lookup: TldLookup, + pub inr_backup_bootstrap: InrBackupBootstrap, + pub error_on_checks: bool, + pub no_cache: bool, + pub max_cache_age: u32, +} + +pub(crate) async fn do_query<'a, W: std::io::Write>( + query_type: &QueryType, + processing_params: &ProcessingParams, + client: &Client, + write: &mut W, +) -> Result<(), RdapCliError> { + match query_type { + QueryType::IpV4Addr(_) + | QueryType::IpV6Addr(_) + | QueryType::IpV4Cidr(_) + | QueryType::IpV6Cidr(_) + | QueryType::AsNumber(_) => { + do_inr_query(query_type, processing_params, client, write).await + } + QueryType::Domain(_) | QueryType::DomainNameSearch(_) => { + do_domain_query(query_type, processing_params, client, write).await + } + _ => do_basic_query(query_type, processing_params, None, client, write).await, + } +} + +async fn do_domain_query<'a, W: std::io::Write>( + query_type: &QueryType, + processing_params: &ProcessingParams, + client: &Client, + write: &mut W, +) -> Result<(), RdapCliError> { + let mut transactions = RequestResponses::new(); + + // special processing for TLD Lookups + let base_url = if let QueryType::Domain(ref domain) = query_type { + if domain.is_tld() && matches!(processing_params.tld_lookup, TldLookup::Iana) { + "https://rdap.iana.org".to_string() + } else { + get_base_url(&processing_params.bootstrap_type, client, query_type).await? + } + } else { + get_base_url(&processing_params.bootstrap_type, client, query_type).await? + }; + + let response = do_request(&base_url, query_type, processing_params, client).await; + let registrar_response; + match response { + Ok(response) => { + let source_host = response.http_data.host.to_owned(); + let req_data = RequestData { + req_number: 1, + source_host: &source_host, + source_type: SourceType::DomainRegistry, + }; + let replaced_rdap = replace_redacted_items(response.rdap.clone()); + let replaced_data = ResponseData { + rdap: replaced_rdap, + // copy other fields from `response` + ..response.clone() + }; + if let ProcessType::Registrar = processing_params.process_type { + transactions = + do_no_output(processing_params, &req_data, &replaced_data, transactions); + } else { + transactions = do_output( + processing_params, + &req_data, + &replaced_data, + write, + transactions, + )?; + } + let regr_source_host; + let regr_req_data: RequestData; + if !matches!(processing_params.process_type, ProcessType::Registry) { + if let Some(url) = get_related_links(&response.rdap).first() { + info!("Querying domain name from registrar."); + debug!("Registrar RDAP Url: {url}"); + let query_type = QueryType::Url(url.to_string()); + let registrar_response_result = + do_request(&base_url, &query_type, processing_params, client).await; + match registrar_response_result { + Ok(response_data) => { + registrar_response = response_data; + regr_source_host = registrar_response.http_data.host.to_owned(); + regr_req_data = RequestData { + req_number: 2, + source_host: ®r_source_host, + source_type: SourceType::DomainRegistrar, + }; + if let ProcessType::Registry = processing_params.process_type { + transactions = do_no_output( + processing_params, + ®r_req_data, + ®istrar_response, + transactions, + ); + } else { + transactions = do_output( + processing_params, + ®r_req_data, + ®istrar_response, + write, + transactions, + )?; + } + } + Err(error) => return Err(error), + } + } else if matches!(processing_params.process_type, ProcessType::Registrar) { + return Err(RdapCliError::NoRegistrarFound); + } + } + do_final_output(processing_params, write, transactions)?; + } + Err(error) => { + if matches!(processing_params.process_type, ProcessType::Registry) { + return Err(RdapCliError::NoRegistryFound); + } else { + return Err(error); + } + } + }; + Ok(()) +} + +async fn do_inr_query<'a, W: std::io::Write>( + query_type: &QueryType, + processing_params: &ProcessingParams, + client: &Client, + write: &mut W, +) -> Result<(), RdapCliError> { + let mut transactions = RequestResponses::new(); + let mut base_url = get_base_url(&processing_params.bootstrap_type, client, query_type).await; + if base_url.is_err() + && matches!( + processing_params.inr_backup_bootstrap, + InrBackupBootstrap::Arin + ) + { + base_url = Ok("https://rdap.arin.net/registry".to_string()); + }; + let response = do_request(&base_url?, query_type, processing_params, client).await; + match response { + Ok(response) => { + let source_host = response.http_data.host.to_owned(); + let req_data = RequestData { + req_number: 1, + source_host: &source_host, + source_type: SourceType::RegionalInternetRegistry, + }; + let replaced_rdap = replace_redacted_items(response.rdap.clone()); + let replaced_data = ResponseData { + rdap: replaced_rdap, + // copy other fields from `response` + ..response.clone() + }; + transactions = do_output( + processing_params, + &req_data, + &replaced_data, + write, + transactions, + )?; + do_final_output(processing_params, write, transactions)?; + } + Err(error) => return Err(error), + }; + Ok(()) +} + +async fn do_basic_query<'a, W: std::io::Write>( + query_type: &QueryType, + processing_params: &ProcessingParams, + req_data: Option<&'a RequestData<'a>>, + client: &Client, + write: &mut W, +) -> Result<(), RdapCliError> { + let mut transactions = RequestResponses::new(); + let base_url = get_base_url(&processing_params.bootstrap_type, client, query_type).await?; + let response = do_request(&base_url, query_type, processing_params, client).await; + match response { + Ok(response) => { + let source_host = response.http_data.host.to_owned(); + let req_data = if let Some(meta) = req_data { + RequestData { + req_number: meta.req_number + 1, + source_host: meta.source_host, + source_type: SourceType::UncategorizedRegistry, + } + } else { + RequestData { + req_number: 1, + source_host: &source_host, + source_type: SourceType::UncategorizedRegistry, + } + }; + let replaced_rdap = replace_redacted_items(response.rdap.clone()); + let replaced_data = ResponseData { + rdap: replaced_rdap, + // copy other fields from `response` + ..response.clone() + }; + transactions = do_output( + processing_params, + &req_data, + &replaced_data, + write, + transactions, + )?; + do_final_output(processing_params, write, transactions)?; + } + Err(error) => return Err(error), + }; + Ok(()) +} + +fn do_output<'a, W: std::io::Write>( + processing_params: &ProcessingParams, + req_data: &'a RequestData, + response: &'a ResponseData, + write: &mut W, + mut transactions: RequestResponses<'a>, +) -> Result, RdapCliError> { + match processing_params.output_type { + OutputType::RenderedMarkdown => { + let mut skin = MadSkin::default_dark(); + skin.set_headers_fg(Yellow); + skin.headers[1].align = Alignment::Center; + skin.headers[2].align = Alignment::Center; + skin.headers[3].align = Alignment::Center; + skin.headers[4].compound_style.set_fg(DarkGreen); + skin.headers[5].compound_style.set_fg(Magenta); + skin.headers[6].compound_style.set_fg(Cyan); + skin.headers[7].compound_style.set_fg(Red); + skin.bold.set_fg(DarkBlue); + skin.italic.set_fg(Red); + skin.quote_mark.set_fg(DarkBlue); + skin.table.set_fg(DarkGreen); + skin.table.align = Alignment::Center; + skin.inline_code.set_fgbg(Cyan, Reset); + skin.write_text_on( + write, + &response.rdap.to_md(MdParams { + heading_level: 1, + root: &response.rdap, + http_data: &response.http_data, + parent_type: response.rdap.get_type(), + check_types: &processing_params.check_types, + options: &MdOptions::default(), + req_data, + }), + )?; + } + OutputType::Markdown => { + writeln!( + write, + "{}", + response.rdap.to_md(MdParams { + heading_level: 1, + root: &response.rdap, + http_data: &response.http_data, + parent_type: response.rdap.get_type(), + check_types: &processing_params.check_types, + options: &MdOptions { + text_style_char: '_', + style_in_justify: true, + ..MdOptions::default() + }, + req_data, + }) + )?; + } + OutputType::GtldWhois => { + let mut params = GtldParams { + root: &response.rdap, + parent_type: response.rdap.get_type(), + label: "".to_string(), + }; + writeln!(write, "{}", response.rdap.to_gtld_whois(&mut params))?; + } + _ => {} // do nothing + }; + + let req_res = RequestResponse { + checks: do_output_checks(response), + req_data, + res_data: response, + }; + transactions.push(req_res); + Ok(transactions) +} + +fn do_no_output<'a>( + _processing_params: &ProcessingParams, + req_data: &'a RequestData, + response: &'a ResponseData, + mut transactions: RequestResponses<'a>, +) -> RequestResponses<'a> { + let req_res = RequestResponse { + checks: do_output_checks(response), + req_data, + res_data: response, + }; + transactions.push(req_res); + transactions +} + +fn do_output_checks(response: &ResponseData) -> Checks { + let check_params = CheckParams { + do_subchecks: true, + root: &response.rdap, + parent_type: response.rdap.get_type(), + allow_unreg_ext: false, + }; + let mut checks = response.rdap.get_checks(check_params); + checks + .items + .append(&mut response.http_data.get_checks(check_params).items); + checks +} + +fn do_final_output( + processing_params: &ProcessingParams, + write: &mut W, + transactions: RequestResponses<'_>, +) -> Result<(), RdapCliError> { + match processing_params.output_type { + OutputType::Json => { + for req_res in &transactions { + writeln!( + write, + "{}", + serde_json::to_string(&req_res.res_data.rdap).unwrap() + )?; + } + } + OutputType::PrettyJson => { + for req_res in &transactions { + writeln!( + write, + "{}", + serde_json::to_string_pretty(&req_res.res_data.rdap).unwrap() + )?; + } + } + OutputType::JsonExtra => { + writeln!(write, "{}", serde_json::to_string(&transactions).unwrap())? + } + OutputType::GtldWhois => {} + OutputType::Url => { + for rr in &transactions { + if let Some(url) = rr.res_data.http_data.request_uri() { + writeln!(write, "{url}")?; + } + } + } + _ => {} // do nothing + }; + + let mut checks_found = false; + // we don't want to error on informational + let error_check_types: Vec = processing_params + .check_types + .iter() + .filter(|ct| *ct != &CheckClass::Informational) + .copied() + .collect(); + for req_res in &transactions { + let found = traverse_checks( + &req_res.checks, + &error_check_types, + None, + &mut |struct_tree, check_item| { + if processing_params.error_on_checks { + error!("{struct_tree} -> {check_item}") + } + }, + ); + if found { + checks_found = true + } + } + if checks_found && processing_params.error_on_checks { + return Err(RdapCliError::ErrorOnChecks); + } + + Ok(()) +} diff --git a/icann-rdap-cli/src/bin/rdap/request.rs b/icann-rdap-cli/src/bin/rdap/request.rs new file mode 100644 index 0000000..7881cdb --- /dev/null +++ b/icann-rdap-cli/src/bin/rdap/request.rs @@ -0,0 +1,88 @@ +use std::{ + fs::{self, File}, + io::{BufRead, BufReader}, +}; + +use { + icann_rdap_client::{ + http::Client, + rdap::{rdap_url_request, QueryType, ResponseData}, + }, + icann_rdap_common::{httpdata::HttpData, response::GetSelfLink}, + pct_str::{PctString, URIReserved}, + tracing::{debug, info}, +}; + +use crate::{dirs::rdap_cache_path, error::RdapCliError, query::ProcessingParams}; + +pub(crate) async fn do_request( + base_url: &str, + query_type: &QueryType, + processing_params: &ProcessingParams, + client: &Client, +) -> Result { + if processing_params.no_cache { + info!("Cache has been disabled.") + } + let query_url = query_type.query_url(base_url)?; + if !processing_params.no_cache { + let file_name = format!( + "{}.cache", + PctString::encode(query_url.chars(), URIReserved) + ); + let path = rdap_cache_path().join(&file_name); + if path.exists() { + let input = File::open(path)?; + let buf = BufReader::new(input); + let mut lines = vec![]; + for line in buf.lines() { + lines.push(line?) + } + let cache_data = HttpData::from_lines(&lines)?; + if !cache_data + .0 + .is_expired(processing_params.max_cache_age as i64) + { + debug!("Returning response from cache file {file_name}"); + let response: ResponseData = serde_json::from_str(&cache_data.1.join(""))?; + return Ok(response); + } + } + } + let response = rdap_url_request(&query_url, client).await?; + if !processing_params.no_cache { + if response.http_data.should_cache() { + let data = serde_json::to_string_pretty(&response)?; + let cache_contents = response.http_data.to_lines(&data)?; + let query_url = query_type.query_url(base_url)?; + let file_name = format!( + "{}.cache", + PctString::encode(query_url.chars(), URIReserved) + ); + debug!("Saving query response to cache file {file_name}"); + let path = rdap_cache_path().join(file_name); + fs::write(path, &cache_contents)?; + if let Some(self_link) = response.rdap.get_self_link() { + if let Some(self_link_href) = &self_link.href { + if query_url != *self_link_href { + let file_name = format!( + "{}.cache", + PctString::encode(self_link_href.chars(), URIReserved) + ); + debug!("Saving object with self link to cache file {file_name}"); + let path = rdap_cache_path().join(file_name); + fs::write(path, &cache_contents)?; + } + } + } + } else { + debug!("Not caching data according to server policy."); + debug!("Expires header: {:?}", &response.http_data.expires); + debug!( + "Cache-control header: {:?}", + &response.http_data.cache_control + ); + } + } + Ok(response) +} diff --git a/icann-rdap-cli/src/bin/rdap/write.rs b/icann-rdap-cli/src/bin/rdap/write.rs new file mode 100644 index 0000000..b31491d --- /dev/null +++ b/icann-rdap-cli/src/bin/rdap/write.rs @@ -0,0 +1,35 @@ +use std::io::ErrorKind; + +use minus::Pager; + +#[derive(Clone)] +pub(crate) struct FmtWrite(pub(crate) W); + +impl std::io::Write for FmtWrite { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.0 + .write_str(&String::from_utf8_lossy(buf)) + .map_err(|e| std::io::Error::new(ErrorKind::Other, e))?; + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +#[derive(Clone)] +pub(crate) struct PagerWrite(pub(crate) Pager); + +impl std::io::Write for PagerWrite { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.0 + .push_str(String::from_utf8_lossy(buf)) + .map_err(|e| std::io::Error::new(ErrorKind::Other, e))?; + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} diff --git a/icann-rdap-cli/src/dirs/fcbs.rs b/icann-rdap-cli/src/dirs/fcbs.rs new file mode 100644 index 0000000..b65afc6 --- /dev/null +++ b/icann-rdap-cli/src/dirs/fcbs.rs @@ -0,0 +1,389 @@ +use std::{ + fs::{self, File}, + io::{BufRead, BufReader}, + path::PathBuf, +}; + +use { + icann_rdap_client::iana::{BootstrapStore, RegistryHasNotExpired}, + icann_rdap_common::{ + httpdata::HttpData, + iana::{BootstrapRegistry, IanaRegistry, IanaRegistryType}, + }, + tracing::debug, +}; + +use super::bootstrap_cache_path; + +pub struct FileCacheBootstrapStore; + +impl BootstrapStore for FileCacheBootstrapStore { + fn has_bootstrap_registry( + &self, + reg_type: &IanaRegistryType, + ) -> Result { + let path = bootstrap_cache_path().join(reg_type.file_name()); + if path.exists() { + let fc_reg = fetch_file_cache_bootstrap(path, |s| debug!("Checking for {s}"))?; + return Ok(Some(fc_reg).registry_has_not_expired()); + } + Ok(false) + } + + fn put_bootstrap_registry( + &self, + reg_type: &IanaRegistryType, + registry: IanaRegistry, + http_data: HttpData, + ) -> Result<(), icann_rdap_client::RdapClientError> { + let path = bootstrap_cache_path().join(reg_type.file_name()); + let data = serde_json::to_string_pretty(®istry)?; + let cache_contents = http_data.to_lines(&data)?; + fs::write(path, cache_contents)?; + Ok(()) + } + + fn get_dns_urls(&self, ldh: &str) -> Result, icann_rdap_client::RdapClientError> { + let path = bootstrap_cache_path().join(IanaRegistryType::RdapBootstrapDns.file_name()); + let (iana, _http_data) = fetch_file_cache_bootstrap(path, |s| debug!("Reading {s}"))?; + Ok(iana.get_dns_bootstrap_urls(ldh)?) + } + + fn get_asn_urls(&self, asn: &str) -> Result, icann_rdap_client::RdapClientError> { + let path = bootstrap_cache_path().join(IanaRegistryType::RdapBootstrapAsn.file_name()); + let (iana, _http_data) = fetch_file_cache_bootstrap(path, |s| debug!("Reading {s}"))?; + Ok(iana.get_asn_bootstrap_urls(asn)?) + } + + fn get_ipv4_urls(&self, ipv4: &str) -> Result, icann_rdap_client::RdapClientError> { + let path = bootstrap_cache_path().join(IanaRegistryType::RdapBootstrapIpv4.file_name()); + let (iana, _http_data) = fetch_file_cache_bootstrap(path, |s| debug!("Reading {s}"))?; + Ok(iana.get_ipv4_bootstrap_urls(ipv4)?) + } + + fn get_ipv6_urls(&self, ipv6: &str) -> Result, icann_rdap_client::RdapClientError> { + let path = bootstrap_cache_path().join(IanaRegistryType::RdapBootstrapIpv6.file_name()); + let (iana, _http_data) = fetch_file_cache_bootstrap(path, |s| debug!("Reading {s}"))?; + Ok(iana.get_ipv6_bootstrap_urls(ipv6)?) + } + + fn get_tag_urls(&self, tag: &str) -> Result, icann_rdap_client::RdapClientError> { + let path = bootstrap_cache_path().join(IanaRegistryType::RdapObjectTags.file_name()); + let (iana, _http_data) = fetch_file_cache_bootstrap(path, |s| debug!("Reading {s}"))?; + Ok(iana.get_tag_bootstrap_urls(tag)?) + } +} + +pub fn fetch_file_cache_bootstrap( + path: PathBuf, + callback: F, +) -> Result<(IanaRegistry, HttpData), std::io::Error> +where + F: FnOnce(String), +{ + let input = File::open(&path)?; + let buf = BufReader::new(input); + let mut lines = vec![]; + for line in buf.lines() { + lines.push(line?); + } + let cache_data = HttpData::from_lines(&lines)?; + callback(path.display().to_string()); + let iana: IanaRegistry = serde_json::from_str(&cache_data.1.join(""))?; + Ok((iana, cache_data.0)) +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod test { + use { + icann_rdap_client::{ + iana::{BootstrapStore, PreferredUrl}, + rdap::QueryType, + }, + icann_rdap_common::{ + httpdata::HttpData, + iana::{IanaRegistry, IanaRegistryType}, + }, + serial_test::serial, + test_dir::{DirBuilder, FileType, TestDir}, + }; + + use crate::dirs::{self, fcbs::FileCacheBootstrapStore}; + + fn test_dir() -> TestDir { + let test_dir = TestDir::temp() + .create("cache", FileType::Dir) + .create("config", FileType::Dir); + std::env::set_var("XDG_CACHE_HOME", test_dir.path("cache")); + std::env::set_var("XDG_CONFIG_HOME", test_dir.path("config")); + dirs::init().expect("unable to init directories"); + test_dir + } + + #[test] + #[serial] + fn GIVEN_fcbootstrap_with_dns_WHEN_get_domain_query_url_THEN_correct_url() { + // GIVEN + let _test_dir = test_dir(); + let bs = FileCacheBootstrapStore; + let bootstrap = r#" + { + "version": "1.0", + "publication": "2024-01-07T10:11:12Z", + "description": "Some text", + "services": [ + [ + ["net", "com"], + [ + "https://registry.example.com/myrdap/" + ] + ], + [ + ["org", "mytld"], + [ + "https://example.org/" + ] + ] + ] + } + "#; + let iana = + serde_json::from_str::(bootstrap).expect("cannot parse domain bootstrap"); + bs.put_bootstrap_registry( + &IanaRegistryType::RdapBootstrapDns, + iana, + HttpData::example().build(), + ) + .expect("put iana registry"); + + // WHEN + let actual = bs + .get_domain_query_urls(&QueryType::domain("example.org").expect("invalid domain name")) + .expect("get bootstrap url") + .preferred_url() + .expect("preferred url"); + + // THEN + assert_eq!(actual, "https://example.org/") + } + + #[test] + #[serial] + fn GIVEN_fcbootstrap_with_autnum_WHEN_get_autnum_query_url_THEN_correct_url() { + // GIVEN + let _test_dir = test_dir(); + let bs = FileCacheBootstrapStore; + let bootstrap = r#" + { + "version": "1.0", + "publication": "2024-01-07T10:11:12Z", + "description": "RDAP Bootstrap file for example registries.", + "services": [ + [ + ["64496-64496"], + [ + "https://rir3.example.com/myrdap/" + ] + ], + [ + ["64497-64510", "65536-65551"], + [ + "https://example.org/" + ] + ], + [ + ["64512-65534"], + [ + "http://example.net/rdaprir2/", + "https://example.net/rdaprir2/" + ] + ] + ] + } + "#; + let iana = + serde_json::from_str::(bootstrap).expect("cannot parse autnum bootstrap"); + bs.put_bootstrap_registry( + &IanaRegistryType::RdapBootstrapAsn, + iana, + HttpData::example().build(), + ) + .expect("put iana registry"); + + // WHEN + let actual = bs + .get_autnum_query_urls(&QueryType::autnum("as64512").expect("invalid autnum")) + .expect("get bootstrap url") + .preferred_url() + .expect("preferred url"); + + // THEN + assert_eq!(actual, "https://example.net/rdaprir2/"); + } + + #[test] + #[serial] + fn GIVEN_fcbootstrap_with_ipv4_THEN_get_ipv4_query_urls_THEN_correct_url() { + // GIVEN + let _test_dir = test_dir(); + let bs = FileCacheBootstrapStore; + let bootstrap = r#" + { + "version": "1.0", + "publication": "2024-01-07T10:11:12Z", + "description": "RDAP Bootstrap file for example registries.", + "services": [ + [ + ["198.51.100.0/24", "192.0.0.0/8"], + [ + "https://rir1.example.com/myrdap/" + ] + ], + [ + ["203.0.113.0/24", "192.0.2.0/24"], + [ + "https://example.org/" + ] + ], + [ + ["203.0.113.0/28"], + [ + "https://example.net/rdaprir2/", + "http://example.net/rdaprir2/" + ] + ] + ] + } + "#; + let iana = + serde_json::from_str::(bootstrap).expect("cannot parse autnum bootstrap"); + bs.put_bootstrap_registry( + &IanaRegistryType::RdapBootstrapIpv4, + iana, + HttpData::example().build(), + ) + .expect("put iana registry"); + + // WHEN + let actual = bs + .get_ipv4_query_urls(&QueryType::ipv4("198.51.100.1").expect("invalid IP address")) + .expect("get bootstrap url") + .preferred_url() + .expect("preferred url"); + + // THEN + assert_eq!(actual, "https://rir1.example.com/myrdap/"); + } + + #[test] + #[serial] + fn GIVEN_fcbootstrap_with_ipv6_THEN_get_ipv6_query_urls_THEN_correct_url() { + // GIVEN + let _test_dir = test_dir(); + let bs = FileCacheBootstrapStore; + let bootstrap = r#" + { + "version": "1.0", + "publication": "2024-01-07T10:11:12Z", + "description": "RDAP Bootstrap file for example registries.", + "services": [ + [ + ["2001:db8::/34"], + [ + "https://rir2.example.com/myrdap/" + ] + ], + [ + ["2001:db8:4000::/36", "2001:db8:ffff::/48"], + [ + "https://example.org/" + ] + ], + [ + ["2001:db8:1000::/36"], + [ + "https://example.net/rdaprir2/", + "http://example.net/rdaprir2/" + ] + ] + ] + } + "#; + let iana = + serde_json::from_str::(bootstrap).expect("cannot parse autnum bootstrap"); + bs.put_bootstrap_registry( + &IanaRegistryType::RdapBootstrapIpv6, + iana, + HttpData::example().build(), + ) + .expect("put iana registry"); + + // WHEN + let actual = bs + .get_ipv6_query_urls(&QueryType::ipv6("2001:db8::1").expect("invalid IP address")) + .expect("get bootstrap url") + .preferred_url() + .expect("preferred url"); + + // THEN + assert_eq!(actual, "https://rir2.example.com/myrdap/"); + } + + #[test] + #[serial] + fn GIVEN_fcbootstrap_with_tag_THEN_get_entity_handle_query_urls_THEN_correct_url() { + // GIVEN + let _test_dir = test_dir(); + let bs = FileCacheBootstrapStore; + let bootstrap = r#" + { + "version": "1.0", + "publication": "YYYY-MM-DDTHH:MM:SSZ", + "description": "RDAP bootstrap file for service provider object tags", + "services": [ + [ + ["contact@example.com"], + ["YYYY"], + [ + "https://example.com/rdap/" + ] + ], + [ + ["contact@example.org"], + ["ZZ54"], + [ + "http://rdap.example.org/" + ] + ], + [ + ["contact@example.net"], + ["1754"], + [ + "https://example.net/rdap/", + "http://example.net/rdap/" + ] + ] + ] + } + "#; + let iana = + serde_json::from_str::(bootstrap).expect("cannot parse autnum bootstrap"); + bs.put_bootstrap_registry( + &IanaRegistryType::RdapObjectTags, + iana, + HttpData::example().build(), + ) + .expect("put iana registry"); + + // WHEN + let actual = bs + .get_entity_handle_query_urls(&QueryType::Entity("foo-YYYY".to_string())) + .expect("get bootstrap url") + .preferred_url() + .expect("preferred url"); + + // THEN + assert_eq!(actual, "https://example.com/rdap/"); + } +} diff --git a/icann-rdap-cli/src/dirs/mod.rs b/icann-rdap-cli/src/dirs/mod.rs new file mode 100644 index 0000000..e2b0eb7 --- /dev/null +++ b/icann-rdap-cli/src/dirs/mod.rs @@ -0,0 +1,4 @@ +pub mod fcbs; +pub mod project; + +pub use project::*; diff --git a/icann-rdap-cli/src/dirs/project.rs b/icann-rdap-cli/src/dirs/project.rs new file mode 100644 index 0000000..e1b6173 --- /dev/null +++ b/icann-rdap-cli/src/dirs/project.rs @@ -0,0 +1,57 @@ +use std::{ + fs::{create_dir_all, remove_dir_all, write}, + path::PathBuf, + sync::LazyLock, +}; + +use directories::ProjectDirs; + +pub const QUALIFIER: &str = "org"; +pub const ORGANIZATION: &str = "ICANN"; +pub const APPLICATION: &str = "rdap"; + +pub const ENV_FILE_NAME: &str = "rdap.env"; +pub const RDAP_CACHE_NAME: &str = "rdap_cache"; +pub const BOOTSTRAP_CACHE_NAME: &str = "bootstrap_cache"; + +pub(crate) static PROJECT_DIRS: LazyLock = LazyLock::new(|| { + ProjectDirs::from(QUALIFIER, ORGANIZATION, APPLICATION) + .expect("unable to formulate project directories") +}); + +/// Initializes the directories to be used. +pub fn init() -> Result<(), std::io::Error> { + create_dir_all(PROJECT_DIRS.config_dir())?; + create_dir_all(PROJECT_DIRS.cache_dir())?; + create_dir_all(rdap_cache_path())?; + create_dir_all(bootstrap_cache_path())?; + + // create default config file + if !config_path().exists() { + let example_config = include_str!("rdap.env"); + write(config_path(), example_config)?; + } + Ok(()) +} + +/// Reset the directories. +pub fn reset() -> Result<(), std::io::Error> { + remove_dir_all(PROJECT_DIRS.config_dir())?; + remove_dir_all(PROJECT_DIRS.cache_dir())?; + init() +} + +/// Returns a [PathBuf] to the configuration file. +pub fn config_path() -> PathBuf { + PROJECT_DIRS.config_dir().join(ENV_FILE_NAME) +} + +/// Returns a [PathBuf] to the cache directory for RDAP responses. +pub fn rdap_cache_path() -> PathBuf { + PROJECT_DIRS.cache_dir().join(RDAP_CACHE_NAME) +} + +/// Returns a [PathBuf] to the cache directory for bootstrap files. +pub fn bootstrap_cache_path() -> PathBuf { + PROJECT_DIRS.cache_dir().join(BOOTSTRAP_CACHE_NAME) +} diff --git a/icann-rdap-cli/src/dirs/rdap.env b/icann-rdap-cli/src/dirs/rdap.env new file mode 100644 index 0000000..b13ed2b --- /dev/null +++ b/icann-rdap-cli/src/dirs/rdap.env @@ -0,0 +1,40 @@ +# This file controls the environment variables for the RDAP CLI. +# The file format is that of a shell script setting variables. + +# Use --help to determine the active values. + +# Sets the logging level. Valid values are off, error, warn, info, debug, and trace. +#RDAP_LOG=info + +# Determines if output is sent to a pager. Valid values are embedded, none, and auto. +#RDAP_PAGING=none + +# Determines the output format of the output. Valid values are markdown, rendered-markdown, pretty-json, json, json-extra, and auto. +#RDAP_OUTPUT=auto + +# Sets a base URL from a name in the RDAP bootstrap registry. +#RDAP_BASE= + +# Sets a base URL explicitly. +#RDAP_BASE_URL + +# Where to lookup TLDs +#RDAP_TLD_LOOKUP=iana + +# Which base URL to use if no IP address or autnum bootstrap can be found. +#RDAP_INR_BACKUP_BOOTSTRAP=arin + +# Do not use cache. +#RDAP_NO_CACHE=true + +# The maximum age of an item in the cache. +#RDAP_MAX_CACHE_AGE=86400 + +# Allow HTTP connections +#RDAP_ALLOW_HTTP=true + +# Allow invalid host names in HTTPS. +#RDAP_ALLOW_INVALID_HOST_NAMES=true + +# Allow invalid certificates in HTTPS. +#RDAP_ALLOW_INVALID_CERTIFICATES=true diff --git a/icann-rdap-cli/src/lib.rs b/icann-rdap-cli/src/lib.rs new file mode 100644 index 0000000..b72a293 --- /dev/null +++ b/icann-rdap-cli/src/lib.rs @@ -0,0 +1,2 @@ +pub mod dirs; +pub mod rt; diff --git a/icann-rdap-cli/src/rt/exec.rs b/icann-rdap-cli/src/rt/exec.rs new file mode 100644 index 0000000..5889474 --- /dev/null +++ b/icann-rdap-cli/src/rt/exec.rs @@ -0,0 +1,463 @@ +//! Function to execute tests. + +use std::{ + net::{Ipv4Addr, Ipv6Addr}, + str::FromStr, +}; + +use { + hickory_client::{ + client::{AsyncClient, ClientConnection, ClientHandle}, + rr::{DNSClass, Name, RecordType}, + udp::UdpClientConnection, + }, + icann_rdap_client::{ + http::{create_client, create_client_with_addr, ClientConfig}, + iana::{qtype_to_bootstrap_url, BootstrapStore}, + rdap::{rdap_url_request, QueryType}, + RdapClientError, + }, + icann_rdap_common::response::{get_related_links, ExtensionId}, + reqwest::{header::HeaderValue, Url}, + thiserror::Error, + tracing::{debug, info}, + url::ParseError, +}; + +use crate::rt::results::{RunFeature, TestRun}; + +use super::results::{DnsData, TestResults}; + +#[derive(Default)] +pub struct TestOptions { + pub skip_v4: bool, + pub skip_v6: bool, + pub skip_origin: bool, + pub origin_value: String, + pub chase_referral: bool, + pub expect_extensions: Vec, + pub expect_groups: Vec, + pub allow_unregistered_extensions: bool, + pub one_addr: bool, + pub dns_resolver: Option, +} + +#[derive(Clone)] +pub enum ExtensionGroup { + Gtld, + Nro, + NroAsn, +} + +#[derive(Debug, Error)] +pub enum TestExecutionError { + #[error(transparent)] + RdapClient(#[from] RdapClientError), + #[error(transparent)] + UrlParseError(#[from] ParseError), + #[error(transparent)] + AddrParseError(#[from] std::net::AddrParseError), + #[error("No host to resolve")] + NoHostToResolve, + #[error("No rdata")] + NoRdata, + #[error("Bad rdata")] + BadRdata, + #[error(transparent)] + Client(#[from] reqwest::Error), + #[error(transparent)] + InvalidHeader(#[from] reqwest::header::InvalidHeaderValue), + #[error("Unsupporte Query Type")] + UnsupportedQueryType, + #[error("No referral to chase")] + NoReferralToChase, + #[error("Unregistered extension")] + UnregisteredExtension, +} + +pub async fn execute_tests<'a, BS: BootstrapStore>( + bs: &BS, + value: &QueryType, + options: &TestOptions, + client_config: &ClientConfig, +) -> Result { + let bs_client = create_client(client_config)?; + + // normalize extensions + let extensions = normalize_extension_ids(options)?; + let options = &TestOptions { + expect_extensions: extensions, + expect_groups: options.expect_groups.clone(), + origin_value: options.origin_value.clone(), + dns_resolver: options.dns_resolver.clone(), + ..*options + }; + + // get the query url + let mut query_url = match value { + QueryType::Help => return Err(TestExecutionError::UnsupportedQueryType), + QueryType::Url(url) => url.to_owned(), + _ => { + let base_url = qtype_to_bootstrap_url(&bs_client, bs, value, |reg| { + info!("Fetching IANA registry {} for value {value}", reg.url()) + }) + .await?; + value.query_url(&base_url)? + } + }; + // if the URL to test is a referral + if options.chase_referral { + let client = create_client(client_config)?; + info!("Fetching referral from {query_url}"); + let response_data = rdap_url_request(&query_url, &client).await?; + query_url = get_related_links(&response_data.rdap) + .first() + .ok_or(TestExecutionError::NoReferralToChase)? + .to_string(); + info!("Referral is {query_url}"); + } + + let parsed_url = Url::parse(&query_url)?; + let port = parsed_url.port().unwrap_or_else(|| { + if parsed_url.scheme().eq("https") { + 443 + } else { + 80 + } + }); + let host = parsed_url + .host_str() + .ok_or(TestExecutionError::NoHostToResolve)?; + + info!("Testing {query_url}"); + let dns_data = get_dns_records(host, options).await?; + let mut test_results = TestResults::new(query_url.clone(), dns_data.clone()); + + let mut more_runs = true; + for v4 in dns_data.v4_addrs { + // test run without origin + let mut test_run = TestRun::new_v4(vec![], v4, port); + if !options.skip_v4 && more_runs { + let client = create_client_with_addr(client_config, host, test_run.socket_addr)?; + info!("Sending request to {}", test_run.socket_addr); + let rdap_response = rdap_url_request(&query_url, &client).await; + test_run = test_run.end(rdap_response, options); + } + test_results.add_test_run(test_run); + + // test run with origin + let mut test_run = TestRun::new_v4(vec![RunFeature::OriginHeader], v4, port); + if !options.skip_v4 && !options.skip_origin && more_runs { + let client_config = ClientConfig::from_config(client_config) + .origin(HeaderValue::from_str(&options.origin_value)?) + .build(); + let client = create_client_with_addr(&client_config, host, test_run.socket_addr)?; + info!("Sending request to {}", test_run.socket_addr); + let rdap_response = rdap_url_request(&query_url, &client).await; + test_run = test_run.end(rdap_response, options); + } + test_results.add_test_run(test_run); + if options.one_addr { + more_runs = false; + } + } + + let mut more_runs = true; + for v6 in dns_data.v6_addrs { + // test run without origin + let mut test_run = TestRun::new_v6(vec![], v6, port); + if !options.skip_v6 && more_runs { + let client = create_client_with_addr(client_config, host, test_run.socket_addr)?; + info!("Sending request to {}", test_run.socket_addr); + let rdap_response = rdap_url_request(&query_url, &client).await; + test_run = test_run.end(rdap_response, options); + } + test_results.add_test_run(test_run); + + // test run with origin + let mut test_run = TestRun::new_v6(vec![RunFeature::OriginHeader], v6, port); + if !options.skip_v6 && !options.skip_origin && more_runs { + let client_config = ClientConfig::from_config(client_config) + .origin(HeaderValue::from_str(&options.origin_value)?) + .build(); + let client = create_client_with_addr(&client_config, host, test_run.socket_addr)?; + info!("Sending request to {}", test_run.socket_addr); + let rdap_response = rdap_url_request(&query_url, &client).await; + test_run = test_run.end(rdap_response, options); + } + test_results.add_test_run(test_run); + if options.one_addr { + more_runs = false; + } + } + + test_results.end(options); + info!("Testing complete."); + Ok(test_results) +} + +async fn get_dns_records(host: &str, options: &TestOptions) -> Result { + // short circuit dns if these are ip addresses + if let Ok(ip4) = Ipv4Addr::from_str(host) { + return Ok(DnsData { + v4_cname: None, + v6_cname: None, + v4_addrs: vec![ip4], + v6_addrs: vec![], + }); + } else if let Ok(ip6) = Ipv6Addr::from_str(host.trim_start_matches('[').trim_end_matches(']')) { + return Ok(DnsData { + v4_cname: None, + v6_cname: None, + v4_addrs: vec![], + v6_addrs: vec![ip6], + }); + } + + let def_dns_resolver = "8.8.8.8:53".to_string(); + let dns_resolver = options.dns_resolver.as_ref().unwrap_or(&def_dns_resolver); + let conn = UdpClientConnection::new(dns_resolver.parse()?) + .unwrap() + .new_stream(None); + let (mut client, bg) = AsyncClient::connect(conn).await.unwrap(); + + // make sure to run the background task + tokio::spawn(bg); + + let mut dns_data = DnsData::default(); + + // Create a query future + let query = client.query(Name::from_str(host).unwrap(), DNSClass::IN, RecordType::A); + + // wait for its response + let response = query.await.unwrap(); + + for answer in response.answers() { + match answer.record_type() { + RecordType::CNAME => { + let cname = answer + .data() + .ok_or(TestExecutionError::NoRdata)? + .clone() + .into_cname() + .map_err(|_e| TestExecutionError::BadRdata)? + .0 + .to_string(); + debug!("Found cname {cname}"); + dns_data.v4_cname = Some(cname); + } + RecordType::A => { + let addr = answer + .data() + .ok_or(TestExecutionError::NoRdata)? + .clone() + .into_a() + .map_err(|_e| TestExecutionError::BadRdata)? + .0; + debug!("Found IPv4 {addr}"); + dns_data.v4_addrs.push(addr); + } + _ => { + // do nothing + } + }; + } + + // Create a query future + let query = client.query( + Name::from_str(host).unwrap(), + DNSClass::IN, + RecordType::AAAA, + ); + + // wait for its response + let response = query.await.unwrap(); + + for answer in response.answers() { + match answer.record_type() { + RecordType::CNAME => { + let cname = answer + .data() + .ok_or(TestExecutionError::NoRdata)? + .clone() + .into_cname() + .map_err(|_e| TestExecutionError::BadRdata)? + .0 + .to_string(); + debug!("Found cname {cname}"); + dns_data.v6_cname = Some(cname); + } + RecordType::AAAA => { + let addr = answer + .data() + .ok_or(TestExecutionError::NoRdata)? + .clone() + .into_aaaa() + .map_err(|_e| TestExecutionError::BadRdata)? + .0; + debug!("Found IPv6 {addr}"); + dns_data.v6_addrs.push(addr); + } + _ => { + // do nothing + } + }; + } + + Ok(dns_data) +} + +fn normalize_extension_ids(options: &TestOptions) -> Result, TestExecutionError> { + let mut retval = options.expect_extensions.clone(); + + // check for unregistered extensions + if !options.allow_unregistered_extensions { + for ext in &retval { + if ExtensionId::from_str(ext).is_err() { + return Err(TestExecutionError::UnregisteredExtension); + } + } + } + + // put the groups in + for group in &options.expect_groups { + match group { + ExtensionGroup::Gtld => { + retval.push(format!( + "{}|{}", + ExtensionId::IcannRdapResponseProfile0, + ExtensionId::IcannRdapResponseProfile1 + )); + retval.push(format!( + "{}|{}", + ExtensionId::IcannRdapTechnicalImplementationGuide0, + ExtensionId::IcannRdapTechnicalImplementationGuide1 + )); + } + ExtensionGroup::Nro => { + retval.push(ExtensionId::NroRdapProfile0.to_string()); + retval.push(ExtensionId::Cidr0.to_string()); + } + ExtensionGroup::NroAsn => { + retval.push(ExtensionId::NroRdapProfile0.to_string()); + retval.push(format!( + "{}|{}", + ExtensionId::NroRdapProfileAsnFlat0, + ExtensionId::NroRdapProfileAsnHierarchical0 + )); + } + } + } + Ok(retval) +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use icann_rdap_common::response::ExtensionId; + + use crate::rt::exec::{ExtensionGroup, TestOptions}; + + use super::normalize_extension_ids; + + #[test] + fn GIVEN_gtld_WHEN_normalize_extensions_THEN_list_contains_gtld_ids() { + // GIVEN + let given = vec![ExtensionGroup::Gtld]; + + // WHEN + let options = TestOptions { + expect_groups: given, + ..Default::default() + }; + let actual = normalize_extension_ids(&options).unwrap(); + + // THEN + let expected1 = format!( + "{}|{}", + ExtensionId::IcannRdapResponseProfile0, + ExtensionId::IcannRdapResponseProfile1 + ); + assert!(actual.contains(&expected1)); + + let expected2 = format!( + "{}|{}", + ExtensionId::IcannRdapTechnicalImplementationGuide0, + ExtensionId::IcannRdapTechnicalImplementationGuide1 + ); + assert!(actual.contains(&expected2)); + } + + #[test] + fn GIVEN_nro_and_foo_WHEN_normalize_extensions_THEN_list_contains_nro_ids_and_foo() { + // GIVEN + let groups = vec![ExtensionGroup::Nro]; + let exts = vec!["foo1".to_string()]; + + // WHEN + let options = TestOptions { + allow_unregistered_extensions: true, + expect_extensions: exts, + expect_groups: groups, + ..Default::default() + }; + let actual = normalize_extension_ids(&options).unwrap(); + dbg!(&actual); + + // THEN + assert!(actual.contains(&ExtensionId::NroRdapProfile0.to_string())); + assert!(actual.contains(&ExtensionId::Cidr0.to_string())); + assert!(actual.contains(&"foo1".to_string())); + } + + #[test] + fn GIVEN_nro_and_foo_WHEN_unreg_disallowed_THEN_err() { + // GIVEN + let groups = vec![ExtensionGroup::Nro]; + let exts = vec!["foo1".to_string()]; + + // WHEN + let options = TestOptions { + expect_extensions: exts, + expect_groups: groups, + ..Default::default() + }; + let actual = normalize_extension_ids(&options); + + // THEN + assert!(actual.is_err()) + } + + #[test] + fn GIVEN_unregistered_ext_WHEN_normalize_extensions_THEN_error() { + // GIVEN + let given = vec!["foo".to_string()]; + + // WHEN + let options = TestOptions { + expect_extensions: given, + ..Default::default() + }; + let actual = normalize_extension_ids(&options); + + // THEN + assert!(actual.is_err()); + } + + #[test] + fn GIVEN_unregistered_ext_WHEN_allowed_THEN_no_error() { + // GIVEN + let given = vec!["foo".to_string()]; + + // WHEN + let options = TestOptions { + expect_extensions: given, + allow_unregistered_extensions: true, + ..Default::default() + }; + let actual = normalize_extension_ids(&options); + + // THEN + assert!(actual.is_ok()); + } +} diff --git a/icann-rdap-cli/src/rt/mod.rs b/icann-rdap-cli/src/rt/mod.rs new file mode 100644 index 0000000..50a49da --- /dev/null +++ b/icann-rdap-cli/src/rt/mod.rs @@ -0,0 +1,2 @@ +pub mod exec; +pub mod results; diff --git a/icann-rdap-cli/src/rt/results.rs b/icann-rdap-cli/src/rt/results.rs new file mode 100644 index 0000000..777fa71 --- /dev/null +++ b/icann-rdap-cli/src/rt/results.rs @@ -0,0 +1,487 @@ +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; + +/// Contains the results of test execution. +use chrono::{DateTime, Utc}; +use { + icann_rdap_client::{ + md::{string::StringUtil, table::MultiPartTable, MdOptions}, + rdap::ResponseData, + RdapClientError, + }, + icann_rdap_common::{ + check::{traverse_checks, Check, CheckClass, CheckItem, CheckParams, Checks, GetChecks}, + response::{ExtensionId, RdapResponse}, + }, + reqwest::StatusCode, + serde::Serialize, + strum_macros::Display, +}; + +use super::exec::TestOptions; + +#[derive(Debug, Serialize)] +pub struct TestResults { + pub query_url: String, + pub dns_data: DnsData, + pub start_time: DateTime, + pub end_time: Option>, + pub service_checks: Vec, + pub test_runs: Vec, +} + +impl TestResults { + pub fn new(query_url: String, dns_data: DnsData) -> Self { + Self { + query_url, + dns_data, + start_time: Utc::now(), + end_time: None, + service_checks: vec![], + test_runs: vec![], + } + } + + pub fn end(&mut self, options: &TestOptions) { + self.end_time = Some(Utc::now()); + + //service checks + if self.dns_data.v4_cname.is_some() && self.dns_data.v4_addrs.is_empty() { + self.service_checks + .push(Check::CnameWithoutARecords.check_item()); + } + if self.dns_data.v6_cname.is_some() && self.dns_data.v6_addrs.is_empty() { + self.service_checks + .push(Check::CnameWithoutAAAARecords.check_item()); + } + if self.dns_data.v4_addrs.is_empty() { + self.service_checks.push(Check::NoARecords.check_item()); + } + if self.dns_data.v6_addrs.is_empty() { + self.service_checks.push(Check::NoAAAARecords.check_item()); + + // see if required by ICANN + let tig0 = ExtensionId::IcannRdapTechnicalImplementationGuide0.to_string(); + let tig1 = ExtensionId::IcannRdapTechnicalImplementationGuide1.to_string(); + let both_tigs = format!("{tig0}|{tig1}"); + if options.expect_extensions.contains(&tig0) + || options.expect_extensions.contains(&tig1) + || options.expect_extensions.contains(&both_tigs) + { + self.service_checks + .push(Check::Ipv6SupportRequiredByIcann.check_item()) + } + } + } + + pub fn add_test_run(&mut self, test_run: TestRun) { + self.test_runs.push(test_run); + } + + pub fn to_md(&self, options: &MdOptions, check_classes: &[CheckClass]) -> String { + let mut md = String::new(); + + // h1 + md.push_str(&format!( + "\n{}\n", + self.query_url.to_owned().to_header(1, options) + )); + + // table + let mut table = MultiPartTable::new(); + + // test results summary + table = table.multi_raw(vec![ + "Start Time".to_inline(options), + "End Time".to_inline(options), + "Duration".to_inline(options), + "Tested".to_inline(options), + ]); + let (end_time_s, duration_s) = if let Some(end_time) = self.end_time { + ( + format_date_time(end_time), + format!("{} s", (end_time - self.start_time).num_seconds()), + ) + } else { + ("FATAL".to_em(options), "N/A".to_string()) + }; + let tested = self + .test_runs + .iter() + .filter(|r| matches!(r.outcome, RunOutcome::Tested)) + .count(); + table = table.multi_raw(vec![ + format_date_time(self.start_time), + end_time_s, + duration_s, + format!("{tested} of {}", self.test_runs.len()), + ]); + + // dns data + table = table.multi_raw(vec![ + "DNS Query".to_inline(options), + "DNS Answer".to_inline(options), + ]); + let v4_cname = if let Some(ref cname) = self.dns_data.v4_cname { + cname.to_owned() + } else { + format!("{} A records", self.dns_data.v4_addrs.len()) + }; + table = table.multi_raw(vec!["A (v4)".to_string(), v4_cname]); + let v6_cname = if let Some(ref cname) = self.dns_data.v6_cname { + cname.to_owned() + } else { + format!("{} AAAA records", self.dns_data.v6_addrs.len()) + }; + table = table.multi_raw(vec!["AAAA (v6)".to_string(), v6_cname]); + + // summary of each run + table = table.multi_raw(vec![ + "Address".to_inline(options), + "Attributes".to_inline(options), + "Duration".to_inline(options), + "Outcome".to_inline(options), + ]); + for test_run in &self.test_runs { + table = test_run.add_summary(table, options); + } + md.push_str(&table.to_md_table(options)); + + md.push('\n'); + + // checks that are about the service and not a particular test run + if !self.service_checks.is_empty() { + md.push_str(&"Service Checks".to_string().to_header(1, options)); + let mut table = MultiPartTable::new(); + + table = table.multi_raw(vec!["Message".to_inline(options)]); + for c in &self.service_checks { + let message = check_item_md(c, options); + table = table.multi_raw(vec![message]); + } + md.push_str(&table.to_md_table(options)); + md.push('\n'); + } + + // each run in detail + for run in &self.test_runs { + md.push_str(&run.to_md(options, check_classes)); + } + md + } +} + +#[derive(Debug, Serialize, Clone, Default)] +pub struct DnsData { + pub v4_cname: Option, + pub v6_cname: Option, + pub v4_addrs: Vec, + pub v6_addrs: Vec, +} + +#[derive(Debug, Serialize, Display)] +#[strum(serialize_all = "SCREAMING_SNAKE_CASE")] +pub enum RunOutcome { + Tested, + NetworkError, + HttpProtocolError, + HttpConnectError, + HttpRedirectResponse, + HttpTimeoutError, + HttpNon200Error, + HttpTooManyRequestsError, + HttpNotFoundError, + HttpBadRequestError, + HttpUnauthorizedError, + HttpForbiddenError, + JsonError, + RdapDataError, + InternalError, + Skipped, +} + +#[derive(Debug, Serialize, Display)] +#[strum(serialize_all = "snake_case")] +pub enum RunFeature { + OriginHeader, +} + +impl RunOutcome { + pub fn to_md(&self, options: &MdOptions) -> String { + match self { + Self::Tested => self.to_bold(options), + Self::Skipped => self.to_string(), + _ => self.to_em(options), + } + } +} + +#[derive(Debug, Serialize)] +pub struct TestRun { + pub features: Vec, + pub socket_addr: SocketAddr, + pub start_time: DateTime, + pub end_time: Option>, + pub response_data: Option, + pub outcome: RunOutcome, + pub checks: Option, +} + +impl TestRun { + fn new(features: Vec, socket_addr: SocketAddr) -> Self { + Self { + features, + start_time: Utc::now(), + socket_addr, + end_time: None, + response_data: None, + outcome: RunOutcome::Skipped, + checks: None, + } + } + + pub fn new_v4(features: Vec, ipv4: Ipv4Addr, port: u16) -> Self { + Self::new(features, SocketAddr::new(IpAddr::V4(ipv4), port)) + } + + pub fn new_v6(features: Vec, ipv6: Ipv6Addr, port: u16) -> Self { + Self::new(features, SocketAddr::new(IpAddr::V6(ipv6), port)) + } + + pub fn end( + mut self, + rdap_response: Result, + options: &TestOptions, + ) -> Self { + if let Ok(response_data) = rdap_response { + self.end_time = Some(Utc::now()); + self.outcome = RunOutcome::Tested; + self.checks = Some(do_checks(&response_data, options)); + self.response_data = Some(response_data); + } else { + self.outcome = match rdap_response.err().unwrap() { + RdapClientError::InvalidQueryValue + | RdapClientError::AmbiquousQueryType + | RdapClientError::Poison + | RdapClientError::DomainNameError(_) + | RdapClientError::BootstrapUnavailable + | RdapClientError::BootstrapError(_) + | RdapClientError::IanaResponse(_) => RunOutcome::InternalError, + RdapClientError::Response(_) => RunOutcome::RdapDataError, + RdapClientError::Json(_) => RunOutcome::JsonError, + RdapClientError::ParsingError(e) => { + let status_code = e.http_data.status_code(); + if status_code > 299 && status_code < 400 { + RunOutcome::HttpRedirectResponse + } else { + RunOutcome::JsonError + } + } + RdapClientError::IoError(_) => RunOutcome::NetworkError, + RdapClientError::Client(e) => { + if e.is_redirect() { + RunOutcome::HttpRedirectResponse + } else if e.is_connect() { + RunOutcome::HttpConnectError + } else if e.is_timeout() { + RunOutcome::HttpTimeoutError + } else if e.is_status() { + match e.status().unwrap() { + StatusCode::TOO_MANY_REQUESTS => RunOutcome::HttpTooManyRequestsError, + StatusCode::NOT_FOUND => RunOutcome::HttpNotFoundError, + StatusCode::BAD_REQUEST => RunOutcome::HttpBadRequestError, + StatusCode::UNAUTHORIZED => RunOutcome::HttpUnauthorizedError, + StatusCode::FORBIDDEN => RunOutcome::HttpForbiddenError, + _ => RunOutcome::HttpNon200Error, + } + } else { + RunOutcome::HttpProtocolError + } + } + }; + self.end_time = Some(Utc::now()); + }; + self + } + + fn add_summary(&self, mut table: MultiPartTable, options: &MdOptions) -> MultiPartTable { + let duration_s = if let Some(end_time) = self.end_time { + format!("{} ms", (end_time - self.start_time).num_milliseconds()) + } else { + "n/a".to_string() + }; + table = table.multi_raw(vec![ + self.socket_addr.to_string(), + self.attribute_set(), + duration_s, + self.outcome.to_md(options), + ]); + table + } + + fn to_md(&self, options: &MdOptions, check_classes: &[CheckClass]) -> String { + let mut md = String::new(); + + // h1 + let header_value = format!("{} - {}", self.socket_addr, self.attribute_set()); + md.push_str(&format!("\n{}\n", header_value.to_header(1, options))); + + // if outcome is tested + if matches!(self.outcome, RunOutcome::Tested) { + // get check items according to class + let mut check_v: Vec<(String, String)> = vec![]; + if let Some(ref checks) = self.checks { + traverse_checks(checks, check_classes, None, &mut |struct_name, item| { + let message = check_item_md(item, options); + check_v.push((struct_name.to_string(), message)) + }); + }; + + // table + let mut table = MultiPartTable::new(); + + if check_v.is_empty() { + table = table.header_ref(&"No issues or errors."); + } else { + table = table.multi_raw(vec![ + "RDAP Structure".to_inline(options), + "Message".to_inline(options), + ]); + for c in check_v { + table = table.nv_raw(&c.0, c.1); + } + } + md.push_str(&table.to_md_table(options)); + } else { + let mut table = MultiPartTable::new(); + table = table.multi_raw(vec![self.outcome.to_md(options)]); + md.push_str(&table.to_md_table(options)); + } + + md + } + + fn attribute_set(&self) -> String { + let socket_type = if self.socket_addr.is_ipv4() { + "v4" + } else { + "v6" + }; + if !self.features.is_empty() { + format!( + "{socket_type}, {}", + self.features + .iter() + .map(|f| f.to_string()) + .collect::>() + .join(", ") + ) + } else { + socket_type.to_string() + } + } +} + +fn check_item_md(item: &CheckItem, options: &MdOptions) -> String { + if !matches!(item.check_class, CheckClass::Informational) + && !matches!(item.check_class, CheckClass::SpecificationNote) + { + item.to_string().to_em(options) + } else { + item.to_string() + } +} + +fn format_date_time(date: DateTime) -> String { + date.format("%a, %v %X %Z").to_string() +} + +fn do_checks(response: &ResponseData, options: &TestOptions) -> Checks { + let check_params = CheckParams { + do_subchecks: true, + root: &response.rdap, + parent_type: response.rdap.get_type(), + allow_unreg_ext: options.allow_unregistered_extensions, + }; + let mut checks = response.rdap.get_checks(check_params); + + // httpdata checks + checks + .items + .append(&mut response.http_data.get_checks(check_params).items); + + // add expected extension checks + for ext in &options.expect_extensions { + if !rdap_has_expected_extension(&response.rdap, ext) { + checks + .items + .push(Check::ExpectedExtensionNotFound.check_item()); + } + } + + //return + checks +} + +fn rdap_has_expected_extension(rdap: &RdapResponse, ext: &str) -> bool { + let count = ext.split('|').filter(|s| rdap.has_extension(s)).count(); + count > 0 +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use icann_rdap_common::{ + prelude::ToResponse, + response::{Domain, Extension}, + }; + + use super::rdap_has_expected_extension; + + #[test] + fn GIVEN_expected_extension_WHEN_rdap_has_THEN_true() { + // GIVEN + let domain = Domain::builder() + .extension(Extension::from("foo0")) + .ldh_name("foo.example.com") + .build(); + let rdap = domain.to_response(); + + // WHEN + let actual = rdap_has_expected_extension(&rdap, "foo0"); + + // THEN + assert!(actual); + } + + #[test] + fn GIVEN_expected_extension_WHEN_rdap_does_not_have_THEN_false() { + // GIVEN + let domain = Domain::builder() + .extension(Extension::from("foo0")) + .ldh_name("foo.example.com") + .build(); + let rdap = domain.to_response(); + + // WHEN + let actual = rdap_has_expected_extension(&rdap, "foo1"); + + // THEN + assert!(!actual); + } + + #[test] + fn GIVEN_compound_expected_extension_WHEN_rdap_has_THEN_true() { + // GIVEN + let domain = Domain::builder() + .extension(Extension::from("foo0")) + .ldh_name("foo.example.com") + .build(); + let rdap = domain.to_response(); + + // WHEN + let actual = rdap_has_expected_extension(&rdap, "foo0|foo1"); + + // THEN + assert!(actual); + } +} diff --git a/icann-rdap-cli/tests/integration/main.rs b/icann-rdap-cli/tests/integration/main.rs new file mode 100644 index 0000000..722b307 --- /dev/null +++ b/icann-rdap-cli/tests/integration/main.rs @@ -0,0 +1,3 @@ +mod rdap_cmd; +mod rdap_test_cmd; +mod test_jig; diff --git a/icann-rdap-cli/tests/integration/rdap_cmd/cache.rs b/icann-rdap-cli/tests/integration/rdap_cmd/cache.rs new file mode 100644 index 0000000..3c27242 --- /dev/null +++ b/icann-rdap-cli/tests/integration/rdap_cmd/cache.rs @@ -0,0 +1,50 @@ +#![allow(non_snake_case)] + +use { + icann_rdap_client::rdap::RequestResponseOwned, + icann_rdap_common::response::{Domain, Entity, RdapResponse}, + icann_rdap_srv::storage::StoreOps, +}; + +use crate::test_jig::TestJig; + +#[tokio::test(flavor = "multi_thread")] +async fn GIVEN_domain_with_entity_WHEN_retreived_from_cache_THEN_is_domain() { + // GIVEN + let mut test_jig = TestJig::new_rdap().await; + let mut tx = test_jig.mem.new_tx().await.expect("new transaction"); + tx.add_domain( + &Domain::builder() + .ldh_name("foo.example") + .entity(Entity::builder().handle("bob").build()) + .build(), + ) + .await + .expect("add domain in tx"); + tx.commit().await.expect("tx commit"); + + test_jig.cmd.arg("foo.example"); + let output = test_jig.cmd.output().expect("executing domain query"); + let responses: Vec = + serde_json::from_slice(&output.stdout).expect("parsing stdout"); + let rdap = &responses.first().expect("response is empty").res_data.rdap; + println!("response type is {rdap}"); + + // WHEN + let mut test_jig = test_jig.new_cmd(); + test_jig.cmd.arg("foo.example"); + + // THEN + let output = test_jig.cmd.output().expect("executing domain query"); + let responses: Vec = + serde_json::from_slice(&output.stdout).expect("parsing stdout"); + let rdap = &responses.first().expect("response is empty").res_data.rdap; + println!("response type is {rdap}"); + assert!(matches!(rdap, RdapResponse::Domain(_))); + let rdap_type = &responses + .first() + .expect("response is empty") + .res_data + .rdap_type; + assert_eq!(rdap_type, "Domain"); +} diff --git a/icann-rdap-cli/tests/integration/rdap_cmd/check.rs b/icann-rdap-cli/tests/integration/rdap_cmd/check.rs new file mode 100644 index 0000000..4ec96fb --- /dev/null +++ b/icann-rdap-cli/tests/integration/rdap_cmd/check.rs @@ -0,0 +1,23 @@ +#![allow(non_snake_case)] + +use {icann_rdap_common::response::Domain, icann_rdap_srv::storage::StoreOps}; + +use crate::test_jig::TestJig; + +#[tokio::test(flavor = "multi_thread")] +async fn GIVEN_domain_with_check_WHEN_query_THEN_failure() { + // GIVEN + let mut test_jig = TestJig::new_rdap().await; + let mut tx = test_jig.mem.new_tx().await.expect("new transaction"); + tx.add_domain(&Domain::builder().ldh_name("foo.example").build()) + .await + .expect("add domain in tx"); + tx.commit().await.expect("tx commit"); + + // WHEN + test_jig.cmd.arg("--error-on-check").arg("foo.example"); + + // THEN + let assert = test_jig.cmd.assert(); + assert.failure(); +} diff --git a/icann-rdap-cli/tests/integration/rdap_cmd/mod.rs b/icann-rdap-cli/tests/integration/rdap_cmd/mod.rs new file mode 100644 index 0000000..758e119 --- /dev/null +++ b/icann-rdap-cli/tests/integration/rdap_cmd/mod.rs @@ -0,0 +1,5 @@ +mod cache; +mod check; +mod queries; +mod source; +mod url; diff --git a/icann-rdap-cli/tests/integration/rdap_cmd/queries.rs b/icann-rdap-cli/tests/integration/rdap_cmd/queries.rs new file mode 100644 index 0000000..5ef9f87 --- /dev/null +++ b/icann-rdap-cli/tests/integration/rdap_cmd/queries.rs @@ -0,0 +1,214 @@ +#![allow(non_snake_case)] + +use { + icann_rdap_common::response::{Autnum, Domain, Entity, Nameserver, Network}, + icann_rdap_srv::storage::StoreOps, + rstest::rstest, +}; + +use crate::test_jig::TestJig; + +#[rstest] +#[case("foo.example", "foo.example")] +#[case("foo.example", "foo.example.")] +#[case("foo.example", "FOO.EXAMPLE")] +#[case("foó.example", "foó.example")] // unicode +#[tokio::test(flavor = "multi_thread")] +async fn GIVEN_domain_WHEN_query_THEN_success(#[case] db_domain: &str, #[case] q_domain: &str) { + // GIVEN + let mut test_jig = TestJig::new_rdap().await; + let mut tx = test_jig.mem.new_tx().await.expect("new transaction"); + tx.add_domain(&Domain::builder().ldh_name(db_domain).build()) + .await + .expect("add domain in tx"); + tx.commit().await.expect("tx commit"); + + // WHEN + test_jig.cmd.arg(q_domain); + + // THEN + let assert = test_jig.cmd.assert(); + assert.success(); +} + +#[tokio::test(flavor = "multi_thread")] +async fn GIVEN_tld_WHEN_query_THEN_success() { + // GIVEN + let mut test_jig = TestJig::new_rdap().await; + let mut tx = test_jig.mem.new_tx().await.expect("new transaction"); + tx.add_domain(&Domain::builder().ldh_name("example").build()) + .await + .expect("add domain in tx"); + tx.commit().await.expect("tx commit"); + + // WHEN + // without "--tld-lookup=none" then this attempts to query IANA instead of the test server + test_jig.cmd.arg("--tld-lookup=none").arg(".example"); + + // THEN + let assert = test_jig.cmd.assert(); + assert.success(); +} + +#[tokio::test(flavor = "multi_thread")] +async fn GIVEN_entity_WHEN_query_THEN_success() { + // GIVEN + let mut test_jig = TestJig::new_rdap().await; + let mut tx = test_jig.mem.new_tx().await.expect("new transaction"); + tx.add_entity(&Entity::builder().handle("foo").build()) + .await + .expect("add entity in tx"); + tx.commit().await.expect("tx commit"); + + // WHEN + test_jig.cmd.arg("foo"); + + // THEN + let assert = test_jig.cmd.assert(); + assert.success(); +} + +#[tokio::test(flavor = "multi_thread")] +async fn GIVEN_nameserver_WHEN_query_THEN_success() { + // GIVEN + let mut test_jig = TestJig::new_rdap().await; + let mut tx = test_jig.mem.new_tx().await.expect("new transaction"); + tx.add_nameserver( + &Nameserver::builder() + .ldh_name("ns.foo.example") + .build() + .unwrap(), + ) + .await + .expect("add nameserver in tx"); + tx.commit().await.expect("tx commit"); + + // WHEN + test_jig.cmd.arg("ns.foo.example"); + + // THEN + let assert = test_jig.cmd.assert(); + assert.success(); +} + +#[tokio::test(flavor = "multi_thread")] +async fn GIVEN_autnum_WHEN_query_THEN_success() { + // GIVEN + let mut test_jig = TestJig::new_rdap().await; + let mut tx = test_jig.mem.new_tx().await.expect("new transaction"); + tx.add_autnum(&Autnum::builder().autnum_range(700..710).build()) + .await + .expect("add autnum in tx"); + tx.commit().await.expect("tx commit"); + + // WHEN + test_jig.cmd.arg("700"); + + // THEN + let assert = test_jig.cmd.assert(); + assert.success(); +} + +#[tokio::test(flavor = "multi_thread")] +async fn GIVEN_network_ip_WHEN_query_THEN_success() { + // GIVEN + let mut test_jig = TestJig::new_rdap().await; + let mut tx = test_jig.mem.new_tx().await.expect("new transaction"); + tx.add_network( + &Network::builder() + .cidr("10.0.0.0/24") + .build() + .expect("cidr parsing"), + ) + .await + .expect("add network in tx"); + tx.commit().await.expect("tx commit"); + + // WHEN + test_jig.cmd.arg("10.0.0.1"); + + // THEN + let assert = test_jig.cmd.assert(); + assert.success(); +} + +#[rstest] +#[case("10.0.0.0/24", "10.0.0.0/24")] +#[case("10.0.0.0/24", "10.0.0/24")] +#[tokio::test(flavor = "multi_thread")] +async fn GIVEN_network_cidr_WHEN_query_THEN_success(#[case] db_cidr: &str, #[case] q_cidr: &str) { + // GIVEN + let mut test_jig = TestJig::new_rdap().await; + let mut tx = test_jig.mem.new_tx().await.expect("new transaction"); + tx.add_network( + &Network::builder() + .cidr(db_cidr) + .build() + .expect("cidr parsing"), + ) + .await + .expect("add network in tx"); + tx.commit().await.expect("tx commit"); + + // WHEN + test_jig.cmd.arg(q_cidr); + + // THEN + let assert = test_jig.cmd.assert(); + assert.success(); +} + +#[tokio::test(flavor = "multi_thread")] +async fn GIVEN_url_WHEN_query_THEN_success() { + // GIVEN + let mut test_jig = TestJig::new_rdap().await; + let mut tx = test_jig.mem.new_tx().await.expect("new transaction"); + tx.add_domain(&Domain::builder().ldh_name("foo.example").build()) + .await + .expect("add domain in tx"); + tx.commit().await.expect("tx commit"); + + // WHEN + let url = format!("{}/domain/foo.example", test_jig.rdap_base); + test_jig.cmd.arg(url); + + // THEN + let assert = test_jig.cmd.assert(); + assert.success(); +} + +#[tokio::test(flavor = "multi_thread")] +async fn GIVEN_idn_WHEN_query_a_label_THEN_success() { + // GIVEN + let mut test_jig = TestJig::new_rdap().await; + let mut tx = test_jig.mem.new_tx().await.expect("new transaction"); + tx.add_domain(&Domain::builder().ldh_name("xn--caf-dma.example").build()) + .await + .expect("add domain in tx"); + tx.commit().await.expect("tx commit"); + + // WHEN + test_jig.cmd.arg("-t").arg("a-label").arg("café.example"); + + // THEN + let assert = test_jig.cmd.assert(); + assert.success(); +} + +#[tokio::test(flavor = "multi_thread")] +async fn GIVEN_domain_WHEN_search_domain_names_THEN_success() { + // GIVEN + let mut test_jig = TestJig::new_rdap_with_dn_search().await; + let mut tx = test_jig.mem.new_tx().await.expect("new transaction"); + tx.add_domain(&Domain::builder().ldh_name("foo.example").build()) + .await + .expect("add domain in tx"); + tx.commit().await.expect("tx commit"); + + // WHEN + test_jig.cmd.arg("-t").arg("domain-name").arg("foo.*"); + + // THEN + let assert = test_jig.cmd.assert(); + assert.success(); +} diff --git a/icann-rdap-cli/tests/integration/rdap_cmd/source.rs b/icann-rdap-cli/tests/integration/rdap_cmd/source.rs new file mode 100644 index 0000000..2c4cee3 --- /dev/null +++ b/icann-rdap-cli/tests/integration/rdap_cmd/source.rs @@ -0,0 +1,46 @@ +#![allow(non_snake_case)] + +use { + icann_rdap_client::rdap::{RequestResponseOwned, SourceType}, + icann_rdap_common::response::Network, + icann_rdap_srv::storage::StoreOps, + rstest::rstest, +}; + +use crate::test_jig::TestJig; + +#[rstest] +#[case("10.0.0.0/24", "10.0.0.0/24")] +#[case("10.0.0.0/24", "10.0.0.1")] +#[tokio::test(flavor = "multi_thread")] +async fn GIVEN_inr_query_WHEN_query_THEN_source_is_rir( + #[case] db_cidr: &str, + #[case] q_cidr: &str, +) { + // GIVEN + let mut test_jig = TestJig::new_rdap().await; + let mut tx = test_jig.mem.new_tx().await.expect("new transaction"); + tx.add_network( + &Network::builder() + .cidr(db_cidr) + .build() + .expect("cidr parsing"), + ) + .await + .expect("add network in tx"); + tx.commit().await.expect("tx commit"); + + // WHEN + test_jig.cmd.arg(q_cidr); + + // THEN + let output = test_jig.cmd.output().expect("executing inr query"); + let responses: Vec = + serde_json::from_slice(&output.stdout).expect("parsing stdout"); + let source_type = responses + .first() + .expect("respons is empty") + .req_data + .source_type; + assert!(matches!(source_type, SourceType::RegionalInternetRegistry)); +} diff --git a/icann-rdap-cli/tests/integration/rdap_cmd/url.rs b/icann-rdap-cli/tests/integration/rdap_cmd/url.rs new file mode 100644 index 0000000..80647ff --- /dev/null +++ b/icann-rdap-cli/tests/integration/rdap_cmd/url.rs @@ -0,0 +1,54 @@ +#![allow(non_snake_case)] + +use {icann_rdap_common::response::Network, icann_rdap_srv::storage::StoreOps}; + +use crate::test_jig::TestJig; + +#[tokio::test(flavor = "multi_thread")] +async fn GIVEN_url_used_with_base_url_WHEN_query_THEN_success() { + // GIVEN + let mut test_jig = TestJig::new_rdap().await; + let mut tx = test_jig.mem.new_tx().await.expect("new transaction"); + tx.add_network( + &Network::builder() + .cidr("10.0.0.0/24") + .build() + .expect("cidr parsing"), + ) + .await + .expect("add network in tx"); + tx.commit().await.expect("tx commit"); + + // WHEN + let url = format!("{}/ip/10.0.0.1", test_jig.rdap_base); + test_jig.cmd.arg(url); + + // THEN + let assert = test_jig.cmd.assert(); + assert.success(); +} + +#[tokio::test(flavor = "multi_thread")] +async fn GIVEN_url_used_with_no_base_url_WHEN_query_THEN_success() { + // GIVEN + let mut test_jig = TestJig::new_rdap().await; + test_jig.cmd.env_remove("RDAP_BASE_URL"); + let mut tx = test_jig.mem.new_tx().await.expect("new transaction"); + tx.add_network( + &Network::builder() + .cidr("10.0.0.0/24") + .build() + .expect("cidr parsing"), + ) + .await + .expect("add network in tx"); + tx.commit().await.expect("tx commit"); + + // WHEN + let url = format!("{}/ip/10.0.0.1", test_jig.rdap_base); + test_jig.cmd.arg(url); + + // THEN + let assert = test_jig.cmd.assert(); + assert.success(); +} diff --git a/icann-rdap-cli/tests/integration/rdap_test_cmd/mod.rs b/icann-rdap-cli/tests/integration/rdap_test_cmd/mod.rs new file mode 100644 index 0000000..ec3f638 --- /dev/null +++ b/icann-rdap-cli/tests/integration/rdap_test_cmd/mod.rs @@ -0,0 +1 @@ +mod url; diff --git a/icann-rdap-cli/tests/integration/rdap_test_cmd/url.rs b/icann-rdap-cli/tests/integration/rdap_test_cmd/url.rs new file mode 100644 index 0000000..a3c72f9 --- /dev/null +++ b/icann-rdap-cli/tests/integration/rdap_test_cmd/url.rs @@ -0,0 +1,30 @@ +#![allow(non_snake_case)] + +use {icann_rdap_common::response::Network, icann_rdap_srv::storage::StoreOps}; + +use crate::test_jig::TestJig; + +#[tokio::test(flavor = "multi_thread")] +async fn GIVEN_url_WHEN_test_THEN_success() { + // GIVEN + let mut test_jig = TestJig::new_rdap_test().await; + test_jig.cmd.env_remove("RDAP_BASE_URL"); + let mut tx = test_jig.mem.new_tx().await.expect("new transaction"); + tx.add_network( + &Network::builder() + .cidr("10.0.0.0/24") + .build() + .expect("cidr parsing"), + ) + .await + .expect("add network in tx"); + tx.commit().await.expect("tx commit"); + + // WHEN + let url = format!("{}/ip/10.0.0.1", test_jig.rdap_base); + test_jig.cmd.arg(url); + + // THEN + let assert = test_jig.cmd.assert(); + assert.success(); +} diff --git a/icann-rdap-cli/tests/integration/test_jig.rs b/icann-rdap-cli/tests/integration/test_jig.rs new file mode 100644 index 0000000..7b9b454 --- /dev/null +++ b/icann-rdap-cli/tests/integration/test_jig.rs @@ -0,0 +1,109 @@ +use { + assert_cmd::Command, + icann_rdap_srv::{ + config::ListenConfig, + server::{AppState, Listener}, + storage::{ + mem::{config::MemConfig, ops::Mem}, + CommonConfig, + }, + }, + std::time::Duration, + test_dir::{DirBuilder, FileType, TestDir}, +}; + +pub enum CommandType { + Rdap, + RdapTest, +} + +pub struct TestJig { + pub mem: Mem, + pub cmd: Command, + pub cmd_type: CommandType, + pub rdap_base: String, + // pass ownership to the test so the directories are dropped when the test is done. + test_dir: TestDir, +} + +impl TestJig { + pub async fn new_rdap() -> Self { + let common_config = CommonConfig::default(); + Self::new_common_config(common_config, CommandType::Rdap).await + } + + pub async fn new_rdap_with_dn_search() -> Self { + let common_config = CommonConfig::builder() + .domain_search_by_name_enable(true) + .build(); + Self::new_common_config(common_config, CommandType::Rdap).await + } + + pub async fn new_rdap_test() -> Self { + let common_config = CommonConfig::default(); + Self::new_common_config(common_config, CommandType::RdapTest).await + } + + pub async fn new_common_config(common_config: CommonConfig, cmd_type: CommandType) -> Self { + let mem = Mem::new(MemConfig::builder().common_config(common_config).build()); + let app_state = AppState { + storage: mem.clone(), + bootstrap: false, + }; + let _ = tracing_subscriber::fmt().try_init(); + let listener = Listener::listen(&ListenConfig::default()) + .await + .expect("listening on interface"); + let rdap_base = listener.rdap_base(); + tokio::spawn(async move { + listener + .start_with_state(app_state) + .await + .expect("starting server"); + }); + let test_dir = TestDir::temp() + .create("cache", FileType::Dir) + .create("config", FileType::Dir); + let cmd = Command::new("sh"); //throw away + Self { + mem, + cmd, + cmd_type, + rdap_base, + test_dir, + } + .new_cmd() + } + + /// Creates a new command from an existing one but resetting necessary environment variables. + /// + /// Using the function allows the test jig to stay up but a new command to be executed. + pub fn new_cmd(self) -> Self { + let cmd = match self.cmd_type { + CommandType::Rdap => { + let mut cmd = Command::cargo_bin("rdap").expect("cannot find rdap cmd"); + cmd.env_clear() + .timeout(Duration::from_secs(2)) + .env("RDAP_BASE_URL", self.rdap_base.clone()) + .env("RDAP_PAGING", "none") + .env("RDAP_OUTPUT", "json-extra") + .env("RDAP_LOG", "debug") + .env("RDAP_ALLOW_HTTP", "true") + .env("XDG_CACHE_HOME", self.test_dir.path("cache")) + .env("XDG_CONFIG_HOME", self.test_dir.path("config")); + cmd + } + CommandType::RdapTest => { + let mut cmd = Command::cargo_bin("rdap-test").expect("cannot find rdap-test cmd"); + cmd.env_clear() + .timeout(Duration::from_secs(2)) + .env("RDAP_TEST_LOG", "debug") + .env("RDAP_TEST_ALLOW_HTTP", "true") + .env("XDG_CACHE_HOME", self.test_dir.path("cache")) + .env("XDG_CONFIG_HOME", self.test_dir.path("config")); + cmd + } + }; + Self { cmd, ..self } + } +} diff --git a/icann-rdap-client/Cargo.toml b/icann-rdap-client/Cargo.toml new file mode 100644 index 0000000..35eb065 --- /dev/null +++ b/icann-rdap-client/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "icann-rdap-client" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +description = """ +An RDAP client library. +""" + +[dependencies] + +icann-rdap-common = { version = "0.0.22", path = "../icann-rdap-common" } + +buildstructor.workspace = true +cidr.workspace = true +chrono.workspace = true +const_format.workspace = true +idna.workspace = true +ipnet.workspace = true +jsonpath-rust.workspace = true +jsonpath_lib.workspace = true +pct-str.workspace = true +regex.workspace = true +reqwest.workspace = true +serde.workspace = true +serde_json.workspace = true +strum.workspace = true +strum_macros.workspace = true +thiserror.workspace = true +tracing.workspace = true + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio.workspace = true + +[dev-dependencies] + +# fixture testings +rstest = "0.17.0" + +# tokio async runtime +tokio = { version = "1.21", features = [ "full" ] } diff --git a/icann-rdap-client/README.md b/icann-rdap-client/README.md new file mode 100644 index 0000000..1c52fe1 --- /dev/null +++ b/icann-rdap-client/README.md @@ -0,0 +1,105 @@ +ICANN RDAP Client Library +========================= + +This is a client library for the Registration Data Access Protocol (RDAP) written and sponsored +by the Internet Corporation for Assigned Names and Numbers [(ICANN)](https://www.icann.org). +RDAP is standard of the [IETF](https://ietf.org/), and extensions +to RDAP are a current work activity of the IETF's [REGEXT working group](https://datatracker.ietf.org/wg/regext/documents/). +More information on ICANN's role in RDAP can be found [here](https://www.icann.org/rdap). +General information on RDAP can be found [here](https://rdap.rcode3.com/). + +Installation +------------ + +Add the library to your Cargo.toml: `cargo add icann-rdap-client` + +Also, add the commons library: `cargo add icann-rdap-common`. + +Both [icann_rdap_common] and this crate can be compiled for WASM targets. + +Usage +----- + +In RDAP, [bootstrapping](https://rdap.rcode3.com/bootstrapping/iana.html) +is the process of finding the authoritative RDAP server to +query using the IANA RDAP bootstrap files. To make a query using bootstrapping: + +```rust,no_run +use icann_rdap_client::prelude::*; +use std::str::FromStr; +use tokio::main; + +#[tokio::main] +async fn main() -> Result<(), RdapClientError> { + + // create a query + let query = QueryType::from_str("192.168.0.1")?; + // or + let query = QueryType::from_str("icann.org")?; + + // create a client (from icann-rdap-common) + let config = ClientConfig::default(); + // or let config = ClientConfig::builder().build(); + + let client = create_client(&config)?; + + // ideally, keep store in same context as client + let store = MemoryBootstrapStore::new(); + + // issue the RDAP query + let response = + rdap_bootstrapped_request( + &query, + &client, + &store, + |reg| eprintln!("fetching {reg:?}") + ).await?; + + Ok(()) +} +``` + +To specify a base URL: + +```rust,no_run +use icann_rdap_client::prelude::*; +use std::str::FromStr; +use tokio::main; + +#[tokio::main] +async fn main() -> Result<(), RdapClientError> { + + // create a query + let query = QueryType::from_str("192.168.0.1")?; + // or + let query = QueryType::from_str("icann.org")?; + + // create a client (from icann-rdap-common) + let config = ClientConfig::builder().build(); + // or let config = ClientConfig::default(); + + let client = create_client(&config)?; + + // issue the RDAP query + let base_url = "https://rdap-bootstrap.arin.net/bootstrap"; + let response = rdap_request(base_url, &query, &client).await?; + + Ok(()) +} + +``` + +License +------- + +Licensed under either of +* Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) +* MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT) at your option. + +Contribution +------------ + +Unless you explicitly state otherwise, any contribution, as defined in the Apache-2.0 license, +intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, +shall be dual licensed pursuant to the Apache License, Version 2.0 or the MIT License referenced +as above, at ICANN’s option, without any additional terms or conditions. diff --git a/icann-rdap-client/src/gtld/domain.rs b/icann-rdap-client/src/gtld/domain.rs new file mode 100644 index 0000000..9fee002 --- /dev/null +++ b/icann-rdap-client/src/gtld/domain.rs @@ -0,0 +1,258 @@ +use { + super::{GtldParams, ToGtldWhois}, + icann_rdap_common::response::{Boolish, Domain, Event, Nameserver, Network, SecureDns}, +}; + +impl ToGtldWhois for Domain { + fn to_gtld_whois(&self, params: &mut GtldParams) -> String { + let mut gtld = String::new(); + + gtld.push_str("\n\n"); + // Domain Name + let domain_name = format_domain_name(self); + gtld.push_str(&domain_name); + + // Domain ID + let domain_id = format_domain_id(self.object_common.handle.as_ref()); + gtld.push_str(&domain_id); + + // Date Time for Registry + let date_info = format_registry_dates(&self.object_common.events); + gtld.push_str(&date_info); + + // Common Object Stuff + let domain_info = format_domain_info( + &self.object_common.status.as_ref().map(|v| v.vec().clone()), + &self.object_common.port_43, + ); + gtld.push_str(&domain_info); + + // Enitities: registrar and abuse/tech/admin/registrant info + let formatted_data = self.object_common.entities.to_gtld_whois(params); + gtld.push_str(&formatted_data); + + // nameservers and network + let additional_info = + format_nameservers_and_network(&self.nameservers, &self.network, params); + gtld.push_str(&additional_info); + + // secure dns + let dnssec_info = format_dnssec_info(&self.secure_dns); + gtld.push_str(&dnssec_info); + + gtld.push_str( + "URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/\n", + ); + + // last update info + format_last_update_info(&self.object_common.events, &mut gtld); + + gtld + } +} + +fn format_domain_name(domain: &Domain) -> String { + if let Some(unicode_name) = &domain.unicode_name { + format!("Domain Name: {unicode_name}\n") + } else if let Some(ldh_name) = &domain.ldh_name { + format!("Domain Name: {ldh_name}\n") + } else if let Some(handle) = &domain.object_common.handle { + format!("Domain Name: {handle}\n") + } else { + "Domain Name: \n".to_string() + } +} + +fn format_domain_id(handle: Option<&String>) -> String { + if let Some(handle) = handle { + format!("Registry Domain ID: {handle}\n") + } else { + "Registry Domain ID: \n".to_string() + } +} + +fn format_registry_dates(events: &Option>) -> String { + let mut formatted_dates = String::new(); + if let Some(events) = events { + for event in events { + if let Some(event_action) = &event.event_action { + match event_action.as_str() { + "last changed" => { + if let Some(event_date) = &event.event_date { + formatted_dates.push_str(&format!("Updated Date: {}\n", event_date)); + } + } + "registration" => { + if let Some(event_date) = &event.event_date { + formatted_dates.push_str(&format!("Creation Date: {}\n", event_date)); + } + } + "expiration" => { + if let Some(event_date) = &event.event_date { + formatted_dates + .push_str(&format!("Registry Expiry Date: {}\n", event_date)); + } + } + _ => {} + } + } + } + } + + formatted_dates +} + +fn format_domain_info(status: &Option>, port_43: &Option) -> String { + let mut info = String::new(); + if let Some(status) = status { + for value in status { + info.push_str(&format!("Domain Status: {}\n", *value)); + } + } + if let Some(port_43) = port_43 { + if !port_43.is_empty() { + info.push_str(&format!("Registrar Whois Server: {}\n", port_43)); + } + } + + info +} + +fn format_nameservers_and_network( + nameservers: &Option>, + network: &Option, + params: &mut GtldParams, +) -> String { + let mut gtld = String::new(); + + if let Some(nameservers) = nameservers { + nameservers + .iter() + .for_each(|ns| gtld.push_str(&ns.to_gtld_whois(params))); + } + + if let Some(network) = network { + gtld.push_str(&network.to_gtld_whois(params)); + } + + gtld +} + +fn format_dnssec_info(secure_dns: &Option) -> String { + let mut dnssec_info = String::new(); + + if let Some(secure_dns) = secure_dns { + if secure_dns + .delegation_signed + .as_ref() + .unwrap_or(&Boolish::from(false)) + .into_bool() + { + dnssec_info.push_str("DNSSEC: signedDelegation\n"); + if let Some(ds_data) = &secure_dns.ds_data { + for ds in ds_data { + if let (Some(key_tag), Some(algorithm), Some(digest_type), Some(digest)) = ( + ds.key_tag.as_ref(), + ds.algorithm.as_ref(), + ds.digest_type.as_ref(), + ds.digest.as_ref(), + ) { + dnssec_info.push_str(&format!( + "DNSSEC DS Data: {} {} {} {}\n", + key_tag, algorithm, digest_type, digest + )); + } + } + } + } + } + + dnssec_info +} + +fn format_last_update_info(events: &Option>, gtld: &mut String) { + if let Some(events) = events { + for event in events { + if let Some(event_action) = &event.event_action { + if event_action == "last update of RDAP database" { + if let Some(event_date) = &event.event_date { + gtld.push_str(&format!( + ">>> Last update of RDAP database: {} <<<\n", + event_date + )); + } + break; + } + } + } + } +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use crate::gtld::ToGtldWhois; + + use { + super::GtldParams, + icann_rdap_common::{prelude::ToResponse, response::Domain}, + }; + + use { + serde_json::Value, + std::{any::TypeId, error::Error, fs::File, io::Read}, + }; + + fn process_gtld_file(file_path: &str) -> Result> { + let mut file = File::open(file_path)?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + + let toplevel_json_response: Value = serde_json::from_str(&contents)?; + + let actual = serde_json::from_value::(toplevel_json_response); + let gtld_version_of_the_domain = match actual { + Ok(domain) => { + let rdap_response = Domain::builder().ldh_name("").build().to_response(); + let mut gtld_params = GtldParams { + root: &rdap_response, + parent_type: TypeId::of::(), + label: "".to_string(), + }; + domain.to_gtld_whois(&mut gtld_params) + } + Err(e) => { + return Err(Box::new(e)); + } + }; + + Ok(gtld_version_of_the_domain) + } + + #[test] + fn test_ms_click_response() { + let expected_output = + std::fs::read_to_string("src/test_files/microsoft.click-expected.gtld").unwrap(); + + let output = process_gtld_file("src/test_files/microsoft.click.json").unwrap(); + assert_eq!(output, expected_output); + } + + #[test] + fn test_lemonde_response() { + let expected_output = + std::fs::read_to_string("src/test_files/lemonde.fr-expected.gtld").unwrap(); + + let output = process_gtld_file("src/test_files/lemonde.fr.json").unwrap(); + assert_eq!(output, expected_output); + } + + #[test] + fn test_moscow_response() { + let expected_output = + std::fs::read_to_string("src/test_files/home.moscow-expected.gtld").unwrap(); + + let output = process_gtld_file("src/test_files/home.moscow.json").unwrap(); + assert_eq!(output, expected_output); + } +} diff --git a/icann-rdap-client/src/gtld/entity.rs b/icann-rdap-client/src/gtld/entity.rs new file mode 100644 index 0000000..f885afe --- /dev/null +++ b/icann-rdap-client/src/gtld/entity.rs @@ -0,0 +1,270 @@ +use { + super::{GtldParams, RoleInfo, ToGtldWhois}, + icann_rdap_common::{ + contact::{Contact, PostalAddress}, + response::Entity, + }, +}; + +impl ToGtldWhois for Option> { + fn to_gtld_whois(&self, params: &mut GtldParams) -> String { + let mut front_formatted_data = String::new(); + let mut formatted_data = String::new(); + + if let Some(entities) = self { + for entity in entities { + for role in entity.roles() { + match role.as_str() { + "registrar" => { + if let Some(vcard_array) = &entity.vcard_array { + let role_info = extract_role_info(role, vcard_array, params); + // Now use role_info to append to formatted_data + if !role_info.name.is_empty() { + front_formatted_data += + &format!("{}: {}\n", cfl(role), role_info.name); + } + if !role_info.org.is_empty() { + front_formatted_data += + &format!("{} Organization: {}\n", cfl(role), role_info.org); + } + if !role_info.adr.is_empty() { + front_formatted_data += &role_info.adr; + } + } + // Special Sauce for Registrar IANA ID and Abuse Contact + if let Some(public_ids) = &entity.public_ids { + for public_id in public_ids { + if let Some(id_type) = &public_id.id_type { + if let Some(identifier) = &public_id.identifier { + if id_type.as_str() == "IANA Registrar ID" + && !identifier.is_empty() + { + front_formatted_data += &format!( + "Registrar IANA ID: {}\n", + identifier.clone() + ); + } + } + } + } + } + append_abuse_contact_info(entity, &mut front_formatted_data); + } + "technical" | "administrative" | "registrant" => { + if let Some(vcard_array) = &entity.vcard_array { + let role_info = extract_role_info(role, vcard_array, params); + // Now use role_info to append to formatted_data + if !role_info.name.is_empty() { + formatted_data += + &format!("{} Name: {}\n", cfl(role), role_info.name); + } + if !role_info.org.is_empty() { + formatted_data += + &format!("{} Organization: {}\n", cfl(role), role_info.org); + } + if !role_info.adr.is_empty() { + formatted_data += &role_info.adr; + } + if !role_info.email.is_empty() { + formatted_data += + &format!("{} Email: {}\n", cfl(role), role_info.email); + } + if !role_info.phone.is_empty() { + formatted_data += + &format!("{} Phone: {}\n", cfl(role), role_info.phone); + } + if !role_info.fax.is_empty() { + formatted_data += + &format!("{} Fax: {}\n", cfl(role), role_info.fax); + } + } + } + _ => {} // Are there any roles we are missing? + } + } + } + } + + front_formatted_data += &formatted_data; + front_formatted_data + } +} + +fn format_address_with_label( + params: &mut GtldParams, + address_components: &[serde_json::Value], +) -> String { + // TODO once from_vcard is fixed to handle the way addressing is done, replace this with the normal builder. + let postal_address = PostalAddress::builder() + .street_parts( + address_components + .get(2) + .and_then(|v| v.as_str()) + .map_or_else(Vec::new, |s| vec![s.to_string()]), + ) + .locality( + address_components + .get(3) + .and_then(|v| v.as_str()) + .map_or_else(String::new, String::from), + ) + .region_name( + address_components + .get(4) + .and_then(|v| v.as_str()) + .map_or_else(String::new, String::from), + ) + .country_name( + address_components + .get(6) + .and_then(|v| v.as_str()) + .map_or_else(String::new, String::from), + ) + .country_code( + address_components + .get(6) + .and_then(|v| v.as_str()) + .map_or_else(String::new, String::from), + ) + .postal_code( + address_components + .get(5) + .and_then(|v| v.as_str()) + .map_or_else(String::new, String::from), + ) + .build(); + + postal_address.to_gtld_whois(params).to_string() +} + +fn extract_role_info( + role: &str, + vcard_array: &[serde_json::Value], + params: &mut GtldParams, +) -> RoleInfo { + let contact = match Contact::from_vcard(vcard_array) { + Some(contact) => contact, + None => return RoleInfo::default(), + }; + let mut adr = String::new(); + let label = match role { + "registrar" => "Registrar", + "technical" => "Technical", + "administrative" => "Admin", + "registrant" => "Registrant", + _ => "", + }; + params.label = label.to_string(); + + let name = contact.full_name.unwrap_or_default(); + let org = contact + .organization_names + .and_then(|orgs| orgs.first().cloned()) + .unwrap_or_default(); + + // TODO this is a workout to get the address out of the contact. Replace this when from_vcard is fixed + for vcard in vcard_array.iter() { + if let Some(properties) = vcard.as_array() { + for property in properties { + if let Some(property) = property.as_array() { + if let "adr" = property[0].as_str().unwrap_or("") { + if let Some(address_components) = property[3].as_array() { + adr = format_address_with_label(params, address_components); + } + } + } + } + } + } + + let email = contact + .emails + .and_then(|emails| emails.first().map(|email| email.email.clone())) + .unwrap_or_default(); + let phone = contact + .phones + .as_ref() + .and_then(|phones| { + phones + .iter() + .find(|phone| { + phone + .features + .as_ref() + .map_or(true, |features| !features.contains(&"fax".to_string())) + }) + .map(|phone| phone.phone.clone()) + }) + .unwrap_or_default(); + let fax = contact + .phones + .as_ref() + .and_then(|phones| { + phones + .iter() + .find(|phone| { + phone + .features + .as_ref() + .map_or(false, |features| features.contains(&"fax".to_string())) + }) + .map(|phone| phone.phone.clone()) + }) + .unwrap_or_default(); + + RoleInfo { + name, + org, + adr, + email, + phone, + fax, + } +} + +fn append_abuse_contact_info(entity: &Entity, front_formatted_data: &mut String) { + if let Some(entities) = &entity.object_common.entities { + for entity in entities { + for role in entity.roles() { + if role.as_str() == "abuse" { + if let Some(vcard_array) = &entity.vcard_array { + if let Some(contact) = Contact::from_vcard(vcard_array) { + // Emails + if let Some(emails) = &contact.emails { + for email in emails { + let abuse_contact_email = &email.email; + if !abuse_contact_email.is_empty() { + front_formatted_data.push_str(&format!( + "Registrar Abuse Contact Email: {}\n", + abuse_contact_email + )); + } + } + } + // Phones + if let Some(phones) = &contact.phones { + for phone in phones { + let abuse_contact_phone = &phone.phone; + if !abuse_contact_phone.is_empty() { + front_formatted_data.push_str(&format!( + "Registrar Abuse Contact Phone: {}\n", + abuse_contact_phone + )); + } + } + } + } + } + } + } + } + } +} + +// capitalize first letter +fn cfl(s: &str) -> String { + s.char_indices() + .next() + .map(|(i, c)| c.to_uppercase().collect::() + &s[i + 1..]) + .unwrap_or_default() +} diff --git a/icann-rdap-client/src/gtld/mod.rs b/icann-rdap-client/src/gtld/mod.rs new file mode 100644 index 0000000..dcda9c3 --- /dev/null +++ b/icann-rdap-client/src/gtld/mod.rs @@ -0,0 +1,83 @@ +//! Converts RDAP structures to gTLD Whois output. + +use { + icann_rdap_common::{contact::PostalAddress, response::RdapResponse}, + std::any::TypeId, +}; + +pub mod domain; +pub mod entity; +pub mod nameserver; +pub mod network; +pub mod types; + +#[derive(Clone)] +pub struct GtldParams<'a> { + pub root: &'a RdapResponse, + pub parent_type: TypeId, + pub label: String, +} + +impl GtldParams<'_> { + pub fn from_parent(&mut self, parent_type: TypeId) -> Self { + Self { + parent_type, + root: self.root, + label: self.label.clone(), + } + } + + pub fn next_level(&self) -> Self { + Self { + label: self.label.clone(), + ..*self + } + } +} + +pub trait ToGtldWhois { + fn to_gtld_whois(&self, params: &mut GtldParams) -> String; +} + +impl ToGtldWhois for RdapResponse { + fn to_gtld_whois(&self, params: &mut GtldParams) -> String { + let mut gtld = String::new(); + let variant_gtld = match &self { + Self::Domain(domain) => domain.to_gtld_whois(params), + _ => String::new(), + }; + gtld.push_str(&variant_gtld); + gtld + } +} + +impl ToGtldWhois for PostalAddress { + fn to_gtld_whois(&self, params: &mut GtldParams) -> String { + let label = ¶ms.label; + + let street = self + .street_parts + .as_ref() + .map(|parts| parts.join(" ")) + .unwrap_or_default(); + let city = self.locality.as_deref().unwrap_or(""); + let state = self.region_name.as_deref().unwrap_or(""); + let postal_code = self.postal_code.as_deref().unwrap_or(""); + let country = self.country_code.as_deref().unwrap_or(""); + + format!( + "{} Street: {}\n{} City: {}\n{} State/Province: {}\n{} Postal Code: {}\n{} Country: {}\n", + label, street, label, city, label, state, label, postal_code, label, country + ) + } +} + +#[derive(Default)] +pub struct RoleInfo { + name: String, + org: String, + adr: String, + email: String, + phone: String, + fax: String, +} diff --git a/icann-rdap-client/src/gtld/nameserver.rs b/icann-rdap-client/src/gtld/nameserver.rs new file mode 100644 index 0000000..ef6345a --- /dev/null +++ b/icann-rdap-client/src/gtld/nameserver.rs @@ -0,0 +1,22 @@ +use { + super::{GtldParams, ToGtldWhois}, + icann_rdap_common::response::Nameserver, +}; + +impl ToGtldWhois for Nameserver { + fn to_gtld_whois(&self, _params: &mut GtldParams) -> String { + let mut gtld = String::new(); + // header + let header_text = if let Some(unicode_name) = &self.unicode_name { + format!("Name Server: {unicode_name}\n") + } else if let Some(ldh_name) = &self.ldh_name { + format!("Name Server: {ldh_name}\n") + } else if let Some(handle) = &self.object_common.handle { + format!("Name Server: {handle}\n") + } else { + "Name Server: \n".to_string() + }; + gtld.push_str(&header_text); + gtld + } +} diff --git a/icann-rdap-client/src/gtld/network.rs b/icann-rdap-client/src/gtld/network.rs new file mode 100644 index 0000000..896e37a --- /dev/null +++ b/icann-rdap-client/src/gtld/network.rs @@ -0,0 +1,30 @@ +use { + super::{GtldParams, ToGtldWhois}, + icann_rdap_common::response::Network, + std::any::TypeId, +}; + +impl ToGtldWhois for Network { + fn to_gtld_whois(&self, params: &mut GtldParams) -> String { + let _typeid = TypeId::of::(); + let mut gtld = String::new(); + gtld.push_str(&self.common.to_gtld_whois(params)); + let header_text = if self.start_address.is_some() && self.end_address.is_some() { + format!( + "IP Network: {}-{}\n", + &self.start_address.as_ref().unwrap(), + &self.end_address.as_ref().unwrap() + ) + } else if let Some(start_address) = &self.start_address { + format!("IP Network: {start_address}\n") + } else if let Some(handle) = &self.object_common.handle { + format!("IP Network: {handle}\n") + } else if let Some(name) = &self.name { + format!("IP Network: {name}\n") + } else { + "IP Network:\n".to_string() + }; + gtld.push_str(&header_text); + gtld + } +} diff --git a/icann-rdap-client/src/gtld/types.rs b/icann-rdap-client/src/gtld/types.rs new file mode 100644 index 0000000..ce351fa --- /dev/null +++ b/icann-rdap-client/src/gtld/types.rs @@ -0,0 +1,10 @@ +use { + super::{GtldParams, ToGtldWhois}, + icann_rdap_common::response::Common, +}; + +impl ToGtldWhois for Common { + fn to_gtld_whois(&self, _params: &mut GtldParams) -> String { + String::new() + } +} diff --git a/icann-rdap-client/src/http/mod.rs b/icann-rdap-client/src/http/mod.rs new file mode 100644 index 0000000..9925631 --- /dev/null +++ b/icann-rdap-client/src/http/mod.rs @@ -0,0 +1,9 @@ +//! The HTTP layer of RDAP. + +#[doc(inline)] +pub use reqwest::*; +#[doc(inline)] +pub use wrapped::*; + +pub(crate) mod reqwest; +pub(crate) mod wrapped; diff --git a/icann-rdap-client/src/http/reqwest.rs b/icann-rdap-client/src/http/reqwest.rs new file mode 100644 index 0000000..2e9cc64 --- /dev/null +++ b/icann-rdap-client/src/http/reqwest.rs @@ -0,0 +1,226 @@ +//! Creates a Reqwest client. + +pub use reqwest::{ + header::{self, HeaderValue}, + Client as ReqwestClient, Error as ReqwestError, +}; + +use icann_rdap_common::media_types::{JSON_MEDIA_TYPE, RDAP_MEDIA_TYPE}; + +#[cfg(not(target_arch = "wasm32"))] +use {icann_rdap_common::VERSION, std::net::SocketAddr, std::time::Duration}; + +const ACCEPT_HEADER_VALUES: &str = const_format::formatcp!("{RDAP_MEDIA_TYPE}, {JSON_MEDIA_TYPE}"); + +/// Configures the HTTP client. +pub struct ReqwestClientConfig { + /// This string is appended to the user agent. + /// + /// It is provided so + /// library users may identify their programs. + /// This is ignored on wasm32. + pub user_agent_suffix: String, + + /// If set to true, connections will be required to use HTTPS. + /// + /// This is ignored on wasm32. + pub https_only: bool, + + /// If set to true, invalid host names will be accepted. + /// + /// This is ignored on wasm32. + pub accept_invalid_host_names: bool, + + /// If set to true, invalid certificates will be accepted. + /// + /// This is ignored on wasm32. + pub accept_invalid_certificates: bool, + + /// If true, HTTP redirects will be followed. + /// + /// This is ignored on wasm32. + pub follow_redirects: bool, + + /// Specify Host + pub host: Option, + + /// Specify the value of the origin header. + /// + /// Most browsers ignore this by default. + pub origin: Option, + + /// Query timeout in seconds. + /// + /// This corresponds to the total timeout of the request (connection plus reading all the data). + /// + /// This is ignored on wasm32. + pub timeout_secs: u64, +} + +impl Default for ReqwestClientConfig { + fn default() -> Self { + Self { + user_agent_suffix: "library".to_string(), + https_only: true, + accept_invalid_host_names: false, + accept_invalid_certificates: false, + follow_redirects: true, + host: None, + origin: None, + timeout_secs: 60, + } + } +} + +#[buildstructor::buildstructor] +impl ReqwestClientConfig { + #[builder] + #[allow(clippy::too_many_arguments)] + pub fn new( + user_agent_suffix: Option, + https_only: Option, + accept_invalid_host_names: Option, + accept_invalid_certificates: Option, + follow_redirects: Option, + host: Option, + origin: Option, + timeout_secs: Option, + ) -> Self { + let default = Self::default(); + Self { + user_agent_suffix: user_agent_suffix.unwrap_or(default.user_agent_suffix), + https_only: https_only.unwrap_or(default.https_only), + accept_invalid_host_names: accept_invalid_host_names + .unwrap_or(default.accept_invalid_host_names), + accept_invalid_certificates: accept_invalid_certificates + .unwrap_or(default.accept_invalid_certificates), + follow_redirects: follow_redirects.unwrap_or(default.follow_redirects), + host, + origin, + timeout_secs: timeout_secs.unwrap_or(default.timeout_secs), + } + } + + #[builder(entry = "from_config", exit = "build")] + #[allow(clippy::too_many_arguments)] + pub fn new_from_config( + &self, + user_agent_suffix: Option, + https_only: Option, + accept_invalid_host_names: Option, + accept_invalid_certificates: Option, + follow_redirects: Option, + host: Option, + origin: Option, + timeout_secs: Option, + ) -> Self { + Self { + user_agent_suffix: user_agent_suffix.unwrap_or(self.user_agent_suffix.clone()), + https_only: https_only.unwrap_or(self.https_only), + accept_invalid_host_names: accept_invalid_host_names + .unwrap_or(self.accept_invalid_host_names), + accept_invalid_certificates: accept_invalid_certificates + .unwrap_or(self.accept_invalid_certificates), + follow_redirects: follow_redirects.unwrap_or(self.follow_redirects), + host: host.map_or(self.host.clone(), Some), + origin: origin.map_or(self.origin.clone(), Some), + timeout_secs: timeout_secs.unwrap_or(self.timeout_secs), + } + } +} + +/// Creates an HTTP client using Reqwest. The Reqwest +/// client holds its own connection pools, so in many +/// uses cases creating only one client per process is +/// necessary. +#[cfg(not(target_arch = "wasm32"))] +pub fn create_reqwest_client(config: &ReqwestClientConfig) -> Result { + let default_headers = default_headers(config); + + let mut client = reqwest::Client::builder(); + + let redirects = if config.follow_redirects { + reqwest::redirect::Policy::default() + } else { + reqwest::redirect::Policy::none() + }; + client = client + .timeout(Duration::from_secs(config.timeout_secs)) + .user_agent(format!( + "icann_rdap client {VERSION} {}", + config.user_agent_suffix + )) + .redirect(redirects) + .https_only(config.https_only) + .danger_accept_invalid_hostnames(config.accept_invalid_host_names) + .danger_accept_invalid_certs(config.accept_invalid_certificates); + + let client = client.default_headers(default_headers).build()?; + Ok(client) +} + +/// Creates an HTTP client using Reqwest. The Reqwest +/// client holds its own connection pools, so in many +/// uses cases creating only one client per process is +/// necessary. +#[cfg(not(target_arch = "wasm32"))] +pub fn create_reqwest_client_with_addr( + config: &ReqwestClientConfig, + domain: &str, + addr: SocketAddr, +) -> Result { + let default_headers = default_headers(config); + + let mut client = reqwest::Client::builder(); + + let redirects = if config.follow_redirects { + reqwest::redirect::Policy::default() + } else { + reqwest::redirect::Policy::none() + }; + client = client + .timeout(Duration::from_secs(config.timeout_secs)) + .user_agent(format!( + "icann_rdap client {VERSION} {}", + config.user_agent_suffix + )) + .redirect(redirects) + .https_only(config.https_only) + .danger_accept_invalid_hostnames(config.accept_invalid_host_names) + .danger_accept_invalid_certs(config.accept_invalid_certificates) + .resolve(domain, addr); + + let client = client.default_headers(default_headers).build()?; + Ok(client) +} + +/// Creates an HTTP client using Reqwest. The Reqwest +/// client holds its own connection pools, so in many +/// uses cases creating only one client per process is +/// necessary. +/// Note that the WASM version does not set redirect policy, +/// https_only, or TLS settings. +#[cfg(target_arch = "wasm32")] +pub fn create_reqwest_client(config: &ReqwestClientConfig) -> Result { + let default_headers = default_headers(config); + + let client = reqwest::Client::builder(); + + let client = client.default_headers(default_headers).build()?; + Ok(client) +} + +fn default_headers(config: &ReqwestClientConfig) -> header::HeaderMap { + let mut default_headers = header::HeaderMap::new(); + default_headers.insert( + header::ACCEPT, + HeaderValue::from_static(ACCEPT_HEADER_VALUES), + ); + if let Some(host) = &config.host { + default_headers.insert(header::HOST, host.into()); + }; + if let Some(origin) = &config.origin { + default_headers.insert(header::ORIGIN, origin.into()); + } + default_headers +} diff --git a/icann-rdap-client/src/http/wrapped.rs b/icann-rdap-client/src/http/wrapped.rs new file mode 100644 index 0000000..fc5ecb6 --- /dev/null +++ b/icann-rdap-client/src/http/wrapped.rs @@ -0,0 +1,297 @@ +//! Wrapped Client. + +pub use reqwest::{header::HeaderValue, Client as ReqwestClient, Error as ReqwestError}; +use { + icann_rdap_common::httpdata::HttpData, + reqwest::header::{ + ACCESS_CONTROL_ALLOW_ORIGIN, CACHE_CONTROL, CONTENT_TYPE, EXPIRES, LOCATION, RETRY_AFTER, + STRICT_TRANSPORT_SECURITY, + }, +}; + +use { + super::{create_reqwest_client, ReqwestClientConfig}, + crate::RdapClientError, +}; + +#[cfg(not(target_arch = "wasm32"))] +use { + super::create_reqwest_client_with_addr, chrono::DateTime, chrono::Utc, reqwest::StatusCode, + std::net::SocketAddr, tracing::debug, tracing::info, +}; + +/// Used by the request functions. +#[derive(Clone, Copy)] +pub struct RequestOptions { + pub(crate) max_retry_secs: u32, + pub(crate) def_retry_secs: u32, + pub(crate) max_retries: u16, +} + +impl Default for RequestOptions { + fn default() -> Self { + Self { + max_retry_secs: 120, + def_retry_secs: 60, + max_retries: 1, + } + } +} + +/// Configures the HTTP client. +#[derive(Default)] +pub struct ClientConfig { + /// Config for the Reqwest client. + client_config: ReqwestClientConfig, + + /// Request options. + request_options: RequestOptions, +} + +#[buildstructor::buildstructor] +impl ClientConfig { + #[builder] + #[allow(clippy::too_many_arguments)] + pub fn new( + user_agent_suffix: Option, + https_only: Option, + accept_invalid_host_names: Option, + accept_invalid_certificates: Option, + follow_redirects: Option, + host: Option, + origin: Option, + timeout_secs: Option, + max_retry_secs: Option, + def_retry_secs: Option, + max_retries: Option, + ) -> Self { + let default_cc = ReqwestClientConfig::default(); + let default_ro = RequestOptions::default(); + Self { + client_config: ReqwestClientConfig { + user_agent_suffix: user_agent_suffix.unwrap_or(default_cc.user_agent_suffix), + https_only: https_only.unwrap_or(default_cc.https_only), + accept_invalid_host_names: accept_invalid_host_names + .unwrap_or(default_cc.accept_invalid_host_names), + accept_invalid_certificates: accept_invalid_certificates + .unwrap_or(default_cc.accept_invalid_certificates), + follow_redirects: follow_redirects.unwrap_or(default_cc.follow_redirects), + host, + origin, + timeout_secs: timeout_secs.unwrap_or(default_cc.timeout_secs), + }, + request_options: RequestOptions { + max_retry_secs: max_retry_secs.unwrap_or(default_ro.max_retry_secs), + def_retry_secs: def_retry_secs.unwrap_or(default_ro.def_retry_secs), + max_retries: max_retries.unwrap_or(default_ro.max_retries), + }, + } + } + + #[builder(entry = "from_config", exit = "build")] + #[allow(clippy::too_many_arguments)] + pub fn new_from_config( + &self, + user_agent_suffix: Option, + https_only: Option, + accept_invalid_host_names: Option, + accept_invalid_certificates: Option, + follow_redirects: Option, + host: Option, + origin: Option, + timeout_secs: Option, + max_retry_secs: Option, + def_retry_secs: Option, + max_retries: Option, + ) -> Self { + Self { + client_config: ReqwestClientConfig { + user_agent_suffix: user_agent_suffix + .unwrap_or(self.client_config.user_agent_suffix.clone()), + https_only: https_only.unwrap_or(self.client_config.https_only), + accept_invalid_host_names: accept_invalid_host_names + .unwrap_or(self.client_config.accept_invalid_host_names), + accept_invalid_certificates: accept_invalid_certificates + .unwrap_or(self.client_config.accept_invalid_certificates), + follow_redirects: follow_redirects.unwrap_or(self.client_config.follow_redirects), + host: host.map_or(self.client_config.host.clone(), Some), + origin: origin.map_or(self.client_config.origin.clone(), Some), + timeout_secs: timeout_secs.unwrap_or(self.client_config.timeout_secs), + }, + request_options: RequestOptions { + max_retry_secs: max_retry_secs.unwrap_or(self.request_options.max_retry_secs), + def_retry_secs: def_retry_secs.unwrap_or(self.request_options.def_retry_secs), + max_retries: max_retries.unwrap_or(self.request_options.max_retries), + }, + } + } +} + +/// A wrapper around Reqwest client to give additional features when used with the request functions. +pub struct Client { + /// The reqwest client. + pub(crate) reqwest_client: ReqwestClient, + + /// Request options. + pub(crate) request_options: RequestOptions, +} + +impl Client { + pub fn new(reqwest_client: ReqwestClient, request_options: RequestOptions) -> Self { + Self { + reqwest_client, + request_options, + } + } +} + +/// Creates a wrapped HTTP client. The wrapped +/// client holds its own connection pools, so in many +/// uses cases creating only one client per process is +/// necessary. +pub fn create_client(config: &ClientConfig) -> Result { + let client = create_reqwest_client(&config.client_config)?; + Ok(Client::new(client, config.request_options)) +} + +/// Creates a wrapped HTTP client. +/// This will direct the underlying client to connect to a specific socket. +#[cfg(not(target_arch = "wasm32"))] +pub fn create_client_with_addr( + config: &ClientConfig, + domain: &str, + addr: SocketAddr, +) -> Result { + let client = create_reqwest_client_with_addr(&config.client_config, domain, addr)?; + Ok(Client::new(client, config.request_options)) +} + +pub(crate) struct WrappedResponse { + pub(crate) http_data: HttpData, + pub(crate) text: String, +} + +pub(crate) async fn wrapped_request( + request_uri: &str, + client: &Client, +) -> Result { + // send request and loop for possible retries + #[allow(unused_mut)] //because of wasm32 exclusion below + let mut response = client.reqwest_client.get(request_uri).send().await?; + + // this doesn't work on wasm32 because tokio doesn't work on wasm + #[cfg(not(target_arch = "wasm32"))] + { + let mut tries: u16 = 0; + loop { + debug!("HTTP version: {:?}", response.version()); + // don't repeat the request + if !matches!(response.status(), StatusCode::TOO_MANY_REQUESTS) { + break; + } + // loop if HTTP 429 + let retry_after_header = response + .headers() + .get(RETRY_AFTER) + .map(|value| value.to_str().unwrap().to_string()); + let retry_after = if let Some(rt) = retry_after_header { + info!("Server says too many requests and to retry-after '{rt}'."); + rt + } else { + info!("Server says too many requests but does not offer 'retry-after' value."); + client.request_options.def_retry_secs.to_string() + }; + let mut wait_time_seconds = if let Ok(date) = DateTime::parse_from_rfc2822(&retry_after) + { + (date.with_timezone(&Utc) - Utc::now()).num_seconds() as u64 + } else if let Ok(seconds) = retry_after.parse::() { + seconds + } else { + info!( + "Unable to parse retry-after header value. Using {}", + client.request_options.def_retry_secs + ); + client.request_options.def_retry_secs.into() + }; + if wait_time_seconds == 0 { + info!("Given {wait_time_seconds} for retry-after. Does not make sense."); + wait_time_seconds = client.request_options.def_retry_secs as u64; + } + if wait_time_seconds > client.request_options.max_retry_secs as u64 { + info!( + "Server is asking to wait longer than configured max of {}.", + client.request_options.max_retry_secs + ); + wait_time_seconds = client.request_options.max_retry_secs as u64; + } + info!("Waiting {wait_time_seconds} seconds to retry."); + tokio::time::sleep(tokio::time::Duration::from_secs(wait_time_seconds + 1)).await; + tries += 1; + if tries > client.request_options.max_retries { + info!("Max query retries reached."); + break; + } else { + // send the query again + response = client.reqwest_client.get(request_uri).send().await?; + } + } + } + + // throw an error if not 200 OK + let response = response.error_for_status()?; + + // get the response + let content_type = response + .headers() + .get(CONTENT_TYPE) + .map(|value| value.to_str().unwrap().to_string()); + let expires = response + .headers() + .get(EXPIRES) + .map(|value| value.to_str().unwrap().to_string()); + let cache_control = response + .headers() + .get(CACHE_CONTROL) + .map(|value| value.to_str().unwrap().to_string()); + let location = response + .headers() + .get(LOCATION) + .map(|value| value.to_str().unwrap().to_string()); + let access_control_allow_origin = response + .headers() + .get(ACCESS_CONTROL_ALLOW_ORIGIN) + .map(|value| value.to_str().unwrap().to_string()); + let strict_transport_security = response + .headers() + .get(STRICT_TRANSPORT_SECURITY) + .map(|value| value.to_str().unwrap().to_string()); + let retry_after = response + .headers() + .get(RETRY_AFTER) + .map(|value| value.to_str().unwrap().to_string()); + let content_length = response.content_length(); + let status_code = response.status().as_u16(); + let url = response.url().to_owned(); + let text = response.text().await?; + + let http_data = HttpData::now() + .status_code(status_code) + .and_location(location) + .and_content_length(content_length) + .and_content_type(content_type) + .scheme(url.scheme()) + .host( + url.host_str() + .expect("URL has no host. This shouldn't happen.") + .to_owned(), + ) + .and_expires(expires) + .and_cache_control(cache_control) + .and_access_control_allow_origin(access_control_allow_origin) + .and_strict_transport_security(strict_transport_security) + .and_retry_after(retry_after) + .request_uri(request_uri) + .build(); + + Ok(WrappedResponse { http_data, text }) +} diff --git a/icann-rdap-client/src/iana/bootstrap.rs b/icann-rdap-client/src/iana/bootstrap.rs new file mode 100644 index 0000000..f4851ac --- /dev/null +++ b/icann-rdap-client/src/iana/bootstrap.rs @@ -0,0 +1,623 @@ +//! Does RDAP query bootstrapping. + +use std::sync::{Arc, RwLock}; + +use icann_rdap_common::{ + httpdata::HttpData, + iana::{ + get_preferred_url, BootstrapRegistry, BootstrapRegistryError, IanaRegistry, + IanaRegistryType, + }, +}; + +use crate::{http::Client, iana::iana_request::iana_request, rdap::QueryType, RdapClientError}; + +const SECONDS_IN_WEEK: i64 = 604800; + +/// Defines a trait for things that store bootstrap registries. +pub trait BootstrapStore: Send + Sync { + /// Called when store is checked to see if it has a valid bootstrap registry. + /// + /// This method should return false (i.e. `Ok(false)``) if the registry doesn't + /// exist in the store or if the registry in the store is out-of-date (such as + /// the cache control data indicates it is old). + fn has_bootstrap_registry(&self, reg_type: &IanaRegistryType) -> Result; + + /// Puts a registry into the bootstrap registry store. + fn put_bootstrap_registry( + &self, + reg_type: &IanaRegistryType, + registry: IanaRegistry, + http_data: HttpData, + ) -> Result<(), RdapClientError>; + + /// Get the urls for a domain or nameserver (which are domain names) query type. + /// + /// The default method should be good enough for most trait implementations. + fn get_domain_query_urls( + &self, + query_type: &QueryType, + ) -> Result, RdapClientError> { + let domain_name = match query_type { + QueryType::Domain(domain) => domain.to_ascii(), + QueryType::Nameserver(ns) => ns.to_ascii(), + _ => panic!("invalid domain query type"), + }; + self.get_dns_urls(domain_name) + } + + /// Get the urls for an autnum query type. + /// + /// The default method should be good enough for most trait implementations. + fn get_autnum_query_urls( + &self, + query_type: &QueryType, + ) -> Result, RdapClientError> { + let QueryType::AsNumber(asn) = query_type else { + panic!("invalid query type") + }; + self.get_asn_urls(asn.to_string().as_str()) + } + + /// Get the urls for an IPv4 query type. + /// + /// The default method should be good enough for most trait implementations. + fn get_ipv4_query_urls(&self, query_type: &QueryType) -> Result, RdapClientError> { + let ip = match query_type { + QueryType::IpV4Addr(addr) => format!("{addr}/32"), + QueryType::IpV4Cidr(cidr) => cidr.to_string(), + _ => panic!("non ip query for ip bootstrap"), + }; + self.get_ipv4_urls(&ip) + } + + /// Get the urls for an IPv6 query type. + /// + /// The default method should be good enough for most trait implementations. + fn get_ipv6_query_urls(&self, query_type: &QueryType) -> Result, RdapClientError> { + let ip = match query_type { + QueryType::IpV6Addr(addr) => format!("{addr}/128"), + QueryType::IpV6Cidr(cidr) => cidr.to_string(), + _ => panic!("non ip query for ip bootstrap"), + }; + self.get_ipv6_urls(&ip) + } + + /// Get the urls for an entity handle query type. + /// + /// The default method should be good enough for most trait implementations. + fn get_entity_handle_query_urls( + &self, + query_type: &QueryType, + ) -> Result, RdapClientError> { + let QueryType::Entity(handle) = query_type else { + panic!("non entity handle for bootstrap") + }; + let handle_split = handle + .rsplit_once('-') + .ok_or(BootstrapRegistryError::InvalidBootstrapInput)?; + self.get_tag_query_urls(handle_split.1) + } + + /// Get the urls for an object tag query type. + /// + /// The default method should be good enough for most trait implementations. + fn get_tag_query_urls(&self, tag: &str) -> Result, RdapClientError> { + self.get_tag_urls(tag) + } + + /// Get the URLs associated with the IANA RDAP DNS bootstrap. + /// + /// Implementations should implement the logic to pull the [icann_rdap_common::iana::IanaRegistry] + /// and ultimately call its [icann_rdap_common::iana::IanaRegistry::get_dns_bootstrap_urls] method. + fn get_dns_urls(&self, ldh: &str) -> Result, RdapClientError>; + + /// Get the URLs associated with the IANA RDAP ASN bootstrap. + /// + /// Implementations should implement the logic to pull the [icann_rdap_common::iana::IanaRegistry] + /// and ultimately call its [icann_rdap_common::iana::IanaRegistry::get_asn_bootstrap_urls] method. + fn get_asn_urls(&self, asn: &str) -> Result, RdapClientError>; + + /// Get the URLs associated with the IANA RDAP IPv4 bootstrap. + /// + /// Implementations should implement the logic to pull the [icann_rdap_common::iana::IanaRegistry] + /// and ultimately call its [icann_rdap_common::iana::IanaRegistry::get_ipv4_bootstrap_urls] method. + fn get_ipv4_urls(&self, ipv4: &str) -> Result, RdapClientError>; + + /// Get the URLs associated with the IANA RDAP IPv6 bootstrap. + /// + /// Implementations should implement the logic to pull the [icann_rdap_common::iana::IanaRegistry] + /// and ultimately call its [icann_rdap_common::iana::IanaRegistry::get_ipv6_bootstrap_urls] method. + fn get_ipv6_urls(&self, ipv6: &str) -> Result, RdapClientError>; + + /// Get the URLs associated with the IANA RDAP Object Tags bootstrap. + /// + /// Implementations should implement the logic to pull the [icann_rdap_common::iana::IanaRegistry] + /// and ultimately call its [icann_rdap_common::iana::IanaRegistry::get_tag_bootstrap_urls] method. + fn get_tag_urls(&self, tag: &str) -> Result, RdapClientError>; +} + +/// A trait to find the preferred URL from a bootstrap service. +pub trait PreferredUrl { + fn preferred_url(self) -> Result; +} + +impl PreferredUrl for Vec { + fn preferred_url(self) -> Result { + Ok(get_preferred_url(self)?) + } +} + +/// A bootstrap registry store backed by memory. +/// +/// This implementation of [BootstrapStore] keeps registries in memory. Every new instance starts with +/// no registries in memory. They are added and maintained over time by calls to [MemoryBootstrapStore::put_bootstrap_registry()] by the +/// machinery of [crate::rdap::request::rdap_bootstrapped_request()] and [crate::iana::bootstrap::qtype_to_bootstrap_url()]. +/// +/// Ideally, this should be kept in the same scope as [reqwest::Client]. +pub struct MemoryBootstrapStore { + ipv4: Arc>>, + ipv6: Arc>>, + autnum: Arc>>, + dns: Arc>>, + tag: Arc>>, +} + +unsafe impl Send for MemoryBootstrapStore {} +unsafe impl Sync for MemoryBootstrapStore {} + +impl Default for MemoryBootstrapStore { + fn default() -> Self { + Self::new() + } +} + +impl MemoryBootstrapStore { + pub fn new() -> Self { + Self { + ipv4: <_>::default(), + ipv6: <_>::default(), + autnum: <_>::default(), + dns: <_>::default(), + tag: <_>::default(), + } + } +} + +impl BootstrapStore for MemoryBootstrapStore { + fn has_bootstrap_registry(&self, reg_type: &IanaRegistryType) -> Result { + Ok(match reg_type { + IanaRegistryType::RdapBootstrapDns => self.dns.read()?.registry_has_not_expired(), + IanaRegistryType::RdapBootstrapAsn => self.autnum.read()?.registry_has_not_expired(), + IanaRegistryType::RdapBootstrapIpv4 => self.ipv4.read()?.registry_has_not_expired(), + IanaRegistryType::RdapBootstrapIpv6 => self.ipv6.read()?.registry_has_not_expired(), + IanaRegistryType::RdapObjectTags => self.tag.read()?.registry_has_not_expired(), + }) + } + + fn put_bootstrap_registry( + &self, + reg_type: &IanaRegistryType, + registry: IanaRegistry, + http_data: HttpData, + ) -> Result<(), RdapClientError> { + match reg_type { + IanaRegistryType::RdapBootstrapDns => { + let mut g = self.dns.write()?; + *g = Some((registry, http_data)); + } + IanaRegistryType::RdapBootstrapAsn => { + let mut g = self.autnum.write()?; + *g = Some((registry, http_data)); + } + IanaRegistryType::RdapBootstrapIpv4 => { + let mut g = self.ipv4.write()?; + *g = Some((registry, http_data)); + } + IanaRegistryType::RdapBootstrapIpv6 => { + let mut g = self.ipv6.write()?; + *g = Some((registry, http_data)); + } + IanaRegistryType::RdapObjectTags => { + let mut g = self.tag.write()?; + *g = Some((registry, http_data)); + } + }; + Ok(()) + } + + fn get_dns_urls(&self, ldh: &str) -> Result, RdapClientError> { + if let Some((iana, _http_data)) = self.dns.read()?.as_ref() { + Ok(iana.get_dns_bootstrap_urls(ldh)?) + } else { + Err(RdapClientError::BootstrapUnavailable) + } + } + + fn get_asn_urls(&self, asn: &str) -> Result, RdapClientError> { + if let Some((iana, _http_data)) = self.autnum.read()?.as_ref() { + Ok(iana.get_asn_bootstrap_urls(asn)?) + } else { + Err(RdapClientError::BootstrapUnavailable) + } + } + + fn get_ipv4_urls(&self, ipv4: &str) -> Result, RdapClientError> { + if let Some((iana, _http_data)) = self.ipv4.read()?.as_ref() { + Ok(iana.get_ipv4_bootstrap_urls(ipv4)?) + } else { + Err(RdapClientError::BootstrapUnavailable) + } + } + + fn get_ipv6_urls(&self, ipv6: &str) -> Result, RdapClientError> { + if let Some((iana, _http_data)) = self.ipv6.read()?.as_ref() { + Ok(iana.get_ipv6_bootstrap_urls(ipv6)?) + } else { + Err(RdapClientError::BootstrapUnavailable) + } + } + + fn get_tag_urls(&self, tag: &str) -> Result, RdapClientError> { + if let Some((iana, _http_data)) = self.tag.read()?.as_ref() { + Ok(iana.get_tag_bootstrap_urls(tag)?) + } else { + Err(RdapClientError::BootstrapUnavailable) + } + } +} + +/// Trait to determine if a bootstrap registry is past its expiration (i.e. needs to be rechecked). +pub trait RegistryHasNotExpired { + fn registry_has_not_expired(&self) -> bool; +} + +impl RegistryHasNotExpired for Option<(IanaRegistry, HttpData)> { + fn registry_has_not_expired(&self) -> bool { + if let Some((_iana, http_data)) = self { + !http_data.is_expired(SECONDS_IN_WEEK) + } else { + false + } + } +} + +/// Given a [QueryType], it will get the bootstrap URL. +pub async fn qtype_to_bootstrap_url( + client: &Client, + store: &dyn BootstrapStore, + query_type: &QueryType, + callback: F, +) -> Result +where + F: FnOnce(&IanaRegistryType), +{ + match query_type { + QueryType::IpV4Addr(_) | QueryType::IpV4Cidr(_) => { + fetch_bootstrap( + &IanaRegistryType::RdapBootstrapIpv4, + client, + store, + callback, + ) + .await?; + Ok(store.get_ipv4_query_urls(query_type)?.preferred_url()?) + } + QueryType::IpV6Addr(_) | QueryType::IpV6Cidr(_) => { + fetch_bootstrap( + &IanaRegistryType::RdapBootstrapIpv6, + client, + store, + callback, + ) + .await?; + Ok(store.get_ipv6_query_urls(query_type)?.preferred_url()?) + } + QueryType::AsNumber(_) => { + fetch_bootstrap(&IanaRegistryType::RdapBootstrapAsn, client, store, callback).await?; + Ok(store.get_autnum_query_urls(query_type)?.preferred_url()?) + } + QueryType::Domain(_) => { + fetch_bootstrap(&IanaRegistryType::RdapBootstrapDns, client, store, callback).await?; + Ok(store.get_domain_query_urls(query_type)?.preferred_url()?) + } + QueryType::Entity(_) => { + fetch_bootstrap(&IanaRegistryType::RdapObjectTags, client, store, callback).await?; + Ok(store + .get_entity_handle_query_urls(query_type)? + .preferred_url()?) + } + QueryType::Nameserver(_) => { + fetch_bootstrap(&IanaRegistryType::RdapBootstrapDns, client, store, callback).await?; + Ok(store.get_domain_query_urls(query_type)?.preferred_url()?) + } + _ => Err(RdapClientError::BootstrapUnavailable), + } +} + +/// Fetches a bootstrap registry for a [BootstrapStore]. +pub async fn fetch_bootstrap( + reg_type: &IanaRegistryType, + client: &Client, + store: &dyn BootstrapStore, + callback: F, +) -> Result<(), RdapClientError> +where + F: FnOnce(&IanaRegistryType), +{ + if !store.has_bootstrap_registry(reg_type)? { + callback(reg_type); + let iana_resp = iana_request(reg_type.clone(), client).await?; + store.put_bootstrap_registry(reg_type, iana_resp.registry, iana_resp.http_data)?; + } + Ok(()) +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod test { + use icann_rdap_common::{ + httpdata::HttpData, + iana::{IanaRegistry, IanaRegistryType}, + }; + + use crate::{iana::bootstrap::PreferredUrl, rdap::QueryType}; + + use super::{BootstrapStore, MemoryBootstrapStore}; + + #[test] + fn GIVEN_membootstrap_with_dns_WHEN_get_domain_query_url_THEN_correct_url() { + // GIVEN + let mem = MemoryBootstrapStore::new(); + let bootstrap = r#" + { + "version": "1.0", + "publication": "2024-01-07T10:11:12Z", + "description": "Some text", + "services": [ + [ + ["net", "com"], + [ + "https://registry.example.com/myrdap/" + ] + ], + [ + ["org", "mytld"], + [ + "https://example.org/" + ] + ] + ] + } + "#; + let iana = + serde_json::from_str::(bootstrap).expect("cannot parse domain bootstrap"); + mem.put_bootstrap_registry( + &IanaRegistryType::RdapBootstrapDns, + iana, + HttpData::example().build(), + ) + .expect("put iana registry"); + + // WHEN + let actual = mem + .get_domain_query_urls(&QueryType::domain("example.org").expect("invalid domain name")) + .expect("get bootstrap url") + .preferred_url() + .expect("preferred url"); + + // THEN + assert_eq!(actual, "https://example.org/") + } + + #[test] + fn GIVEN_membootstrap_with_autnum_WHEN_get_autnum_query_url_THEN_correct_url() { + // GIVEN + let mem = MemoryBootstrapStore::new(); + let bootstrap = r#" + { + "version": "1.0", + "publication": "2024-01-07T10:11:12Z", + "description": "RDAP Bootstrap file for example registries.", + "services": [ + [ + ["64496-64496"], + [ + "https://rir3.example.com/myrdap/" + ] + ], + [ + ["64497-64510", "65536-65551"], + [ + "https://example.org/" + ] + ], + [ + ["64512-65534"], + [ + "http://example.net/rdaprir2/", + "https://example.net/rdaprir2/" + ] + ] + ] + } + "#; + let iana = + serde_json::from_str::(bootstrap).expect("cannot parse autnum bootstrap"); + mem.put_bootstrap_registry( + &IanaRegistryType::RdapBootstrapAsn, + iana, + HttpData::example().build(), + ) + .expect("put iana registry"); + + // WHEN + let actual = mem + .get_autnum_query_urls(&QueryType::autnum("as64512").expect("invalid autnum")) + .expect("get bootstrap url") + .preferred_url() + .expect("preferred url"); + + // THEN + assert_eq!(actual, "https://example.net/rdaprir2/"); + } + + #[test] + fn GIVEN_membootstrap_with_ipv4_THEN_get_ipv4_query_urls_THEN_correct_url() { + // GIVEN + let mem = MemoryBootstrapStore::new(); + let bootstrap = r#" + { + "version": "1.0", + "publication": "2024-01-07T10:11:12Z", + "description": "RDAP Bootstrap file for example registries.", + "services": [ + [ + ["198.51.100.0/24", "192.0.0.0/8"], + [ + "https://rir1.example.com/myrdap/" + ] + ], + [ + ["203.0.113.0/24", "192.0.2.0/24"], + [ + "https://example.org/" + ] + ], + [ + ["203.0.113.0/28"], + [ + "https://example.net/rdaprir2/", + "http://example.net/rdaprir2/" + ] + ] + ] + } + "#; + let iana = + serde_json::from_str::(bootstrap).expect("cannot parse autnum bootstrap"); + mem.put_bootstrap_registry( + &IanaRegistryType::RdapBootstrapIpv4, + iana, + HttpData::example().build(), + ) + .expect("put iana registry"); + + // WHEN + let actual = mem + .get_ipv4_query_urls(&QueryType::ipv4("198.51.100.1").expect("invalid IP address")) + .expect("get bootstrap url") + .preferred_url() + .expect("preferred url"); + + // THEN + assert_eq!(actual, "https://rir1.example.com/myrdap/"); + } + + #[test] + fn GIVEN_membootstrap_with_ipv6_THEN_get_ipv6_query_urls_THEN_correct_url() { + // GIVEN + let mem = MemoryBootstrapStore::new(); + let bootstrap = r#" + { + "version": "1.0", + "publication": "2024-01-07T10:11:12Z", + "description": "RDAP Bootstrap file for example registries.", + "services": [ + [ + ["2001:db8::/34"], + [ + "https://rir2.example.com/myrdap/" + ] + ], + [ + ["2001:db8:4000::/36", "2001:db8:ffff::/48"], + [ + "https://example.org/" + ] + ], + [ + ["2001:db8:1000::/36"], + [ + "https://example.net/rdaprir2/", + "http://example.net/rdaprir2/" + ] + ] + ] + } + "#; + let iana = + serde_json::from_str::(bootstrap).expect("cannot parse autnum bootstrap"); + mem.put_bootstrap_registry( + &IanaRegistryType::RdapBootstrapIpv6, + iana, + HttpData::example().build(), + ) + .expect("put iana registry"); + + // WHEN + let actual = mem + .get_ipv6_query_urls(&QueryType::ipv6("2001:db8::1").expect("invalid IP address")) + .expect("get bootstrap url") + .preferred_url() + .expect("preferred url"); + + // THEN + assert_eq!(actual, "https://rir2.example.com/myrdap/"); + } + + #[test] + fn GIVEN_membootstrap_with_tag_THEN_get_tag_query_urls_THEN_correct_url() { + // GIVEN + let mem = MemoryBootstrapStore::new(); + let bootstrap = r#" + { + "version": "1.0", + "publication": "YYYY-MM-DDTHH:MM:SSZ", + "description": "RDAP bootstrap file for service provider object tags", + "services": [ + [ + ["contact@example.com"], + ["YYYY"], + [ + "https://example.com/rdap/" + ] + ], + [ + ["contact@example.org"], + ["ZZ54"], + [ + "http://rdap.example.org/" + ] + ], + [ + ["contact@example.net"], + ["1754"], + [ + "https://example.net/rdap/", + "http://example.net/rdap/" + ] + ] + ] + } + "#; + let iana = + serde_json::from_str::(bootstrap).expect("cannot parse autnum bootstrap"); + mem.put_bootstrap_registry( + &IanaRegistryType::RdapObjectTags, + iana, + HttpData::example().build(), + ) + .expect("put iana registry"); + + // WHEN + let actual = mem + .get_entity_handle_query_urls(&QueryType::Entity("foo-YYYY".to_string())) + .expect("get bootstrap url") + .preferred_url() + .expect("preferred url"); + + // THEN + assert_eq!(actual, "https://example.com/rdap/"); + } +} diff --git a/icann-rdap-client/src/iana/iana_request.rs b/icann-rdap-client/src/iana/iana_request.rs new file mode 100644 index 0000000..96d1fcc --- /dev/null +++ b/icann-rdap-client/src/iana/iana_request.rs @@ -0,0 +1,48 @@ +//! The IANA RDAP Bootstrap Registries. + +use { + icann_rdap_common::{ + httpdata::HttpData, + iana::{IanaRegistry, IanaRegistryType, RdapBootstrapRegistry}, + }, + serde::{Deserialize, Serialize}, + thiserror::Error, +}; + +use crate::http::{wrapped_request, Client}; + +/// Response from getting an IANA registry. +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct IanaResponse { + pub registry: IanaRegistry, + pub registry_type: IanaRegistryType, + pub http_data: HttpData, +} + +/// Errors from issuing a request to get an IANA registry. +#[derive(Debug, Error)] +pub enum IanaResponseError { + #[error(transparent)] + Reqwest(#[from] reqwest::Error), + #[error(transparent)] + SerdeJson(#[from] serde_json::Error), +} + +/// Issues the HTTP request to get an IANA registry. +pub async fn iana_request( + registry_type: IanaRegistryType, + client: &Client, +) -> Result { + let url = registry_type.url(); + + let wrapped_response = wrapped_request(url, client).await?; + let text = wrapped_response.text; + let http_data = wrapped_response.http_data; + + let json: RdapBootstrapRegistry = serde_json::from_str(&text)?; + Ok(IanaResponse { + registry: IanaRegistry::RdapBootstrapRegistry(json), + registry_type, + http_data, + }) +} diff --git a/icann-rdap-client/src/iana/mod.rs b/icann-rdap-client/src/iana/mod.rs new file mode 100644 index 0000000..11dbe15 --- /dev/null +++ b/icann-rdap-client/src/iana/mod.rs @@ -0,0 +1,9 @@ +//! IANA and RDAP Bootstrapping + +#[doc(inline)] +pub use bootstrap::*; +#[doc(inline)] +pub use iana_request::*; + +pub(crate) mod bootstrap; +pub(crate) mod iana_request; diff --git a/icann-rdap-client/src/lib.rs b/icann-rdap-client/src/lib.rs new file mode 100644 index 0000000..0457f25 --- /dev/null +++ b/icann-rdap-client/src/lib.rs @@ -0,0 +1,112 @@ +#![allow(dead_code)] // TODO remove this at some point +#![allow(rustdoc::bare_urls)] +#![doc = include_str!("../README.md")] +use std::{fmt::Display, sync::PoisonError}; + +use { + iana::iana_request::IanaResponseError, + icann_rdap_common::{ + dns_types::DomainNameError, httpdata::HttpData, iana::BootstrapRegistryError, + response::RdapResponseError, + }, + thiserror::Error, +}; + +pub mod gtld; +pub mod http; +pub mod iana; +pub mod md; +pub mod rdap; + +/// Basics necesasry for a simple clients. +pub mod prelude { + #[doc(inline)] + pub use crate::http::create_client; + #[doc(inline)] + pub use crate::http::ClientConfig; + #[doc(inline)] + pub use crate::iana::MemoryBootstrapStore; + #[doc(inline)] + pub use crate::rdap::rdap_bootstrapped_request; + #[doc(inline)] + pub use crate::rdap::rdap_request; + #[doc(inline)] + pub use crate::rdap::rdap_url_request; + #[doc(inline)] + pub use crate::rdap::QueryType; + #[doc(inline)] + pub use crate::RdapClientError; +} + +/// Error returned by RDAP client functions and methods. +#[derive(Error, Debug)] +pub enum RdapClientError { + #[error("Query value is not valid.")] + InvalidQueryValue, + + #[error("Ambiquous query type.")] + AmbiquousQueryType, + + #[error(transparent)] + Response(#[from] RdapResponseError), + + #[error(transparent)] + Client(#[from] reqwest::Error), + + #[error("Error parsing response")] + ParsingError(Box), + + #[error(transparent)] + Json(#[from] serde_json::Error), + + #[error("RwLock Poison Error")] + Poison, + + #[error("Bootstrap unavailable")] + BootstrapUnavailable, + + #[error(transparent)] + BootstrapError(#[from] BootstrapRegistryError), + + #[error(transparent)] + IanaResponse(#[from] IanaResponseError), + + #[error(transparent)] + IoError(#[from] std::io::Error), + + #[error(transparent)] + DomainNameError(#[from] DomainNameError), +} + +impl From> for RdapClientError { + fn from(_err: PoisonError) -> Self { + Self::Poison + } +} + +/// Describes the error that occurs when parsing RDAP responses. +#[derive(Debug)] +pub struct ParsingErrorInfo { + pub text: String, + pub http_data: HttpData, + pub error: serde_json::Error, +} + +impl Display for ParsingErrorInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Error: {}\n,Content Length: {}\nContent Type: {}\nUrl: {}\nText:\n{}\n", + self.error, + self.http_data + .content_length + .map_or("No content length given".to_string(), |n| n.to_string()), + self.http_data + .content_type + .clone() + .unwrap_or("No content type given".to_string()), + self.http_data.host, + self.text + ) + } +} diff --git a/icann-rdap-client/src/md/autnum.rs b/icann-rdap-client/src/md/autnum.rs new file mode 100644 index 0000000..c0c047e --- /dev/null +++ b/icann-rdap-client/src/md/autnum.rs @@ -0,0 +1,111 @@ +use std::any::TypeId; + +use icann_rdap_common::{ + check::{CheckParams, GetChecks, GetSubChecks}, + response::Autnum, +}; + +use super::{ + string::StringUtil, + table::{MultiPartTable, ToMpTable}, + types::checks_to_table, + FromMd, MdHeaderText, MdParams, MdUtil, ToMd, HR, +}; + +impl ToMd for Autnum { + fn to_md(&self, params: MdParams) -> String { + let typeid = TypeId::of::(); + let mut md = String::new(); + md.push_str(&self.common.to_md(params.from_parent(typeid))); + + let header_text = self.get_header_text(); + md.push_str( + &header_text + .to_string() + .to_header(params.heading_level, params.options), + ); + + // multipart data + let mut table = MultiPartTable::new(); + + // summary + table = table.summary(header_text); + + // identifiers + table = table + .header_ref(&"Identifiers") + .and_nv_ref( + &"Start AS Number", + &self.start_autnum.as_ref().map(|n| n.to_string()), + ) + .and_nv_ref( + &"End AS Number", + &self.end_autnum.as_ref().map(|n| n.to_string()), + ) + .and_nv_ref(&"Handle", &self.object_common.handle) + .and_nv_ref(&"Autnum Type", &self.autnum_type) + .and_nv_ref(&"Autnum Name", &self.name) + .and_nv_ref(&"Country", &self.country); + + // common object stuff + table = self.object_common.add_to_mptable(table, params); + + // checks + let check_params = CheckParams::from_md(params, typeid); + let mut checks = self.object_common.get_sub_checks(check_params); + checks.push(self.get_checks(check_params)); + table = checks_to_table(checks, table, params); + + // render table + md.push_str(&table.to_md(params)); + + // remarks + md.push_str(&self.object_common.remarks.to_md(params.from_parent(typeid))); + + // only other object classes from here + md.push_str(HR); + + // entities + md.push_str( + &self + .object_common + .entities + .to_md(params.from_parent(typeid)), + ); + + // redacted + if let Some(redacted) = &self.object_common.redacted { + md.push_str(&redacted.as_slice().to_md(params.from_parent(typeid))); + } + + md.push('\n'); + md + } +} + +impl MdUtil for Autnum { + fn get_header_text(&self) -> MdHeaderText { + let header_text = if self.start_autnum.is_some() && self.end_autnum.is_some() { + format!( + "Autonomous Systems {} - {}", + &self.start_autnum.as_ref().unwrap().replace_md_chars(), + &self.end_autnum.as_ref().unwrap().replace_md_chars() + ) + } else if let Some(start_autnum) = &self.start_autnum { + format!("Autonomous System {}", start_autnum.replace_md_chars()) + } else if let Some(handle) = &self.object_common.handle { + format!("Autonomous System {}", handle.replace_md_chars()) + } else if let Some(name) = &self.name { + format!("Autonomous System {}", name.replace_md_chars()) + } else { + "Autonomous System".to_string() + }; + let mut header_text = MdHeaderText::builder().header_text(header_text); + if let Some(entities) = &self.object_common.entities { + for entity in entities { + header_text = header_text.children_entry(entity.get_header_text()); + } + }; + header_text.build() + } +} diff --git a/icann-rdap-client/src/md/domain.rs b/icann-rdap-client/src/md/domain.rs new file mode 100644 index 0000000..605e6f2 --- /dev/null +++ b/icann-rdap-client/src/md/domain.rs @@ -0,0 +1,267 @@ +use std::any::TypeId; + +use icann_rdap_common::{ + dns_types::{DnsAlgorithmType, DnsDigestType}, + response::{Domain, SecureDns, Variant}, +}; + +use icann_rdap_common::check::{CheckParams, GetChecks, GetSubChecks}; + +use crate::rdap::registered_redactions::{self, text_or_registered_redaction}; + +use super::{ + redacted::REDACTED_TEXT, + string::{StringListUtil, StringUtil}, + table::{MultiPartTable, ToMpTable}, + types::{checks_to_table, events_to_table, links_to_table, public_ids_to_table}, + FromMd, MdHeaderText, MdParams, MdUtil, ToMd, HR, +}; + +impl ToMd for Domain { + fn to_md(&self, params: MdParams) -> String { + let typeid = TypeId::of::(); + let mut md = String::new(); + md.push_str(&self.common.to_md(params.from_parent(typeid))); + + // header + let header_text = self.get_header_text(); + md.push_str( + &header_text + .to_string() + .to_header(params.heading_level, params.options), + ); + + // multipart data + let mut table = MultiPartTable::new(); + + let domain_handle = text_or_registered_redaction( + params.root, + ®istered_redactions::RedactedName::RegistryDomainId, + &self.object_common.handle, + REDACTED_TEXT, + ); + + // summary + table = table.summary(header_text); + + // identifiers + table = table + .header_ref(&"Identifiers") + .and_nv_ref(&"LDH Name", &self.ldh_name) + .and_nv_ref(&"Unicode Name", &self.unicode_name) + .and_nv_ref(&"Handle", &domain_handle); + if let Some(public_ids) = &self.public_ids { + table = public_ids_to_table(public_ids, table); + } + + // common object stuff + table = self.object_common.add_to_mptable(table, params); + + // checks + let check_params = CheckParams::from_md(params, typeid); + let mut checks = self.object_common.get_sub_checks(check_params); + checks.push(self.get_checks(check_params)); + table = checks_to_table(checks, table, params); + + // render table + md.push_str(&table.to_md(params)); + + // variants require a custom table + if let Some(variants) = &self.variants { + md.push_str(&do_variants(variants, params)) + } + + // secure dns + if let Some(secure_dns) = &self.secure_dns { + md.push_str(&do_secure_dns(secure_dns, params)) + } + + // remarks + md.push_str(&self.object_common.remarks.to_md(params.from_parent(typeid))); + + // only other object classes from here + md.push_str(HR); + + // entities + md.push_str( + &self + .object_common + .entities + .to_md(params.from_parent(typeid)), + ); + + // nameservers + if let Some(nameservers) = &self.nameservers { + nameservers + .iter() + .for_each(|ns| md.push_str(&ns.to_md(params.next_level()))); + } + + // network + if let Some(network) = &self.network { + md.push_str(&network.to_md(params.next_level())); + } + + // redacted + if let Some(redacted) = &self.object_common.redacted { + md.push_str(&redacted.as_slice().to_md(params.from_parent(typeid))); + } + + md.push('\n'); + md + } +} + +fn do_variants(variants: &[Variant], params: MdParams) -> String { + let mut md = String::new(); + md.push_str(&format!( + "|:-:|\n|{}|\n", + "Domain Variants".to_right_bold(8, params.options) + )); + md.push_str("|:-:|:-:|:-:|\n|Relations|IDN Table|Variant Names|\n"); + variants.iter().for_each(|v| { + md.push_str(&format!( + "|{}|{}|{}|", + v.relations().make_title_case_list(), + v.idn_table.as_deref().unwrap_or_default(), + v.variant_names + .as_deref() + .unwrap_or_default() + .iter() + .map(|dv| format!( + "ldh: '{}' utf:'{}'", + dv.ldh_name.as_deref().unwrap_or_default(), + dv.unicode_name.as_deref().unwrap_or_default() + )) + .collect::>() + .join(", "), + )) + }); + md.push('\n'); + md +} + +fn do_secure_dns(secure_dns: &SecureDns, params: MdParams) -> String { + let mut md = String::new(); + // multipart data + let mut table = MultiPartTable::new(); + + table = table + .header_ref(&"DNSSEC Information") + .and_nv_ref( + &"Zone Signed", + &secure_dns.zone_signed.as_ref().map(|b| b.to_string()), + ) + .and_nv_ref( + &"Delegation Signed", + &secure_dns.delegation_signed.as_ref().map(|b| b.to_string()), + ) + .and_nv_ref( + &"Max Sig Life", + &secure_dns.max_sig_life.as_ref().map(|u| u.to_string()), + ); + + if let Some(ds_data) = &secure_dns.ds_data { + for (i, ds) in ds_data.iter().enumerate() { + let header = format!("DS Data ({i})").replace_md_chars(); + table = table + .header_ref(&header) + .and_nv_ref(&"Key Tag", &ds.key_tag.as_ref().map(|k| k.to_string())) + .and_nv_ref( + &"Algorithm", + &dns_algorithm(&ds.algorithm.as_ref().and_then(|a| a.as_u8())), + ) + .and_nv_ref(&"Digest", &ds.digest) + .and_nv_ref( + &"Digest Type", + &dns_digest_type(&ds.digest_type.as_ref().and_then(|d| d.as_u8())), + ); + if let Some(events) = &ds.events { + let ds_header = format!("DS ({i}) Events"); + table = events_to_table(events, table, &ds_header, params); + } + if let Some(links) = &ds.links { + let ds_header = format!("DS ({i}) Links"); + table = links_to_table(links, table, &ds_header); + } + } + } + + if let Some(key_data) = &secure_dns.key_data { + for (i, key) in key_data.iter().enumerate() { + let header = format!("Key Data ({i})").replace_md_chars(); + table = table + .header_ref(&header) + .and_nv_ref(&"Flags", &key.flags.as_ref().map(|k| k.to_string())) + .and_nv_ref(&"Protocol", &key.protocol.as_ref().map(|a| a.to_string())) + .and_nv_ref(&"Public Key", &key.public_key) + .and_nv_ref( + &"Algorithm", + &dns_algorithm(&key.algorithm.as_ref().and_then(|a| a.as_u8())), + ); + if let Some(events) = &key.events { + let key_header = format!("Key ({i}) Events"); + table = events_to_table(events, table, &key_header, params); + } + if let Some(links) = &key.links { + let key_header = format!("Key ({i}) Links"); + table = links_to_table(links, table, &key_header); + } + } + } + + // checks + let typeid = TypeId::of::(); + let check_params = CheckParams::from_md(params, typeid); + let checks = secure_dns.get_sub_checks(check_params); + table = checks_to_table(checks, table, params); + + // render table + md.push_str(&table.to_md(params)); + md +} + +fn dns_algorithm(alg: &Option) -> Option { + alg.map(|alg| { + DnsAlgorithmType::mnemonic(alg).map_or(format!("{alg} - Unassigned or Reserved"), |a| { + format!("{alg} - {a}") + }) + }) +} + +fn dns_digest_type(dt: &Option) -> Option { + dt.map(|dt| { + DnsDigestType::mnemonic(dt).map_or(format!("{dt} - Unassigned or Reserved"), |a| { + format!("{dt} - {a}") + }) + }) +} + +impl MdUtil for Domain { + fn get_header_text(&self) -> MdHeaderText { + let header_text = if let Some(unicode_name) = &self.unicode_name { + format!("Domain {}", unicode_name.replace_md_chars()) + } else if let Some(ldh_name) = &self.ldh_name { + format!("Domain {}", ldh_name.replace_md_chars()) + } else if let Some(handle) = &self.object_common.handle { + format!("Domain {}", handle.replace_md_chars()) + } else { + "Domain".to_string() + }; + let mut header_text = MdHeaderText::builder().header_text(header_text); + if let Some(entities) = &self.object_common.entities { + for entity in entities { + header_text = header_text.children_entry(entity.get_header_text()); + } + }; + if let Some(nameservers) = &self.nameservers { + for ns in nameservers { + header_text = header_text.children_entry(ns.get_header_text()); + } + }; + if let Some(network) = &self.network { + header_text = header_text.children_entry(network.get_header_text()); + } + header_text.build() + } +} diff --git a/icann-rdap-client/src/md/entity.rs b/icann-rdap-client/src/md/entity.rs new file mode 100644 index 0000000..8c1436b --- /dev/null +++ b/icann-rdap-client/src/md/entity.rs @@ -0,0 +1,359 @@ +use std::any::TypeId; + +use icann_rdap_common::{ + contact::{NameParts, PostalAddress}, + response::{Entity, EntityRole}, +}; + +use icann_rdap_common::check::{CheckParams, GetChecks, GetSubChecks}; + +use crate::rdap::registered_redactions::{ + are_redactions_registered_for_roles, is_redaction_registered_for_role, + text_or_registered_redaction_for_role, RedactedName, +}; + +use super::{ + redacted::REDACTED_TEXT, + string::StringUtil, + table::{MultiPartTable, ToMpTable}, + types::{checks_to_table, public_ids_to_table}, + FromMd, MdHeaderText, MdParams, MdUtil, ToMd, HR, +}; + +impl ToMd for Entity { + fn to_md(&self, params: MdParams) -> String { + let typeid = TypeId::of::(); + let mut md = String::new(); + md.push_str(&self.common.to_md(params.from_parent(typeid))); + + // header + let header_text = self.get_header_text(); + md.push_str( + &header_text + .to_string() + .to_header(params.heading_level, params.options), + ); + + // A note about the RFC 9537 redactions. A lot of this code is to do RFC 9537 redactions + // that are registered with the IANA. As RFC 9537 is horribly broken, it is likely only + // gTLD registries will use registered redactions, and when they do they will use all + // of them. Therefore, as horribly complicated as this logic is, it attempts to simplify + // things by assuming all the registrations will be used at once, which will be the case + // in the gTLD space. + + // check if registrant or tech ids are RFC 9537 redacted + let mut entity_handle = text_or_registered_redaction_for_role( + params.root, + &RedactedName::RegistryRegistrantId, + self, + &EntityRole::Registrant, + &self.object_common.handle, + REDACTED_TEXT, + ); + entity_handle = text_or_registered_redaction_for_role( + params.root, + &RedactedName::RegistryTechId, + self, + &EntityRole::Technical, + &entity_handle, + REDACTED_TEXT, + ); + + // multipart data + let mut table = MultiPartTable::new(); + + // summary + table = table.summary(header_text); + + // identifiers + table = table + .header_ref(&"Identifiers") + .and_nv_ref(&"Handle", &entity_handle) + .and_nv_ul(&"Roles", Some(self.roles().to_vec())); + if let Some(public_ids) = &self.public_ids { + table = public_ids_to_table(public_ids, table); + } + + if let Some(contact) = self.contact() { + // nutty RFC 9537 redaction stuff + + // check if registrant or tech name are redacted + let mut registrant_name = text_or_registered_redaction_for_role( + params.root, + &RedactedName::RegistrantName, + self, + &EntityRole::Registrant, + &contact.full_name, + REDACTED_TEXT, + ); + registrant_name = text_or_registered_redaction_for_role( + params.root, + &RedactedName::TechName, + self, + &EntityRole::Technical, + ®istrant_name, + REDACTED_TEXT, + ); + + // check to see if registrant postal address parts are redacted + let postal_addresses = if are_redactions_registered_for_roles( + params.root, + &[ + &RedactedName::RegistrantStreet, + &RedactedName::RegistrantCity, + &RedactedName::RegistrantPostalCode, + ], + self, + &[&EntityRole::Registrant], + ) { + let mut new_pas = contact.postal_addresses.clone(); + if let Some(ref mut new_pas) = new_pas { + new_pas.iter_mut().for_each(|pa| { + pa.street_parts = Some(vec![REDACTED_TEXT.to_string()]); + pa.locality = Some(REDACTED_TEXT.to_string()); + pa.postal_code = Some(REDACTED_TEXT.to_string()); + }) + } + new_pas + } else { + contact.postal_addresses + }; + + table = table + .header_ref(&"Contact") + .and_nv_ref_maybe(&"Kind", &contact.kind) + .and_nv_ref_maybe(&"Full Name", ®istrant_name) + .and_nv_ul(&"Titles", contact.titles) + .and_nv_ul(&"Org Roles", contact.roles) + .and_nv_ul(&"Nicknames", contact.nick_names); + if is_redaction_registered_for_role( + params.root, + &RedactedName::RegistrantOrganization, + self, + &EntityRole::Registrant, + ) { + table = table.nv_ref(&"Organization Name", &REDACTED_TEXT.to_string()); + } else { + table = table.and_nv_ul(&"Organization Names", contact.organization_names); + } + table = table.and_nv_ul(&"Languages", contact.langs); + if are_redactions_registered_for_roles( + params.root, + &[ + &RedactedName::RegistrantPhone, + &RedactedName::RegistrantPhoneExt, + &RedactedName::RegistrantFax, + &RedactedName::RegistrantFaxExt, + &RedactedName::TechPhone, + &RedactedName::TechPhoneExt, + ], + self, + &[&EntityRole::Registrant, &EntityRole::Technical], + ) { + table = table.nv_ref(&"Phones", &REDACTED_TEXT.to_string()); + } else { + table = table.and_nv_ul(&"Phones", contact.phones); + } + if are_redactions_registered_for_roles( + params.root, + &[&RedactedName::TechEmail, &RedactedName::RegistrantEmail], + self, + &[&EntityRole::Registrant, &EntityRole::Technical], + ) { + table = table.nv_ref(&"Emails", &REDACTED_TEXT.to_string()); + } else { + table = table.and_nv_ul(&"Emails", contact.emails); + } + table = table + .and_nv_ul(&"Web Contact", contact.contact_uris) + .and_nv_ul(&"URLs", contact.urls); + table = postal_addresses.add_to_mptable(table, params); + table = contact.name_parts.add_to_mptable(table, params) + } + + // common object stuff + table = self.object_common.add_to_mptable(table, params); + + // checks + let check_params = CheckParams::from_md(params, typeid); + let mut checks = self.object_common.get_sub_checks(check_params); + checks.push(self.get_checks(check_params)); + table = checks_to_table(checks, table, params); + + // render table + md.push_str(&table.to_md(params)); + + // remarks + md.push_str(&self.object_common.remarks.to_md(params.from_parent(typeid))); + + // only other object classes from here + md.push_str(HR); + + // entities + md.push_str( + &self + .object_common + .entities + .to_md(params.from_parent(typeid)), + ); + + // redacted + if let Some(redacted) = &self.object_common.redacted { + md.push_str(&redacted.as_slice().to_md(params.from_parent(typeid))); + } + + md.push('\n'); + md + } +} + +impl ToMd for Option> { + fn to_md(&self, params: MdParams) -> String { + let mut md = String::new(); + if let Some(entities) = &self { + entities + .iter() + .for_each(|entity| md.push_str(&entity.to_md(params.next_level()))); + } + md + } +} + +impl ToMpTable for Option> { + fn add_to_mptable(&self, mut table: MultiPartTable, params: MdParams) -> MultiPartTable { + if let Some(addrs) = self { + for addr in addrs { + table = addr.add_to_mptable(table, params); + } + } + table + } +} + +impl ToMpTable for PostalAddress { + fn add_to_mptable(&self, mut table: MultiPartTable, _params: MdParams) -> MultiPartTable { + if self.contexts.is_some() && self.preference.is_some() { + table = table.nv( + &"Address", + format!( + "{} (pref: {})", + self.contexts.as_ref().unwrap().join(" "), + self.preference.unwrap() + ), + ); + } else if self.contexts.is_some() { + table = table.nv(&"Address", self.contexts.as_ref().unwrap().join(" ")); + } else if self.preference.is_some() { + table = table.nv( + &"Address", + format!("preference: {}", self.preference.unwrap()), + ); + } else { + table = table.nv(&"Address", ""); + } + if let Some(street_parts) = &self.street_parts { + table = table.nv_ul_ref(&"Street", street_parts.iter().collect()); + } + if let Some(locality) = &self.locality { + table = table.nv_ref(&"Locality", locality); + } + if self.region_name.is_some() && self.region_code.is_some() { + table = table.nv( + &"Region", + format!( + "{} ({})", + self.region_name.as_ref().unwrap(), + self.region_code.as_ref().unwrap() + ), + ); + } else if let Some(region_name) = &self.region_name { + table = table.nv_ref(&"Region", region_name); + } else if let Some(region_code) = &self.region_code { + table = table.nv_ref(&"Region", region_code); + } + if self.country_name.is_some() && self.country_code.is_some() { + table = table.nv( + &"Country", + format!( + "{} ({})", + self.country_name.as_ref().unwrap(), + self.country_code.as_ref().unwrap() + ), + ); + } else if let Some(country_name) = &self.country_name { + table = table.nv_ref(&"Country", country_name); + } else if let Some(country_code) = &self.country_code { + table = table.nv_ref(&"Country", country_code); + } + if let Some(postal_code) = &self.postal_code { + table = table.nv_ref(&"Postal Code", postal_code); + } + if let Some(full_address) = &self.full_address { + let parts = full_address.split('\n').collect::>(); + for (i, p) in parts.iter().enumerate() { + table = table.nv_ref(&i.to_string(), p); + } + } + table + } +} + +impl ToMpTable for Option { + fn add_to_mptable(&self, mut table: MultiPartTable, _params: MdParams) -> MultiPartTable { + if let Some(parts) = self { + if let Some(prefixes) = &parts.prefixes { + table = table.nv(&"Honorifics", prefixes.join(", ")); + } + if let Some(given_names) = &parts.given_names { + table = table.nv_ul(&"Given Names", given_names.to_vec()); + } + if let Some(middle_names) = &parts.middle_names { + table = table.nv_ul(&"Middle Names", middle_names.to_vec()); + } + if let Some(surnames) = &parts.surnames { + table = table.nv_ul(&"Surnames", surnames.to_vec()); + } + if let Some(suffixes) = &parts.suffixes { + table = table.nv(&"Suffixes", suffixes.join(", ")); + } + } + table + } +} + +impl MdUtil for Entity { + fn get_header_text(&self) -> MdHeaderText { + let role = self + .roles() + .first() + .map(|s| s.replace_md_chars().to_title_case()); + let header_text = if let Some(handle) = &self.object_common.handle { + if let Some(role) = role { + format!("{} ({})", handle.replace_md_chars(), role) + } else { + format!("Entity {}", handle) + } + } else if let Some(role) = role { + role.to_string() + } else { + "Entity".to_string() + }; + let mut header_text = MdHeaderText::builder().header_text(header_text); + if let Some(entities) = &self.object_common.entities { + for entity in entities { + header_text = header_text.children_entry(entity.get_header_text()); + } + }; + if let Some(networks) = &self.networks { + for network in networks { + header_text = header_text.children_entry(network.get_header_text()); + } + }; + if let Some(autnums) = &self.autnums { + for autnum in autnums { + header_text = header_text.children_entry(autnum.get_header_text()); + } + }; + header_text.build() + } +} diff --git a/icann-rdap-client/src/md/error.rs b/icann-rdap-client/src/md/error.rs new file mode 100644 index 0000000..2d4708c --- /dev/null +++ b/icann-rdap-client/src/md/error.rs @@ -0,0 +1,21 @@ +use std::any::TypeId; + +use icann_rdap_common::response::Rfc9083Error; + +use super::{MdHeaderText, MdParams, MdUtil, ToMd, HR}; + +impl ToMd for Rfc9083Error { + fn to_md(&self, params: MdParams) -> String { + let mut md = String::new(); + md.push_str(&self.common.to_md(params.from_parent(TypeId::of::()))); + md.push_str(HR); + md.push('\n'); + md + } +} + +impl MdUtil for Rfc9083Error { + fn get_header_text(&self) -> MdHeaderText { + MdHeaderText::builder().header_text("RDAP Error").build() + } +} diff --git a/icann-rdap-client/src/md/help.rs b/icann-rdap-client/src/md/help.rs new file mode 100644 index 0000000..44c888b --- /dev/null +++ b/icann-rdap-client/src/md/help.rs @@ -0,0 +1,21 @@ +use std::any::TypeId; + +use icann_rdap_common::response::Help; + +use super::{MdHeaderText, MdParams, MdUtil, ToMd, HR}; + +impl ToMd for Help { + fn to_md(&self, params: MdParams) -> String { + let mut md = String::new(); + md.push_str(&self.common.to_md(params.from_parent(TypeId::of::()))); + md.push_str(HR); + md.push('\n'); + md + } +} + +impl MdUtil for Help { + fn get_header_text(&self) -> MdHeaderText { + MdHeaderText::builder().header_text("Server Help").build() + } +} diff --git a/icann-rdap-client/src/md/mod.rs b/icann-rdap-client/src/md/mod.rs new file mode 100644 index 0000000..af227d3 --- /dev/null +++ b/icann-rdap-client/src/md/mod.rs @@ -0,0 +1,205 @@ +//! Converts RDAP to Markdown. + +use { + crate::rdap::rr::RequestData, + buildstructor::Builder, + icann_rdap_common::{check::CheckParams, httpdata::HttpData, response::RdapResponse}, + std::{any::TypeId, char}, + strum::EnumMessage, +}; + +use icann_rdap_common::check::{CheckClass, Checks, CHECK_CLASS_LEN}; + +use self::string::StringUtil; + +pub mod autnum; +pub mod domain; +pub mod entity; +pub mod error; +pub mod help; +pub mod nameserver; +pub mod network; +pub mod redacted; +pub mod search; +pub mod string; +pub mod table; +pub mod types; + +pub(crate) const _CODE_INDENT: &str = " "; + +pub(crate) const HR: &str = "----------------------------------------\n"; + +/// Specifies options for generating markdown. +pub struct MdOptions { + /// If true, do not use Unicode characters. + pub no_unicode_chars: bool, + + /// The character used for text styling of bold and italics. + pub text_style_char: char, + + /// If true, headers use the hash marks or under lines. + pub hash_headers: bool, + + /// If true, the text_style_char will appear in a justified text. + pub style_in_justify: bool, +} + +impl Default for MdOptions { + fn default() -> Self { + Self { + no_unicode_chars: false, + text_style_char: '*', + hash_headers: true, + style_in_justify: false, + } + } +} + +impl MdOptions { + /// Defaults for markdown that looks more like plain text. + pub fn plain_text() -> Self { + Self { + no_unicode_chars: true, + text_style_char: '_', + hash_headers: false, + style_in_justify: true, + } + } +} + +#[derive(Clone, Copy)] +pub struct MdParams<'a> { + pub heading_level: usize, + pub root: &'a RdapResponse, + pub http_data: &'a HttpData, + pub parent_type: TypeId, + pub check_types: &'a [CheckClass], + pub options: &'a MdOptions, + pub req_data: &'a RequestData<'a>, +} + +impl MdParams<'_> { + pub fn from_parent(&self, parent_type: TypeId) -> Self { + Self { + parent_type, + heading_level: self.heading_level, + root: self.root, + http_data: self.http_data, + check_types: self.check_types, + options: self.options, + req_data: self.req_data, + } + } + + pub fn next_level(&self) -> Self { + Self { + heading_level: self.heading_level + 1, + ..*self + } + } +} + +pub trait ToMd { + fn to_md(&self, params: MdParams) -> String; +} + +impl ToMd for RdapResponse { + fn to_md(&self, params: MdParams) -> String { + let mut md = String::new(); + md.push_str(¶ms.http_data.to_md(params)); + let variant_md = match &self { + Self::Entity(entity) => entity.to_md(params), + Self::Domain(domain) => domain.to_md(params), + Self::Nameserver(nameserver) => nameserver.to_md(params), + Self::Autnum(autnum) => autnum.to_md(params), + Self::Network(network) => network.to_md(params), + Self::DomainSearchResults(results) => results.to_md(params), + Self::EntitySearchResults(results) => results.to_md(params), + Self::NameserverSearchResults(results) => results.to_md(params), + Self::ErrorResponse(error) => error.to_md(params), + Self::Help(help) => help.to_md(params), + }; + md.push_str(&variant_md); + md + } +} + +pub trait MdUtil { + fn get_header_text(&self) -> MdHeaderText; +} + +#[derive(Builder)] +pub struct MdHeaderText { + header_text: String, + children: Vec, +} + +#[allow(clippy::to_string_trait_impl)] +impl ToString for MdHeaderText { + fn to_string(&self) -> String { + self.header_text.clone() + } +} + +impl MdUtil for RdapResponse { + fn get_header_text(&self) -> MdHeaderText { + match &self { + Self::Entity(entity) => entity.get_header_text(), + Self::Domain(domain) => domain.get_header_text(), + Self::Nameserver(nameserver) => nameserver.get_header_text(), + Self::Autnum(autnum) => autnum.get_header_text(), + Self::Network(network) => network.get_header_text(), + Self::DomainSearchResults(results) => results.get_header_text(), + Self::EntitySearchResults(results) => results.get_header_text(), + Self::NameserverSearchResults(results) => results.get_header_text(), + Self::ErrorResponse(error) => error.get_header_text(), + Self::Help(help) => help.get_header_text(), + } + } +} + +pub(crate) fn checks_ul(checks: &Checks, params: MdParams) -> String { + let mut md = String::new(); + checks + .items + .iter() + .filter(|item| params.check_types.contains(&item.check_class)) + .for_each(|item| { + md.push_str(&format!( + "* {}: {}\n", + &item + .check_class + .to_string() + .to_right_em(*CHECK_CLASS_LEN, params.options), + item.check + .get_message() + .expect("Check has no message. Coding error.") + )) + }); + md +} + +pub(crate) trait FromMd<'a> { + fn from_md(md_params: MdParams<'a>, parent_type: TypeId) -> Self; + fn from_md_no_parent(md_params: MdParams<'a>) -> Self; +} + +impl<'a> FromMd<'a> for CheckParams<'a> { + fn from_md(md_params: MdParams<'a>, parent_type: TypeId) -> Self { + Self { + do_subchecks: false, + root: md_params.root, + parent_type, + allow_unreg_ext: false, + } + } + + fn from_md_no_parent(md_params: MdParams<'a>) -> Self { + Self { + do_subchecks: false, + root: md_params.root, + parent_type: md_params.parent_type, + allow_unreg_ext: false, + } + } +} diff --git a/icann-rdap-client/src/md/nameserver.rs b/icann-rdap-client/src/md/nameserver.rs new file mode 100644 index 0000000..54da50d --- /dev/null +++ b/icann-rdap-client/src/md/nameserver.rs @@ -0,0 +1,106 @@ +use std::any::TypeId; + +use icann_rdap_common::response::Nameserver; + +use icann_rdap_common::check::{CheckParams, GetChecks, GetSubChecks}; + +use super::{ + string::StringUtil, + table::{MultiPartTable, ToMpTable}, + types::checks_to_table, + FromMd, MdHeaderText, MdParams, MdUtil, ToMd, HR, +}; + +impl ToMd for Nameserver { + fn to_md(&self, params: MdParams) -> String { + let typeid = TypeId::of::(); + let mut md = String::new(); + + // other common stuff + md.push_str(&self.common.to_md(params.from_parent(typeid))); + + // header + let header_text = self.get_header_text(); + md.push_str( + &header_text + .to_string() + .to_header(params.heading_level, params.options), + ); + + // multipart data + let mut table = MultiPartTable::new(); + + // summary + table = table.summary(header_text); + + // identifiers + table = table + .header_ref(&"Identifiers") + .and_nv_ref(&"LDH Name", &self.ldh_name) + .and_nv_ref(&"Unicode Name", &self.unicode_name) + .and_nv_ref(&"Handle", &self.object_common.handle); + if let Some(addresses) = &self.ip_addresses { + if let Some(v4) = &addresses.v4 { + table = table.nv_ul_ref(&"Ipv4", v4.vec().iter().collect()); + } + if let Some(v6) = &addresses.v6 { + table = table.nv_ul_ref(&"Ipv6", v6.vec().iter().collect()); + } + } + + // common object stuff + table = self.object_common.add_to_mptable(table, params); + + // checks + let check_params = CheckParams::from_md(params, typeid); + let mut checks = self.object_common.get_sub_checks(check_params); + checks.push(self.get_checks(check_params)); + table = checks_to_table(checks, table, params); + + // render table + md.push_str(&table.to_md(params)); + + // remarks + md.push_str(&self.object_common.remarks.to_md(params.from_parent(typeid))); + + // only other object classes from here + md.push_str(HR); + + // entities + md.push_str( + &self + .object_common + .entities + .to_md(params.from_parent(typeid)), + ); + + // redacted + if let Some(redacted) = &self.object_common.redacted { + md.push_str(&redacted.as_slice().to_md(params.from_parent(typeid))); + } + + md.push('\n'); + md + } +} + +impl MdUtil for Nameserver { + fn get_header_text(&self) -> MdHeaderText { + let header_text = if let Some(unicode_name) = &self.unicode_name { + format!("Nameserver {}", unicode_name.replace_md_chars()) + } else if let Some(ldh_name) = &self.ldh_name { + format!("Nameserver {}", ldh_name.replace_md_chars()) + } else if let Some(handle) = &self.object_common.handle { + format!("Nameserver {}", handle.replace_md_chars()) + } else { + "Domain".to_string() + }; + let mut header_text = MdHeaderText::builder().header_text(header_text); + if let Some(entities) = &self.object_common.entities { + for entity in entities { + header_text = header_text.children_entry(entity.get_header_text()); + } + }; + header_text.build() + } +} diff --git a/icann-rdap-client/src/md/network.rs b/icann-rdap-client/src/md/network.rs new file mode 100644 index 0000000..6997ad5 --- /dev/null +++ b/icann-rdap-client/src/md/network.rs @@ -0,0 +1,108 @@ +use std::any::TypeId; + +use icann_rdap_common::{ + check::{CheckParams, GetChecks, GetSubChecks}, + response::Network, +}; + +use super::{ + string::StringUtil, + table::{MultiPartTable, ToMpTable}, + types::checks_to_table, + FromMd, MdHeaderText, MdParams, MdUtil, ToMd, HR, +}; + +impl ToMd for Network { + fn to_md(&self, params: MdParams) -> String { + let typeid = TypeId::of::(); + let mut md = String::new(); + md.push_str(&self.common.to_md(params)); + + let header_text = self.get_header_text(); + md.push_str( + &header_text + .to_string() + .to_header(params.heading_level, params.options), + ); + + // multipart data + let mut table = MultiPartTable::new(); + + // summary + table = table.summary(header_text); + + // identifiers + table = table + .header_ref(&"Identifiers") + .and_nv_ref(&"Start Address", &self.start_address) + .and_nv_ref(&"End Address", &self.end_address) + .and_nv_ref(&"IP Version", &self.ip_version) + .and_nv_ul(&"CIDR", self.cidr0_cidrs.clone()) + .and_nv_ref(&"Handle", &self.object_common.handle) + .and_nv_ref(&"Parent Handle", &self.parent_handle) + .and_nv_ref(&"Network Type", &self.network_type) + .and_nv_ref(&"Network Name", &self.name) + .and_nv_ref(&"Country", &self.country); + + // common object stuff + table = self.object_common.add_to_mptable(table, params); + + // checks + let check_params = CheckParams::from_md(params, typeid); + let mut checks = self.object_common.get_sub_checks(check_params); + checks.push(self.get_checks(check_params)); + table = checks_to_table(checks, table, params); + + // render table + md.push_str(&table.to_md(params)); + + // remarks + md.push_str(&self.object_common.remarks.to_md(params.from_parent(typeid))); + + // only other object classes from here + md.push_str(HR); + + // entities + md.push_str( + &self + .object_common + .entities + .to_md(params.from_parent(typeid)), + ); + + // redacted + if let Some(redacted) = &self.object_common.redacted { + md.push_str(&redacted.as_slice().to_md(params.from_parent(typeid))); + } + + md.push('\n'); + md + } +} + +impl MdUtil for Network { + fn get_header_text(&self) -> MdHeaderText { + let header_text = if self.start_address.is_some() && self.end_address.is_some() { + format!( + "IP Network {} - {}", + &self.start_address.as_ref().unwrap().replace_md_chars(), + &self.end_address.as_ref().unwrap().replace_md_chars() + ) + } else if let Some(start_address) = &self.start_address { + format!("IP Network {}", start_address.replace_md_chars()) + } else if let Some(handle) = &self.object_common.handle { + format!("IP Network {}", handle.replace_md_chars()) + } else if let Some(name) = &self.name { + format!("IP Network {}", name.replace_md_chars()) + } else { + "IP Network".to_string() + }; + let mut header_text = MdHeaderText::builder().header_text(header_text); + if let Some(entities) = &self.object_common.entities { + for entity in entities { + header_text = header_text.children_entry(entity.get_header_text()); + } + }; + header_text.build() + } +} diff --git a/icann-rdap-client/src/md/redacted.rs b/icann-rdap-client/src/md/redacted.rs new file mode 100644 index 0000000..c375e59 --- /dev/null +++ b/icann-rdap-client/src/md/redacted.rs @@ -0,0 +1,279 @@ +use std::str::FromStr; + +use { + icann_rdap_common::response::redacted::Redacted, + jsonpath::replace_with, + jsonpath_lib as jsonpath, + jsonpath_rust::{JsonPathFinder, JsonPathInst}, + serde_json::{json, Value}, +}; + +use { + super::{string::StringUtil, table::MultiPartTable, MdOptions, MdParams, ToMd}, + icann_rdap_common::response::RdapResponse, +}; + +/// The text to appear if something is redacted. +/// +/// This should be REDACTED in bold. +pub const REDACTED_TEXT: &str = "*REDACTED*"; + +impl ToMd for &[Redacted] { + fn to_md(&self, params: MdParams) -> String { + let mut md = String::new(); + + // header + let header_text = "Redacted".to_string(); + md.push_str(&header_text.to_header(params.heading_level, params.options)); + + // multipart data + let mut table = MultiPartTable::new(); + table = table.header_ref(&"Fields"); + + for (index, redacted) in self.iter().enumerate() { + let options = MdOptions { + text_style_char: '*', + ..Default::default() + }; + + // make the name bold + let name = "Redaction"; + let b_name = name.to_bold(&options); + // build the table + table = table.and_nv_ref(&b_name, &Some((index + 1).to_string())); + + // Get the data itself + let name_data = redacted + .name + .description + .clone() + .or(redacted.name.type_field.clone()); + let method_data = redacted.method.as_ref().map(|m| m.to_string()); + let reason_data = redacted.reason.as_ref().map(|m| m.to_string()); + + // Special case the 'column' fields + table = table + .and_nv_ref(&"name".to_title_case(), &name_data) + .and_nv_ref(&"prePath".to_title_case(), &redacted.pre_path) + .and_nv_ref(&"postPath".to_title_case(), &redacted.post_path) + .and_nv_ref( + &"replacementPath".to_title_case(), + &redacted.replacement_path, + ) + .and_nv_ref(&"pathLang".to_title_case(), &redacted.path_lang) + .and_nv_ref(&"method".to_title_case(), &method_data) + .and_nv_ref(&"reason".to_title_case(), &reason_data); + + // we don't have these right now but if we put them in later we will need them + // let check_params = CheckParams::from_md(params, typeid); + // let mut checks = redacted.object_common.get_sub_checks(check_params); + // checks.push(redacted.get_checks(check_params)); + // table = checks_to_table(checks, table, params); + } + + // render table + md.push_str(&table.to_md(params)); + md.push('\n'); + md + } +} + +// this is our public entry point +pub fn replace_redacted_items(orignal_response: RdapResponse) -> RdapResponse { + // convert the RdapResponse to a string + let rdap_json = serde_json::to_string(&orignal_response).unwrap(); + + // Redaction is not a top-level entity so we have to check the JSON + // to see if anything exists in the way of "redacted", this should find it in the rdapConformance + if !rdap_json.contains("\"redacted\"") { + // If there are no redactions, return the original response + return orignal_response; + } + + // convert the string to a JSON Value + let mut rdap_json_response: Value = serde_json::from_str(&rdap_json).unwrap(); + + // this double checks to see if "redacted" is an array + if rdap_json_response["redacted"].as_array().is_none() { + // If "redacted" is not an array, return the original response + return orignal_response; + } + + // Initialize the final response with the original response + let mut response = orignal_response; + // pull the redacted array out of the JSON + let redacted_array_option = rdap_json_response["redacted"].as_array().cloned(); + + // if there are any redactions we need to do some modifications + if let Some(ref redacted_array) = redacted_array_option { + let new_json_response = convert_redactions(&mut rdap_json_response, redacted_array).clone(); + // convert the Value back to a RdapResponse + response = serde_json::from_value(new_json_response).unwrap(); + } + + // send the response back so we can display it to the client + response +} + +fn convert_redactions<'a>( + rdap_json_response: &'a mut Value, + redacted_array: &'a [Value], +) -> &'a mut Value { + for item in redacted_array { + let item_map = item.as_object().unwrap(); + let post_path = get_string_from_map(item_map, "postPath"); + let method = get_string_from_map(item_map, "method"); + + if let Some(path_lang) = item_map.get("pathLang") { + if let Some(path_lang) = path_lang.as_str() { + if !path_lang.eq_ignore_ascii_case("jsonpath") { + continue; + } + } + } + + // if method doesn't equal emptyValue or partialValue, we don't need to do anything, we can skip to the next item + if method != "emptyValue" && method != "partialValue" && !post_path.is_empty() { + continue; + } + + match JsonPathInst::from_str(&post_path) { + Ok(json_path) => { + let finder = + JsonPathFinder::new(Box::new(rdap_json_response.clone()), Box::new(json_path)); + let matches = finder.find_as_path(); + if let Value::Array(paths) = matches { + if paths.is_empty() { + continue; // we don't need to do anything, we can skip to the next item + } else { + for path_value in paths { + if let Value::String(found_path) = path_value { + let no_value = Value::String("NO_VALUE".to_string()); + let json_pointer = convert_to_json_pointer_path(&found_path); + let value_at_path = rdap_json_response + .pointer(&json_pointer) + .unwrap_or(&no_value); + if value_at_path.is_string() { + // grab the value at the end point of the JSON path + let end_of_path_value = + match rdap_json_response.pointer(&json_pointer) { + Some(value) => value.clone(), + None => { + continue; + } + }; + let replaced_json = replace_with( + rdap_json_response.clone(), + &found_path, + &mut |x| { + // STRING ONLY! This is the only spot where we are ACTUALLY replacing or updating something + if x.is_string() { + match x.as_str() { + Some("") => Some(json!("*REDACTED*")), + Some(s) => Some(json!(format!("*{}*", s))), + _ => Some(json!("*REDACTED*")), + } + } else { + Some(end_of_path_value.clone()) // it isn't a string, put it back in there + } + }, + ); + match replaced_json { + Ok(new_json) => *rdap_json_response = new_json, + _ => { + // why did we fail to modify the JSON? + } + }; + } + } + } + } + } + } + _ => { + // do nothing + } + } + } + + rdap_json_response +} + +// utility functions +fn convert_to_json_pointer_path(path: &str) -> String { + let pointer_path = path + .trim_start_matches('$') + .replace('.', "/") + .replace("['", "/") + .replace("']", "") + .replace('[', "/") + .replace(']', "") + .replace("//", "/"); + pointer_path +} + +fn get_string_from_map(map: &serde_json::Map, key: &str) -> String { + map.get(key) + .and_then(|v| v.as_str()) + .map(|s| s.to_string()) + .unwrap_or_default() +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use { + serde_json::Value, + std::{error::Error, fs::File, io::Read}, + }; + + fn process_redacted_file(file_path: &str) -> Result> { + let mut file = File::open(file_path)?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + + // this has to be setup very specifically, just like replace_redacted_items is setup. + let mut rdap_json_response: Value = serde_json::from_str(&contents)?; + let redacted_array_option = rdap_json_response["redacted"].as_array().cloned(); + // we are testing parse_redacted_json here -- just the JSON transforms + if let Some(redacted_array) = redacted_array_option { + crate::md::redacted::convert_redactions(&mut rdap_json_response, &redacted_array); + } else { + panic!("No redacted array found in the JSON"); + } + let pretty_json = serde_json::to_string_pretty(&rdap_json_response)?; + println!("{}", pretty_json); + Ok(pretty_json) + } + + #[test] + fn test_process_empty_value() { + let expected_output = + std::fs::read_to_string("src/test_files/example-1_empty_value-expected.json").unwrap(); + let output = process_redacted_file("src/test_files/example-1_empty_value.json").unwrap(); + assert_eq!(output, expected_output); + } + + #[test] + fn test_process_partial_value() { + let expected_output = + std::fs::read_to_string("src/test_files/example-2_partial_value-expected.json") + .unwrap(); + let output = process_redacted_file("src/test_files/example-2_partial_value.json").unwrap(); + assert_eq!(output, expected_output); + } + + #[test] + fn test_process_dont_replace_number() { + let expected_output = std::fs::read_to_string( + "src/test_files/example-3-dont_replace_redaction_of_a_number.json", + ) + .unwrap(); + // we don't need an expected for this one, it should remain unchanged + let output = process_redacted_file( + "src/test_files/example-3-dont_replace_redaction_of_a_number.json", + ) + .unwrap(); + assert_eq!(output, expected_output); + } +} diff --git a/icann-rdap-client/src/md/search.rs b/icann-rdap-client/src/md/search.rs new file mode 100644 index 0000000..6adf177 --- /dev/null +++ b/icann-rdap-client/src/md/search.rs @@ -0,0 +1,82 @@ +use std::any::TypeId; + +use icann_rdap_common::response::{ + DomainSearchResults, EntitySearchResults, NameserverSearchResults, +}; + +use super::{MdHeaderText, MdParams, MdUtil, ToMd}; + +impl ToMd for DomainSearchResults { + fn to_md(&self, params: MdParams) -> String { + let typeid = TypeId::of::(); + let mut md = String::new(); + md.push_str(&self.common.to_md(params.from_parent(typeid))); + self.results.iter().for_each(|result| { + md.push_str(&result.to_md(MdParams { + heading_level: params.heading_level + 1, + parent_type: typeid, + ..params + })) + }); + md.push('\n'); + md + } +} + +impl ToMd for NameserverSearchResults { + fn to_md(&self, params: MdParams) -> String { + let typeid = TypeId::of::(); + let mut md = String::new(); + md.push_str(&self.common.to_md(params.from_parent(typeid))); + self.results.iter().for_each(|result| { + md.push_str(&result.to_md(MdParams { + heading_level: params.heading_level + 1, + parent_type: typeid, + ..params + })) + }); + md.push('\n'); + md + } +} + +impl ToMd for EntitySearchResults { + fn to_md(&self, params: MdParams) -> String { + let typeid = TypeId::of::(); + let mut md = String::new(); + md.push_str(&self.common.to_md(params.from_parent(typeid))); + self.results.iter().for_each(|result| { + md.push_str(&result.to_md(MdParams { + heading_level: params.heading_level + 1, + parent_type: typeid, + ..params + })) + }); + md.push('\n'); + md + } +} + +impl MdUtil for DomainSearchResults { + fn get_header_text(&self) -> MdHeaderText { + MdHeaderText::builder() + .header_text("Domain Search Results") + .build() + } +} + +impl MdUtil for EntitySearchResults { + fn get_header_text(&self) -> MdHeaderText { + MdHeaderText::builder() + .header_text("Entity Search Results") + .build() + } +} + +impl MdUtil for NameserverSearchResults { + fn get_header_text(&self) -> MdHeaderText { + MdHeaderText::builder() + .header_text("Nameserver Search Results") + .build() + } +} diff --git a/icann-rdap-client/src/md/string.rs b/icann-rdap-client/src/md/string.rs new file mode 100644 index 0000000..41f2874 --- /dev/null +++ b/icann-rdap-client/src/md/string.rs @@ -0,0 +1,267 @@ +use chrono::DateTime; + +use super::{MdOptions, MdParams}; + +pub trait StringUtil { + /// Replaces and filters markdown characters. + fn replace_md_chars(self) -> String; + fn to_em(self, options: &MdOptions) -> String; + fn to_bold(self, options: &MdOptions) -> String; + fn to_inline(self, options: &MdOptions) -> String; + fn to_header(self, level: usize, options: &MdOptions) -> String; + fn to_right(self, width: usize, options: &MdOptions) -> String; + fn to_right_em(self, width: usize, options: &MdOptions) -> String; + fn to_right_bold(self, width: usize, options: &MdOptions) -> String; + fn to_left(self, width: usize, options: &MdOptions) -> String; + fn to_left_em(self, width: usize, options: &MdOptions) -> String; + fn to_left_bold(self, width: usize, options: &MdOptions) -> String; + fn to_center(self, width: usize, options: &MdOptions) -> String; + fn to_center_em(self, width: usize, options: &MdOptions) -> String; + fn to_center_bold(self, width: usize, options: &MdOptions) -> String; + fn to_title_case(self) -> String; + fn to_words_title_case(self) -> String; + fn to_cap_acronyms(self) -> String; + fn format_date_time(self, params: MdParams) -> Option; +} + +impl StringUtil for T { + fn replace_md_chars(self) -> String { + self.to_string() + .replace(|c: char| c.is_whitespace(), " ") + .chars() + .map(|c| match c { + '*' | '_' | '|' | '#' => format!("\\{c}"), + _ => c.to_string(), + }) + .collect() + } + + fn to_em(self, options: &MdOptions) -> String { + format!( + "{}{}{}", + options.text_style_char, + self.to_string(), + options.text_style_char + ) + } + + fn to_bold(self, options: &MdOptions) -> String { + format!( + "{}{}{}{}{}", + options.text_style_char, + options.text_style_char, + self.to_string(), + options.text_style_char, + options.text_style_char + ) + } + + fn to_inline(self, _options: &MdOptions) -> String { + format!("`{}`", self.to_string(),) + } + + fn to_header(self, level: usize, options: &MdOptions) -> String { + let s = self.to_string(); + if options.hash_headers { + format!("{} {s}\n\n", "#".repeat(level)) + } else { + let line = if level == 1 { + "=".repeat(s.len()) + } else { + "-".repeat(s.len()) + }; + format!("{s}\n{line}\n\n") + } + } + + fn to_right(self, width: usize, options: &MdOptions) -> String { + let str = self.to_string(); + if options.no_unicode_chars { + format!("{str:>width$}") + } else { + format!("{str:\u{2003}>width$}") + } + } + + fn to_right_em(self, width: usize, options: &MdOptions) -> String { + if options.style_in_justify { + self.to_em(options).to_right(width, options) + } else { + self.to_right(width, options).to_em(options) + } + } + + fn to_right_bold(self, width: usize, options: &MdOptions) -> String { + if options.style_in_justify { + self.to_bold(options).to_right(width, options) + } else { + self.to_right(width, options).to_bold(options) + } + } + + fn to_left(self, width: usize, options: &MdOptions) -> String { + let str = self.to_string(); + if options.no_unicode_chars { + format!("{str: String { + if options.style_in_justify { + self.to_em(options).to_left(width, options) + } else { + self.to_left(width, options).to_em(options) + } + } + + fn to_left_bold(self, width: usize, options: &MdOptions) -> String { + if options.style_in_justify { + self.to_bold(options).to_left(width, options) + } else { + self.to_left(width, options).to_bold(options) + } + } + + fn to_center(self, width: usize, options: &MdOptions) -> String { + let str = self.to_string(); + if options.no_unicode_chars { + format!("{str:^width$}") + } else { + format!("{str:\u{2003}^width$}") + } + } + + fn to_center_em(self, width: usize, options: &MdOptions) -> String { + if options.style_in_justify { + self.to_em(options).to_center(width, options) + } else { + self.to_center(width, options).to_bold(options) + } + } + + fn to_center_bold(self, width: usize, options: &MdOptions) -> String { + if options.style_in_justify { + self.to_bold(options).to_center(width, options) + } else { + self.to_center(width, options).to_bold(options) + } + } + + fn to_title_case(self) -> String { + self.to_string() + .char_indices() + .map(|(i, mut c)| { + if i == 0 { + c.make_ascii_uppercase(); + c + } else { + c + } + }) + .collect::() + } + + fn to_words_title_case(self) -> String { + self.to_string() + .split_whitespace() + .map(|s| s.to_title_case()) + .collect::>() + .join(" ") + } + + fn format_date_time(self, _params: MdParams) -> Option { + let date = DateTime::parse_from_rfc3339(&self.to_string()).ok()?; + Some(date.format("%a, %v %X %Z").to_string()) + } + + fn to_cap_acronyms(self) -> String { + self.to_string() + .replace_md_chars() + .replace("rdap", "RDAP") + .replace("icann", "ICANN") + .replace("arin", "ARIN") + .replace("ripe", "RIPE") + .replace("apnic", "APNIC") + .replace("lacnic", "LACNIC") + .replace("afrinic", "AFRINIC") + .replace("nro", "NRO") + .replace("ietf", "IETF") + } +} + +pub(crate) trait StringListUtil { + fn make_list_all_title_case(self) -> Vec; + fn make_title_case_list(self) -> String; +} + +impl StringListUtil for &[T] { + fn make_list_all_title_case(self) -> Vec { + self.iter() + .map(|s| s.to_string().to_words_title_case()) + .collect::>() + } + + fn make_title_case_list(self) -> String { + self.make_list_all_title_case().join(", ") + } +} + +#[cfg(test)] +mod tests { + use rstest::rstest; + + use super::{StringListUtil, StringUtil}; + + #[rstest] + #[case("foo", "Foo")] + #[case("FOO", "FOO")] + fn test_words(#[case] word: &str, #[case] expected: &str) { + // GIVEN in arguments + + // WHEN + let actual = word.to_title_case(); + + // THEN + assert_eq!(actual, expected); + } + + #[rstest] + #[case("foo bar", "Foo Bar")] + #[case("foo bar", "Foo Bar")] + #[case("foO baR", "FoO BaR")] + fn test_sentences(#[case] sentence: &str, #[case] expected: &str) { + // GIVEN in arguments + + // WHEN + let actual = sentence.to_words_title_case(); + + // THEN + assert_eq!(actual, expected); + } + + #[test] + fn test_list_of_sentences() { + // GIVEN + let v = ["foo bar", "foO baR"]; + + // WHEN + let actual = v.make_list_all_title_case(); + + // THEN + assert_eq!(actual, vec!["Foo Bar".to_string(), "FoO BaR".to_string()]) + } + + #[test] + fn test_list() { + // GIVEN + let list = ["foo bar", "bizz buzz"]; + + // WHEN + let actual = list.make_title_case_list(); + + // THEN + assert_eq!(actual, "Foo Bar, Bizz Buzz"); + } +} diff --git a/icann-rdap-client/src/md/table.rs b/icann-rdap-client/src/md/table.rs new file mode 100644 index 0000000..6a45e31 --- /dev/null +++ b/icann-rdap-client/src/md/table.rs @@ -0,0 +1,477 @@ +use std::cmp::max; + +use super::{string::StringUtil, MdHeaderText, MdOptions, MdParams, ToMd}; + +pub(crate) trait ToMpTable { + fn add_to_mptable(&self, table: MultiPartTable, params: MdParams) -> MultiPartTable; +} + +/// A datastructue to hold various row types for a markdown table. +/// +/// This datastructure has the following types of rows: +/// * header - just the left most column which is centered and bolded text +/// * name/value - first column is the name and the second column is data. +/// +/// For name/value rows, the name is right justified. Name/value rows may also +/// have unordered (bulleted) lists. In markdown, there is no such thing as a +/// multiline row, so this creates multiple rows where the name is left blank. +pub struct MultiPartTable { + rows: Vec, +} + +enum Row { + Header(String), + NameValue((String, String)), + MultiValue(Vec), +} + +impl Default for MultiPartTable { + fn default() -> Self { + Self::new() + } +} + +impl MultiPartTable { + pub fn new() -> Self { + Self { rows: vec![] } + } + + /// Add a header row. + pub fn header_ref(mut self, name: &impl ToString) -> Self { + self.rows.push(Row::Header(name.to_string())); + self + } + + /// Add a name/value row. + pub fn nv_ref(mut self, name: &impl ToString, value: &impl ToString) -> Self { + self.rows.push(Row::NameValue(( + name.to_string(), + value.to_string().replace_md_chars(), + ))); + self + } + + /// Add a name/value row. + pub fn nv(mut self, name: &impl ToString, value: impl ToString) -> Self { + self.rows.push(Row::NameValue(( + name.to_string(), + value.to_string().replace_md_chars(), + ))); + self + } + + /// Add a name/value row without processing whitespace or markdown charaters. + pub fn nv_raw(mut self, name: &impl ToString, value: impl ToString) -> Self { + self.rows + .push(Row::NameValue((name.to_string(), value.to_string()))); + self + } + + /// Add a name/value row with unordered list. + pub fn nv_ul_ref(mut self, name: &impl ToString, value: Vec<&impl ToString>) -> Self { + value.iter().enumerate().for_each(|(i, v)| { + if i == 0 { + self.rows.push(Row::NameValue(( + name.to_string(), + format!("* {}", v.to_string().replace_md_chars()), + ))) + } else { + self.rows.push(Row::NameValue(( + String::default(), + format!("* {}", v.to_string().replace_md_chars()), + ))) + } + }); + self + } + + /// Add a name/value row with unordered list. + pub fn nv_ul(mut self, name: &impl ToString, value: Vec) -> Self { + value.iter().enumerate().for_each(|(i, v)| { + if i == 0 { + self.rows.push(Row::NameValue(( + name.to_string(), + format!("* {}", v.to_string().replace_md_chars()), + ))) + } else { + self.rows.push(Row::NameValue(( + String::default(), + format!("* {}", v.to_string().replace_md_chars()), + ))) + } + }); + self + } + + /// Add a name/value row. + pub fn and_nv_ref(mut self, name: &impl ToString, value: &Option) -> Self { + self.rows.push(Row::NameValue(( + name.to_string(), + value + .as_deref() + .unwrap_or_default() + .to_string() + .replace_md_chars(), + ))); + self + } + + /// Add a name/value row. + pub fn and_nv_ref_maybe(self, name: &impl ToString, value: &Option) -> Self { + if let Some(value) = value { + self.nv_ref(name, value) + } else { + self + } + } + + /// Add a name/value row with unordered list. + pub fn and_nv_ul_ref(self, name: &impl ToString, value: Option>) -> Self { + if let Some(value) = value { + self.nv_ul_ref(name, value) + } else { + self + } + } + + /// Add a name/value row with unordered list. + pub fn and_nv_ul(self, name: &impl ToString, value: Option>) -> Self { + if let Some(value) = value { + self.nv_ul(name, value) + } else { + self + } + } + + /// A summary row is a special type of name/value row that has an unordered (bulleted) list + /// that is output in a tree structure (max 3 levels). + pub fn summary(mut self, header_text: MdHeaderText) -> Self { + self.rows.push(Row::NameValue(( + "Summary".to_string(), + header_text.to_string().replace_md_chars().to_string(), + ))); + // note that termimad has limits on list depth, so we can't go too crazy. + // however, this seems perfectly reasonable for must RDAP use cases. + for level1 in header_text.children { + self.rows.push(Row::NameValue(( + "".to_string(), + format!("* {}", level1.to_string().replace_md_chars()), + ))); + for level2 in level1.children { + self.rows.push(Row::NameValue(( + "".to_string(), + format!(" * {}", level2.to_string().replace_md_chars()), + ))); + } + } + self + } + + /// Adds a multivalue row. + pub fn multi(mut self, values: Vec) -> Self { + self.rows.push(Row::MultiValue( + values.iter().map(|s| s.replace_md_chars()).collect(), + )); + self + } + + /// Adds a multivalue row. + pub fn multi_ref(mut self, values: &[&str]) -> Self { + self.rows.push(Row::MultiValue( + values.iter().map(|s| s.replace_md_chars()).collect(), + )); + self + } + + /// Adds a multivalue row without processing whitespace or markdown characters. + pub fn multi_raw(mut self, values: Vec) -> Self { + self.rows.push(Row::MultiValue( + values.iter().map(|s| s.to_owned()).collect(), + )); + self + } + + /// Adds a multivalue row without processing whitespace or markdown characters. + pub fn multi_raw_ref(mut self, values: &[&str]) -> Self { + self.rows.push(Row::MultiValue( + values.iter().map(|s| s.to_string()).collect(), + )); + self + } + + pub fn to_md_table(&self, options: &MdOptions) -> String { + let mut md = String::new(); + + let col_type_width = max( + self.rows + .iter() + .map(|row| match row { + Row::Header(header) => header.len(), + Row::NameValue((name, _value)) => name.len(), + Row::MultiValue(_) => 1, + }) + .max() + .unwrap_or(1), + 1, + ); + + self.rows + .iter() + .scan(true, |state, x| { + let new_state = match x { + Row::Header(name) => { + md.push_str(&format!( + "|:-:|\n|{}|\n", + name.to_center_bold(col_type_width, options) + )); + true + } + Row::NameValue((name, value)) => { + if *state { + md.push_str("|-:|:-|\n"); + }; + md.push_str(&format!( + "|{}|{}|\n", + name.to_right(col_type_width, options), + value + )); + false + } + Row::MultiValue(values) => { + // column formatting + md.push('|'); + for _col in values { + md.push_str(":--:|"); + } + md.push('\n'); + + // the actual data + md.push('|'); + for col in values { + md.push_str(&format!("{col}|")); + } + md.push('\n'); + true + } + }; + *state = new_state; + Some(new_state) + }) + .last(); + + md.push_str("|\n\n"); + md + } +} + +impl ToMd for MultiPartTable { + fn to_md(&self, params: super::MdParams) -> String { + self.to_md_table(params.options) + } +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use icann_rdap_common::{httpdata::HttpData, prelude::ToResponse, response::Rfc9083Error}; + + use crate::{ + md::ToMd, + rdap::rr::{RequestData, SourceType}, + }; + + use super::MultiPartTable; + + #[test] + fn GIVEN_header_WHEN_to_md_THEN_header_format_and_header() { + // GIVEN + let table = MultiPartTable::new().header_ref(&"foo"); + + // WHEN + let req_data = RequestData { + req_number: 0, + source_host: "", + source_type: SourceType::UncategorizedRegistry, + }; + let rdap_response = Rfc9083Error::builder() + .error_code(500) + .build() + .to_response(); + let actual = table.to_md(crate::md::MdParams { + heading_level: 0, + root: &rdap_response, + http_data: &HttpData::example().build(), + parent_type: std::any::TypeId::of::(), + check_types: &[], + options: &crate::md::MdOptions::plain_text(), + req_data: &req_data, + }); + + assert_eq!(actual, "|:-:|\n|__foo__|\n|\n\n") + } + + #[test] + fn GIVEN_header_and_data_ref_WHEN_to_md_THEN_header_format_and_header() { + // GIVEN + let table = MultiPartTable::new() + .header_ref(&"foo") + .nv_ref(&"bizz", &"buzz"); + + // WHEN + let req_data = RequestData { + req_number: 0, + source_host: "", + source_type: SourceType::UncategorizedRegistry, + }; + let rdap_response = Rfc9083Error::builder() + .error_code(500) + .build() + .to_response(); + let actual = table.to_md(crate::md::MdParams { + heading_level: 0, + root: &rdap_response, + http_data: &HttpData::example().build(), + parent_type: std::any::TypeId::of::(), + check_types: &[], + options: &crate::md::MdOptions::plain_text(), + req_data: &req_data, + }); + + assert_eq!(actual, "|:-:|\n|__foo__|\n|-:|:-|\n|bizz|buzz|\n|\n\n") + } + + #[test] + fn GIVEN_header_and_2_data_ref_WHEN_to_md_THEN_header_format_and_header() { + // GIVEN + let table = MultiPartTable::new() + .header_ref(&"foo") + .nv_ref(&"bizz", &"buzz") + .nv_ref(&"bar", &"baz"); + + // WHEN + let req_data = RequestData { + req_number: 0, + source_host: "", + source_type: SourceType::UncategorizedRegistry, + }; + let rdap_response = Rfc9083Error::builder() + .error_code(500) + .build() + .to_response(); + let actual = table.to_md(crate::md::MdParams { + heading_level: 0, + root: &rdap_response, + http_data: &HttpData::example().build(), + parent_type: std::any::TypeId::of::(), + check_types: &[], + options: &crate::md::MdOptions::plain_text(), + req_data: &req_data, + }); + + assert_eq!( + actual, + "|:-:|\n|__foo__|\n|-:|:-|\n|bizz|buzz|\n| bar|baz|\n|\n\n" + ) + } + + #[test] + fn GIVEN_header_and_data_WHEN_to_md_THEN_header_format_and_header() { + // GIVEN + let table = MultiPartTable::new() + .header_ref(&"foo") + .nv(&"bizz", "buzz".to_string()); + + // WHEN + let req_data = RequestData { + req_number: 0, + source_host: "", + source_type: SourceType::UncategorizedRegistry, + }; + let rdap_response = Rfc9083Error::builder() + .error_code(500) + .build() + .to_response(); + let actual = table.to_md(crate::md::MdParams { + heading_level: 0, + root: &rdap_response, + http_data: &HttpData::example().build(), + parent_type: std::any::TypeId::of::(), + check_types: &[], + options: &crate::md::MdOptions::plain_text(), + req_data: &req_data, + }); + + assert_eq!(actual, "|:-:|\n|__foo__|\n|-:|:-|\n|bizz|buzz|\n|\n\n") + } + + #[test] + fn GIVEN_header_and_2_data_WHEN_to_md_THEN_header_format_and_header() { + // GIVEN + let table = MultiPartTable::new() + .header_ref(&"foo") + .nv(&"bizz", "buzz") + .nv(&"bar", "baz"); + + // WHEN + let req_data = RequestData { + req_number: 0, + source_host: "", + source_type: SourceType::UncategorizedRegistry, + }; + let rdap_response = Rfc9083Error::builder() + .error_code(500) + .build() + .to_response(); + let actual = table.to_md(crate::md::MdParams { + heading_level: 0, + root: &rdap_response, + http_data: &HttpData::example().build(), + parent_type: std::any::TypeId::of::(), + check_types: &[], + options: &crate::md::MdOptions::plain_text(), + req_data: &req_data, + }); + + assert_eq!( + actual, + "|:-:|\n|__foo__|\n|-:|:-|\n|bizz|buzz|\n| bar|baz|\n|\n\n" + ) + } + + #[test] + fn GIVEN_header_and_2_data_ref_twice_WHEN_to_md_THEN_header_format_and_header() { + // GIVEN + let table = MultiPartTable::new() + .header_ref(&"foo") + .nv_ref(&"bizz", &"buzz") + .nv_ref(&"bar", &"baz") + .header_ref(&"foo") + .nv_ref(&"bizz", &"buzz") + .nv_ref(&"bar", &"baz"); + + // WHEN + let req_data = RequestData { + req_number: 0, + source_host: "", + source_type: SourceType::UncategorizedRegistry, + }; + let rdap_response = Rfc9083Error::builder() + .error_code(500) + .build() + .to_response(); + let actual = table.to_md(crate::md::MdParams { + heading_level: 0, + root: &rdap_response, + http_data: &HttpData::example().build(), + parent_type: std::any::TypeId::of::(), + check_types: &[], + options: &crate::md::MdOptions::plain_text(), + req_data: &req_data, + }); + + assert_eq!( + actual, + "|:-:|\n|__foo__|\n|-:|:-|\n|bizz|buzz|\n| bar|baz|\n|:-:|\n|__foo__|\n|-:|:-|\n|bizz|buzz|\n| bar|baz|\n|\n\n" + ) + } +} diff --git a/icann-rdap-client/src/md/types.rs b/icann-rdap-client/src/md/types.rs new file mode 100644 index 0000000..0e50a71 --- /dev/null +++ b/icann-rdap-client/src/md/types.rs @@ -0,0 +1,468 @@ +use { + icann_rdap_common::prelude::ObjectCommon, + std::{any::TypeId, sync::LazyLock}, +}; + +use { + icann_rdap_common::{ + check::StringCheck, + httpdata::HttpData, + response::{ + Common, Event, Link, Links, NoticeOrRemark, Notices, PublicId, RdapConformance, Remarks, + }, + }, + reqwest::header::{ + ACCESS_CONTROL_ALLOW_ORIGIN, CACHE_CONTROL, CONTENT_LENGTH, EXPIRES, HOST, + STRICT_TRANSPORT_SECURITY, + }, + strum::EnumMessage, +}; + +use icann_rdap_common::check::{ + CheckClass, CheckItem, CheckParams, Checks, GetChecks, CHECK_CLASS_LEN, +}; + +use super::{ + checks_ul, + string::{StringListUtil, StringUtil}, + table::{MultiPartTable, ToMpTable}, + FromMd, MdParams, ToMd, HR, +}; + +impl ToMd for RdapConformance { + fn to_md(&self, params: MdParams) -> String { + let mut md = String::new(); + md.push_str( + &format!( + "{} Conformance Claims", + params.req_data.source_host.to_title_case() + ) + .to_header(5, params.options), + ); + self.iter().for_each(|s| { + md.push_str(&format!( + "* {}\n", + s.0.replace('_', " ") + .to_cap_acronyms() + .to_words_title_case() + )) + }); + self.get_checks(CheckParams::from_md_no_parent(params)) + .items + .iter() + .filter(|item| params.check_types.contains(&item.check_class)) + .for_each(|item| { + md.push_str(&format!( + "* {}: {}\n", + item.check_class.to_string().to_em(params.options), + item.check + .get_message() + .expect("Check has no message. Coding error.") + )) + }); + md.push('\n'); + md + } +} + +impl ToMd for Links { + fn to_md(&self, mdparams: MdParams) -> String { + let mut md = String::new(); + self.iter() + .for_each(|link| md.push_str(&link.to_md(mdparams))); + md + } +} + +impl ToMd for Link { + fn to_md(&self, params: MdParams) -> String { + let mut md = String::new(); + if let Some(title) = &self.title { + md.push_str(&format!("* {}:\n", title.replace_md_chars())); + } else { + md.push_str("* Link:\n") + }; + if let Some(href) = &self.href { + md.push_str(&format!( + "* {}\n", + href.to_owned().to_inline(params.options) + )); + }; + if let Some(rel) = &self.rel { + md.push_str(&format!("* Relation: {}\n", rel.replace_md_chars())); + }; + if let Some(media_type) = &self.media_type { + md.push_str(&format!("* Type: {}\n", media_type.replace_md_chars())); + }; + if let Some(media) = &self.media { + md.push_str(&format!("* Media: {}\n", media.replace_md_chars())); + }; + if let Some(value) = &self.value { + md.push_str(&format!("* Value: {}\n", value.replace_md_chars())); + }; + if let Some(hreflang) = &self.hreflang { + match hreflang { + icann_rdap_common::response::HrefLang::Lang(lang) => { + md.push_str(&format!("* Language: {}\n", lang.replace_md_chars())); + } + icann_rdap_common::response::HrefLang::Langs(langs) => { + md.push_str(&format!( + "* Languages: {}", + langs.join(", ").replace_md_chars() + )); + } + } + }; + let checks = self.get_checks(CheckParams::from_md(params, TypeId::of::())); + md.push_str(&checks_ul(&checks, params)); + md.push('\n'); + md + } +} + +impl ToMd for Notices { + fn to_md(&self, params: MdParams) -> String { + let mut md = String::new(); + self.iter() + .for_each(|notice| md.push_str(¬ice.0.to_md(params))); + md + } +} + +impl ToMd for Remarks { + fn to_md(&self, params: MdParams) -> String { + let mut md = String::new(); + self.iter() + .for_each(|remark| md.push_str(&remark.0.to_md(params))); + md + } +} + +impl ToMd for Option { + fn to_md(&self, params: MdParams) -> String { + if let Some(remarks) = &self { + remarks.to_md(params) + } else { + String::new() + } + } +} + +impl ToMd for NoticeOrRemark { + fn to_md(&self, params: MdParams) -> String { + let mut md = String::new(); + if let Some(title) = &self.title { + md.push_str(&format!("{}\n", title.to_bold(params.options))); + }; + if let Some(nr_type) = &self.nr_type { + md.push_str(&format!("Type: {}\n", nr_type.to_words_title_case())); + }; + if let Some(description) = &self.description { + description.vec().iter().for_each(|s| { + if !s.is_whitespace_or_empty() { + md.push_str(&format!("> {}\n\n", s.trim().replace_md_chars())) + } + }); + } + self.get_checks(CheckParams::from_md(params, TypeId::of::())) + .items + .iter() + .filter(|item| params.check_types.contains(&item.check_class)) + .for_each(|item| { + md.push_str(&format!( + "* {}: {}\n", + &item.check_class.to_string().to_em(params.options), + item.check + .get_message() + .expect("Check has no message. Coding error.") + )) + }); + if let Some(links) = &self.links { + links + .iter() + .for_each(|link| md.push_str(&link.to_md(params))); + } + md.push('\n'); + md + } +} + +impl ToMd for Common { + fn to_md(&self, params: MdParams) -> String { + let mut md = String::new(); + let not_empty = self.rdap_conformance.is_some() || self.notices.is_some(); + if not_empty { + md.push('\n'); + md.push_str(HR); + let header_text = format!( + "Response from {} at {}", + params.req_data.source_type, + params.req_data.source_host.to_title_case() + ); + md.push_str(&header_text.to_header(params.heading_level, params.options)); + }; + if let Some(rdap_conformance) = &self.rdap_conformance { + md.push_str(&rdap_conformance.to_md(params)); + }; + if let Some(notices) = &self.notices { + md.push_str(&"Server Notices".to_header(5, params.options)); + md.push_str(¬ices.to_md(params)); + } + if not_empty { + md.push_str(HR); + }; + md + } +} + +const RECEIVED: &str = "Received"; +const REQUEST_URI: &str = "Request URI"; + +pub static NAMES: LazyLock<[String; 7]> = LazyLock::new(|| { + [ + HOST.to_string(), + reqwest::header::EXPIRES.to_string(), + reqwest::header::CACHE_CONTROL.to_string(), + reqwest::header::STRICT_TRANSPORT_SECURITY.to_string(), + reqwest::header::ACCESS_CONTROL_ALLOW_ORIGIN.to_string(), + RECEIVED.to_string(), + REQUEST_URI.to_string(), + ] +}); +pub static NAME_LEN: LazyLock = LazyLock::new(|| { + NAMES + .iter() + .max_by_key(|x| x.to_string().len()) + .map_or(8, |x| x.to_string().len()) +}); + +impl ToMd for HttpData { + fn to_md(&self, params: MdParams) -> String { + let mut md = HR.to_string(); + md.push_str(&format!(" * {:())) + .items + .iter() + .filter(|item| params.check_types.contains(&item.check_class)) + .for_each(|item| { + md.push_str(&format!( + "* {}: {}\n", + &item.check_class.to_string().to_em(params.options), + item.check + .get_message() + .expect("Check has no message. Coding error.") + )) + }); + md + } +} + +impl ToMpTable for ObjectCommon { + fn add_to_mptable(&self, mut table: MultiPartTable, params: MdParams) -> MultiPartTable { + if self.status.is_some() || self.port_43.is_some() { + table = table.header_ref(&"Information"); + + // Status + if let Some(status) = &self.status { + let values = status.vec(); + table = table.nv_ul(&"Status", values.make_list_all_title_case()); + } + + // Port 43 + table = table.and_nv_ref(&"Whois", &self.port_43); + } + + // Events + if let Some(events) = &self.events { + table = events_to_table(events, table, "Events", params); + } + + // Links + if let Some(links) = &self.links { + table = links_to_table(links, table, "Links"); + } + + // TODO Checks + table + } +} + +pub(crate) fn public_ids_to_table( + publid_ids: &[PublicId], + mut table: MultiPartTable, +) -> MultiPartTable { + for pid in publid_ids { + table = table.nv_ref( + pid.id_type.as_ref().unwrap_or(&"(not given)".to_string()), + pid.identifier + .as_ref() + .unwrap_or(&"(not given)".to_string()), + ); + } + table +} + +pub(crate) fn events_to_table( + events: &[Event], + mut table: MultiPartTable, + header_name: &str, + params: MdParams, +) -> MultiPartTable { + table = table.header_ref(&header_name.replace_md_chars()); + for event in events { + let event_date = &event + .event_date + .to_owned() + .unwrap_or("????".to_string()) + .format_date_time(params) + .unwrap_or_default(); + let mut ul: Vec<&String> = vec![event_date]; + if let Some(event_actor) = &event.event_actor { + ul.push(event_actor); + } + table = table.nv_ul_ref( + &event + .event_action + .as_ref() + .unwrap_or(&"action not given".to_string()) + .to_owned() + .to_words_title_case(), + ul, + ); + } + table +} + +pub(crate) fn links_to_table( + links: &[Link], + mut table: MultiPartTable, + header_name: &str, +) -> MultiPartTable { + table = table.header_ref(&header_name.replace_md_chars()); + for link in links { + if let Some(title) = &link.title { + table = table.nv_ref(&"Title", &title.trim()); + }; + let rel = link + .rel + .as_ref() + .unwrap_or(&"Link".to_string()) + .to_title_case(); + let mut ul: Vec<&String> = vec![]; + if let Some(href) = &link.href { + ul.push(href) + } + if let Some(media_type) = &link.media_type { + ul.push(media_type) + }; + if let Some(media) = &link.media { + ul.push(media) + }; + if let Some(value) = &link.value { + ul.push(value) + }; + let hreflang_s; + if let Some(hreflang) = &link.hreflang { + hreflang_s = match hreflang { + icann_rdap_common::response::HrefLang::Lang(lang) => lang.to_owned(), + icann_rdap_common::response::HrefLang::Langs(langs) => langs.join(", "), + }; + ul.push(&hreflang_s) + }; + table = table.nv_ul_ref(&rel, ul); + } + table +} + +pub(crate) fn checks_to_table( + checks: Vec, + mut table: MultiPartTable, + params: MdParams, +) -> MultiPartTable { + let mut filtered_checks: Vec = checks + .into_iter() + .flat_map(|checks| checks.items) + .filter(|item| params.check_types.contains(&item.check_class)) + .collect(); + + if !filtered_checks.is_empty() { + filtered_checks.sort(); + filtered_checks.dedup(); + table = table.header_ref(&"Checks"); + + // Informational + let class = CheckClass::Informational; + let ul: Vec = filtered_checks + .iter() + .filter(|item| item.check_class == class) + .map(|item| item.check.get_message().unwrap_or_default().to_owned()) + .collect(); + table = table.nv_ul_ref( + &&class + .to_string() + .to_right_em(*CHECK_CLASS_LEN, params.options), + ul.iter().collect(), + ); + + // Specification Warning + let class = CheckClass::StdWarning; + let ul: Vec = filtered_checks + .iter() + .filter(|item| item.check_class == class) + .map(|item| item.check.get_message().unwrap_or_default().to_owned()) + .collect(); + table = table.nv_ul_ref( + &class + .to_string() + .to_right_em(*CHECK_CLASS_LEN, params.options), + ul.iter().collect(), + ); + + // Specification Error + let class = CheckClass::StdError; + let ul: Vec = filtered_checks + .iter() + .filter(|item| item.check_class == class) + .map(|item| item.check.get_message().unwrap_or_default().to_owned()) + .collect(); + table = table.nv_ul_ref( + &&class + .to_string() + .to_right_em(*CHECK_CLASS_LEN, params.options), + ul.iter().collect(), + ); + } + + table +} diff --git a/icann-rdap-client/src/rdap/mod.rs b/icann-rdap-client/src/rdap/mod.rs new file mode 100644 index 0000000..1b393ac --- /dev/null +++ b/icann-rdap-client/src/rdap/mod.rs @@ -0,0 +1,15 @@ +//! Code for managing RDAP queries. + +#[doc(inline)] +pub use qtype::*; +#[doc(inline)] +pub use registered_redactions::*; +#[doc(inline)] +pub use request::*; +#[doc(inline)] +pub use rr::*; + +pub(crate) mod qtype; +pub(crate) mod registered_redactions; +pub(crate) mod request; +pub(crate) mod rr; diff --git a/icann-rdap-client/src/rdap/qtype.rs b/icann-rdap-client/src/rdap/qtype.rs new file mode 100644 index 0000000..c571282 --- /dev/null +++ b/icann-rdap-client/src/rdap/qtype.rs @@ -0,0 +1,645 @@ +//! Defines the various types of RDAP queries. +use std::{ + net::{IpAddr, Ipv4Addr, Ipv6Addr}, + str::FromStr, + sync::LazyLock, +}; + +use { + cidr::{IpCidr, Ipv4Cidr, Ipv6Cidr}, + icann_rdap_common::{check::StringCheck, dns_types::DomainName}, + pct_str::{PctString, URIReserved}, + regex::Regex, + strum_macros::Display, +}; + +use crate::RdapClientError; + +/// Defines the various types of RDAP lookups and searches. +#[derive(Display, Debug)] +pub enum QueryType { + #[strum(serialize = "IpV4 Address Lookup")] + IpV4Addr(Ipv4Addr), + + #[strum(serialize = "IpV6 Address Lookup")] + IpV6Addr(Ipv6Addr), + + #[strum(serialize = "IpV4 CIDR Lookup")] + IpV4Cidr(Ipv4Cidr), + + #[strum(serialize = "IpV6 CIDR Lookup")] + IpV6Cidr(Ipv6Cidr), + + #[strum(serialize = "Autonomous System Number Lookup")] + AsNumber(u32), + + #[strum(serialize = "Domain Lookup")] + Domain(DomainName), + + #[strum(serialize = "A-Label Domain Lookup")] + ALabel(DomainName), + + #[strum(serialize = "Entity Lookup")] + Entity(String), + + #[strum(serialize = "Nameserver Lookup")] + Nameserver(DomainName), + + #[strum(serialize = "Entity Name Search")] + EntityNameSearch(String), + + #[strum(serialize = "Entity Handle Search")] + EntityHandleSearch(String), + + #[strum(serialize = "Domain Name Search")] + DomainNameSearch(String), + + #[strum(serialize = "Domain Nameserver Name Search")] + DomainNsNameSearch(String), + + #[strum(serialize = "Domain Nameserver IP Address Search")] + DomainNsIpSearch(IpAddr), + + #[strum(serialize = "Nameserver Name Search")] + NameserverNameSearch(String), + + #[strum(serialize = "Nameserver IP Address Search")] + NameserverIpSearch(IpAddr), + + #[strum(serialize = "Server Help Lookup")] + Help, + + #[strum(serialize = "Explicit URL")] + Url(String), +} + +impl QueryType { + pub fn query_url(&self, base_url: &str) -> Result { + let base_url = base_url.trim_end_matches('/'); + match self { + Self::IpV4Addr(value) => Ok(format!( + "{base_url}/ip/{}", + PctString::encode(value.to_string().chars(), URIReserved) + )), + Self::IpV6Addr(value) => Ok(format!( + "{base_url}/ip/{}", + PctString::encode(value.to_string().chars(), URIReserved) + )), + Self::IpV4Cidr(value) => Ok(format!( + "{base_url}/ip/{}/{}", + PctString::encode(value.first_address().to_string().chars(), URIReserved), + PctString::encode(value.network_length().to_string().chars(), URIReserved) + )), + Self::IpV6Cidr(value) => Ok(format!( + "{base_url}/ip/{}/{}", + PctString::encode(value.first_address().to_string().chars(), URIReserved), + PctString::encode(value.network_length().to_string().chars(), URIReserved) + )), + Self::AsNumber(value) => Ok(format!( + "{base_url}/autnum/{}", + PctString::encode(value.to_string().chars(), URIReserved) + )), + Self::Domain(value) => Ok(format!( + "{base_url}/domain/{}", + PctString::encode(value.trim_leading_dot().chars(), URIReserved) + )), + Self::ALabel(value) => Ok(format!( + "{base_url}/domain/{}", + PctString::encode(value.to_ascii().chars(), URIReserved), + )), + Self::Entity(value) => Ok(format!( + "{base_url}/entity/{}", + PctString::encode(value.chars(), URIReserved) + )), + Self::Nameserver(value) => Ok(format!( + "{base_url}/nameserver/{}", + PctString::encode(value.to_ascii().chars(), URIReserved) + )), + Self::EntityNameSearch(value) => search_query(value, "entities?fn", base_url), + Self::EntityHandleSearch(value) => search_query(value, "entities?handle", base_url), + Self::DomainNameSearch(value) => search_query(value, "domains?name", base_url), + Self::DomainNsNameSearch(value) => search_query(value, "domains?nsLdhName", base_url), + Self::DomainNsIpSearch(value) => { + search_query(&value.to_string(), "domains?nsIp", base_url) + } + Self::NameserverNameSearch(value) => search_query(value, "nameservers?name", base_url), + Self::NameserverIpSearch(value) => { + search_query(&value.to_string(), "nameservers?ip", base_url) + } + Self::Help => Ok(format!("{base_url}/help")), + Self::Url(url) => Ok(url.to_owned()), + } + } + + pub fn domain(domain_name: &str) -> Result { + Ok(Self::Domain(DomainName::from_str(domain_name)?)) + } + + pub fn alabel(alabel: &str) -> Result { + Ok(Self::ALabel(DomainName::from_str(alabel)?)) + } + + pub fn ns(nameserver: &str) -> Result { + Ok(Self::Nameserver(DomainName::from_str(nameserver)?)) + } + + pub fn autnum(autnum: &str) -> Result { + let value = autnum + .trim_start_matches(|c| -> bool { matches!(c, 'a' | 'A' | 's' | 'S') }) + .parse::() + .map_err(|_e| RdapClientError::InvalidQueryValue)?; + Ok(Self::AsNumber(value)) + } + + pub fn ipv4(ip: &str) -> Result { + let value = Ipv4Addr::from_str(ip).map_err(|_e| RdapClientError::InvalidQueryValue)?; + Ok(Self::IpV4Addr(value)) + } + + pub fn ipv6(ip: &str) -> Result { + let value = Ipv6Addr::from_str(ip).map_err(|_e| RdapClientError::InvalidQueryValue)?; + Ok(Self::IpV6Addr(value)) + } + + pub fn ipv4cidr(cidr: &str) -> Result { + let value = cidr::parsers::parse_cidr_ignore_hostbits::( + cidr, + cidr::parsers::parse_loose_ip, + ) + .map_err(|_e| RdapClientError::InvalidQueryValue)?; + if let IpCidr::V4(v4) = value { + Ok(Self::IpV4Cidr(v4)) + } else { + Err(RdapClientError::AmbiquousQueryType) + } + } + + pub fn ipv6cidr(cidr: &str) -> Result { + let value = cidr::parsers::parse_cidr_ignore_hostbits::( + cidr, + cidr::parsers::parse_loose_ip, + ) + .map_err(|_e| RdapClientError::InvalidQueryValue)?; + if let IpCidr::V6(v6) = value { + Ok(Self::IpV6Cidr(v6)) + } else { + Err(RdapClientError::AmbiquousQueryType) + } + } + + pub fn domain_ns_ip_search(ip: &str) -> Result { + let value = IpAddr::from_str(ip).map_err(|_e| RdapClientError::InvalidQueryValue)?; + Ok(Self::DomainNsIpSearch(value)) + } + + pub fn ns_ip_search(ip: &str) -> Result { + let value = IpAddr::from_str(ip).map_err(|_e| RdapClientError::InvalidQueryValue)?; + Ok(Self::NameserverIpSearch(value)) + } +} + +fn search_query(value: &str, path_query: &str, base_url: &str) -> Result { + Ok(format!( + "{base_url}/{path_query}={}", + PctString::encode(value.chars(), URIReserved) + )) +} + +impl FromStr for QueryType { + type Err = RdapClientError; + + fn from_str(s: &str) -> Result { + // if it looks like a HTTP(S) url + if s.starts_with("http://") || s.starts_with("https://") { + return Ok(Self::Url(s.to_owned())); + } + + // if looks like an autnum + let autnum = s.trim_start_matches(|c| -> bool { matches!(c, 'a' | 'A' | 's' | 'S') }); + if u32::from_str(autnum).is_ok() { + return Self::autnum(s); + } + + // If it's an IP address + if let Ok(ip_addr) = IpAddr::from_str(s) { + if ip_addr.is_ipv4() { + return Self::ipv4(s); + } else { + return Self::ipv6(s); + } + } + + // if it is a cidr + if let Ok(ip_cidr) = parse_cidr(s) { + return Ok(match ip_cidr { + IpCidr::V4(cidr) => Self::IpV4Cidr(cidr), + IpCidr::V6(cidr) => Self::IpV6Cidr(cidr), + }); + } + + // if it looks like a domain name + if is_domain_name(s) { + return if is_nameserver(s) { + Self::ns(s) + } else { + Self::domain(s) + }; + } + + // if it is just one word + if !s.contains(|c: char| c.is_whitespace() || matches!(c, '.' | ',' | '"')) { + return Ok(Self::Entity(s.to_owned())); + } + + // The query type cannot be deteremined. + Err(RdapClientError::AmbiquousQueryType) + } +} + +fn parse_cidr(s: &str) -> Result { + let Some((prefix, suffix)) = s.split_once('/') else { + return Err(RdapClientError::InvalidQueryValue); + }; + if prefix.chars().all(|c: char| c.is_ascii_alphanumeric()) { + let cidr = cidr::parsers::parse_short_ip_address_as_cidr(prefix) + .map_err(|_e| RdapClientError::InvalidQueryValue)?; + IpCidr::new( + cidr.first_address(), + suffix + .parse::() + .map_err(|_e| RdapClientError::InvalidQueryValue)?, + ) + .map_err(|_e| RdapClientError::InvalidQueryValue) + } else { + cidr::parsers::parse_cidr_ignore_hostbits::(s, cidr::parsers::parse_loose_ip) + .map_err(|_e| RdapClientError::InvalidQueryValue) + } +} + +fn is_ldh_domain(text: &str) -> bool { + static LDH_DOMAIN_RE: LazyLock = + LazyLock::new(|| Regex::new(r"^(?i)(\.?[a-zA-Z0-9-]+)*\.[a-zA-Z0-9-]+\.?$").unwrap()); + LDH_DOMAIN_RE.is_match(text) +} + +fn is_domain_name(text: &str) -> bool { + text.contains('.') && text.is_unicode_domain_name() +} + +fn is_nameserver(text: &str) -> bool { + static NS_RE: LazyLock = LazyLock::new(|| { + Regex::new(r"^(?i)(ns)[a-zA-Z0-9-]*\.[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+\.?$").unwrap() + }); + NS_RE.is_match(text) +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use rstest::rstest; + + use super::*; + + #[test] + fn test_ipv4_query_type_from_str() { + // GIVEN + let s = "129.129.1.1"; + + // WHEN + let q = QueryType::from_str(s); + + // THEN + assert!(matches!(q.unwrap(), QueryType::IpV4Addr(_))) + } + + #[test] + fn test_ipv6_query_type_from_str() { + // GIVEN + let s = "2001::1"; + + // WHEN + let q = QueryType::from_str(s); + + // THEN + assert!(matches!(q.unwrap(), QueryType::IpV6Addr(_))) + } + + #[test] + fn test_ipv4_cidr_query_type_from_str() { + // GIVEN + let s = "129.129.1.1/8"; + + // WHEN + let q = QueryType::from_str(s); + + // THEN + assert!(matches!(q.unwrap(), QueryType::IpV4Cidr(_))) + } + + #[test] + fn test_ipv6_cidr_query_type_from_str() { + // GIVEN + let s = "2001::1/20"; + + // WHEN + let q = QueryType::from_str(s); + + // THEN + assert!(matches!(q.unwrap(), QueryType::IpV6Cidr(_))) + } + + #[test] + fn test_number_query_type_from_str() { + // GIVEN + let s = "16509"; + + // WHEN + let q = QueryType::from_str(s); + + // THEN + assert!(matches!(q.unwrap(), QueryType::AsNumber(_))) + } + + #[test] + fn test_as_followed_by_number_query_type_from_str() { + // GIVEN + let s = "as16509"; + + // WHEN + let q = QueryType::from_str(s); + + // THEN + assert!(matches!(q.unwrap(), QueryType::AsNumber(_))) + } + + #[rstest] + #[case("example.com")] + #[case("foo.example.com")] + #[case("snark.fail")] + #[case("ns.fail")] + #[case(".com")] + fn test_domain_name_query_type_from_str(#[case] input: &str) { + // GIVEN case input + + // WHEN + let q = QueryType::from_str(input); + + // THEN + assert!(matches!(q.unwrap(), QueryType::Domain(_))) + } + + #[rstest] + #[case("ns.example.com")] + #[case("ns1.example.com")] + #[case("NS1.example.com")] + fn test_name_server_query_type_from_str(#[case] input: &str) { + // GIVEN case input + + // WHEN + let q = QueryType::from_str(input); + + // THEN + assert!(matches!(q.unwrap(), QueryType::Nameserver(_))) + } + + #[test] + fn test_single_word_query_type_from_str() { + // GIVEN + let s = "foo"; + + // WHEN + let q = QueryType::from_str(s); + + // THEN + let q = q.unwrap(); + assert!(matches!(q, QueryType::Entity(_))) + } + + #[rstest] + #[case("https://example.com")] + #[case("http://foo.example.com")] + fn test_url_query_type_from_str(#[case] input: &str) { + // GIVEN case input + + // WHEN + let q = QueryType::from_str(input); + + // THEN + assert!(matches!(q.unwrap(), QueryType::Url(_))) + } + + #[rstest] + #[case("ns.foo_bar.com")] + #[case("ns.foo bar.com")] + fn test_bad_input_query_type_from_str(#[case] input: &str) { + // GIVEN case input + + // WHEN + let q = QueryType::from_str(input); + + // THEN + assert!(q.is_err()); + } + + #[rstest] + #[case("10.0.0.0/8", "10.0.0.0/8")] + #[case("10.0.0/8", "10.0.0.0/8")] + #[case("10.0/8", "10.0.0.0/8")] + #[case("10/8", "10.0.0.0/8")] + #[case("10.0.0.0/24", "10.0.0.0/24")] + #[case("10.0.0/24", "10.0.0.0/24")] + #[case("10.0/24", "10.0.0.0/24")] + #[case("10/24", "10.0.0.0/24")] + #[case("129.129.1.1/8", "129.0.0.0/8")] + #[case("2001::1/32", "2001::/32")] + fn test_cidr_parse_cidr(#[case] actual: &str, #[case] expected: &str) { + // GIVEN case input + + // WHEN + let q = parse_cidr(actual); + + // THEN + assert_eq!(q.unwrap().to_string(), expected) + } + + #[test] + fn test_ipv4addr_query_url() { + // GIVEN ipv4 addr query + let q = QueryType::from_str("199.1.1.1").expect("query type"); + + // WHEN + let actual = q.query_url("https://example.com").expect("query url"); + + // THEN + assert_eq!(actual, "https://example.com/ip/199.1.1.1") + } + + #[test] + fn test_ipv6addr_query_url() { + // GIVEN + let q = QueryType::from_str("2000::1").expect("query type"); + + // WHEN + let actual = q.query_url("https://example.com").expect("query url"); + + // THEN + assert_eq!(actual, "https://example.com/ip/2000%3A%3A1") + } + + #[test] + fn test_ipv4cidr_query_url() { + // GIVEN + let q = QueryType::from_str("199.1.1.1/16").expect("query type"); + + // WHEN + let actual = q.query_url("https://example.com").expect("query url"); + + // THEN + assert_eq!(actual, "https://example.com/ip/199.1.0.0/16") + } + + #[test] + fn test_ipv6cidr_query_url() { + // GIVEN + let q = QueryType::from_str("2000::1/16").expect("query type"); + + // WHEN + let actual = q.query_url("https://example.com").expect("query url"); + + // THEN + assert_eq!(actual, "https://example.com/ip/2000%3A%3A/16") + } + + #[test] + fn test_autnum_query_url() { + // GIVEN + let q = QueryType::from_str("as16509").expect("query type"); + + // WHEN + let actual = q.query_url("https://example.com").expect("query url"); + + // THEN + assert_eq!(actual, "https://example.com/autnum/16509") + } + + #[test] + fn test_domain_query_url() { + // GIVEN + let q = QueryType::from_str("example.com").expect("query type"); + + // WHEN + let actual = q.query_url("https://example.com").expect("query url"); + + // THEN + assert_eq!(actual, "https://example.com/domain/example.com") + } + + #[test] + fn test_ns_query_url() { + // GIVEN + let q = QueryType::from_str("ns.example.com").expect("query type"); + + // WHEN + let actual = q.query_url("https://example.com").expect("query url"); + + // THEN + assert_eq!(actual, "https://example.com/nameserver/ns.example.com") + } + + #[test] + fn test_entity_query_url() { + // GIVEN + let q = QueryType::from_str("foo").expect("query type"); + + // WHEN + let actual = q.query_url("https://example.com").expect("query url"); + + // THEN + assert_eq!(actual, "https://example.com/entity/foo") + } + + #[test] + fn test_entity_name_search_query_url() { + // GIVEN + let q = QueryType::EntityNameSearch("foo".to_string()); + + // WHEN + let actual = q.query_url("https://example.com").expect("query url"); + + // THEN + assert_eq!(actual, "https://example.com/entities?fn=foo") + } + + #[test] + fn test_entity_handle_search_query_url() { + // GIVEN + let q = QueryType::EntityHandleSearch("foo".to_string()); + + // WHEN + let actual = q.query_url("https://example.com").expect("query url"); + + // THEN + assert_eq!(actual, "https://example.com/entities?handle=foo") + } + + #[test] + fn test_domain_name_search_query_url() { + // GIVEN + let q = QueryType::DomainNameSearch("foo".to_string()); + + // WHEN + let actual = q.query_url("https://example.com").expect("query url"); + + // THEN + assert_eq!(actual, "https://example.com/domains?name=foo") + } + + #[test] + fn test_domain_ns_name_search_query_url() { + // GIVEN + let q = QueryType::DomainNsNameSearch("foo".to_string()); + + // WHEN + let actual = q.query_url("https://example.com").expect("query url"); + + // THEN + assert_eq!(actual, "https://example.com/domains?nsLdhName=foo") + } + + #[test] + fn test_domain_ns_ip_search_query_url() { + // GIVEN + let q = QueryType::DomainNsIpSearch(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1))); + + // WHEN + let actual = q.query_url("https://example.com").expect("query url"); + + // THEN + assert_eq!(actual, "https://example.com/domains?nsIp=1.1.1.1") + } + + #[test] + fn test_ns_name_search_query_url() { + // GIVEN + let q = QueryType::NameserverNameSearch("foo".to_string()); + + // WHEN + let actual = q.query_url("https://example.com").expect("query url"); + + // THEN + assert_eq!(actual, "https://example.com/nameservers?name=foo") + } + + #[test] + fn test_ns_ip_search_query_url() { + // GIVEN + let q = QueryType::NameserverIpSearch(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1))); + + // WHEN + let actual = q.query_url("https://example.com").expect("query url"); + + // THEN + assert_eq!(actual, "https://example.com/nameservers?ip=1.1.1.1") + } +} diff --git a/icann-rdap-client/src/rdap/registered_redactions.rs b/icann-rdap-client/src/rdap/registered_redactions.rs new file mode 100644 index 0000000..8499ec6 --- /dev/null +++ b/icann-rdap-client/src/rdap/registered_redactions.rs @@ -0,0 +1,636 @@ +//! Determines of an RFC 9537 registered redaction is present. + +use { + icann_rdap_common::response::{Entity, EntityRole, RdapResponse}, + strum_macros::{Display, EnumString}, +}; + +/// Redacted types in the IANA registry +#[derive(Debug, PartialEq, Eq, EnumString, Display)] +pub enum RedactedName { + #[strum(serialize = "Registry Domain ID")] + RegistryDomainId, + #[strum(serialize = "Registry Registrant ID")] + RegistryRegistrantId, + #[strum(serialize = "Registrant Name")] + RegistrantName, + #[strum(serialize = "Registrant Organization")] + RegistrantOrganization, + #[strum(serialize = "Registrant Street")] + RegistrantStreet, + #[strum(serialize = "Registrant City")] + RegistrantCity, + #[strum(serialize = "Registrant Postal Code")] + RegistrantPostalCode, + #[strum(serialize = "Registrant Phone")] + RegistrantPhone, + #[strum(serialize = "Registrant Phone Ext")] + RegistrantPhoneExt, + #[strum(serialize = "Registrant Fax")] + RegistrantFax, + #[strum(serialize = "Registrant Fax Ext")] + RegistrantFaxExt, + #[strum(serialize = "Registrant Email")] + RegistrantEmail, + #[strum(serialize = "Registry Tech ID")] + RegistryTechId, + #[strum(serialize = "Tech Name")] + TechName, + #[strum(serialize = "Tech Phone")] + TechPhone, + #[strum(serialize = "Tech Phone Ext")] + TechPhoneExt, + #[strum(serialize = "Tech Email")] + TechEmail, +} + +/// This function looks at the RDAP response to see if a +/// redaction is present where the type of redaction is registered +/// with the IANA. +/// +/// * rdap_response - a reference to the RDAP response. +/// * redaction_type - a reference to the string registered in the IANA. +pub fn is_redaction_registered( + rdap_response: &RdapResponse, + redaction_type: &RedactedName, +) -> bool { + let object_common = match rdap_response { + RdapResponse::Entity(e) => Some(&e.object_common.redacted), + RdapResponse::Domain(d) => Some(&d.object_common.redacted), + RdapResponse::Nameserver(s) => Some(&s.object_common.redacted), + RdapResponse::Autnum(a) => Some(&a.object_common.redacted), + RdapResponse::Network(n) => Some(&n.object_common.redacted), + _ => None, + }; + if let Some(Some(redacted_vec)) = object_common { + redacted_vec.iter().any(|r| { + if let Some(r_type) = &r.name.type_field { + r_type.eq_ignore_ascii_case(&redaction_type.to_string()) + } else { + false + } + }) + } else { + false + } +} + +/// This function takes a set of [RedactedName]s instead of just one, +/// and runs them through [is_redaction_registered]. +pub fn are_redactions_registered( + rdap_response: &RdapResponse, + redaction_types: &[&RedactedName], +) -> bool { + redaction_types + .iter() + .any(|rn| is_redaction_registered(rdap_response, rn)) +} + +/// This function substitutes redaction_text if [is_redaction_registered] returns true. +pub fn text_or_registered_redaction( + rdap_response: &RdapResponse, + redaction_type: &RedactedName, + text: &Option, + redaction_text: &str, +) -> Option { + if is_redaction_registered(rdap_response, redaction_type) { + Some(redaction_text.to_string()) + } else { + text.clone() + } +} + +/// This function checks that an entity has a certain role, and if so then +/// checks of the redaction is registered for IANA. +/// +/// * rdap_response - a reference to the RDAP response. +/// * redaction_type - a reference to the string registered in the IANA. +/// * entity - a reference to the entity to check +/// * role - the role of the entity +pub fn is_redaction_registered_for_role( + rdap_response: &RdapResponse, + redaction_type: &RedactedName, + entity: &Entity, + entity_role: &EntityRole, +) -> bool { + let roles = entity.roles(); + if roles + .iter() + .any(|r| r.eq_ignore_ascii_case(&entity_role.to_string())) + { + return is_redaction_registered(rdap_response, redaction_type); + } + false +} + +/// Same as [is_redaction_registered_for_role] but takes an array of [EntityRole] references. +pub fn are_redactions_registered_for_roles( + rdap_response: &RdapResponse, + redaction_type: &[&RedactedName], + entity: &Entity, + entity_roles: &[&EntityRole], +) -> bool { + let roles = entity.roles(); + if roles.iter().any(|r| { + entity_roles + .iter() + .any(|er| r.eq_ignore_ascii_case(&er.to_string())) + }) { + return are_redactions_registered(rdap_response, redaction_type); + } + false +} + +/// This function substitutes redaction_text if [is_redaction_registered_for_role] return true. +pub fn text_or_registered_redaction_for_role( + rdap_response: &RdapResponse, + redaction_type: &RedactedName, + entity: &Entity, + entity_role: &EntityRole, + text: &Option, + redaction_text: &str, +) -> Option { + if is_redaction_registered_for_role(rdap_response, redaction_type, entity, entity_role) { + Some(redaction_text.to_string()) + } else { + text.clone() + } +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use icann_rdap_common::{ + prelude::ToResponse, + response::{ + redacted::{Name, Redacted}, + Domain, + }, + }; + + use super::*; + + #[test] + fn GIVEN_redaction_type_WHEN_search_for_type_THEN_true() { + // GIVEN + let domain = Domain::builder() + .ldh_name("example.com") + .redacted(vec![Redacted { + name: Name { + description: None, + type_field: Some(RedactedName::TechEmail.to_string()), + }, + reason: None, + pre_path: None, + post_path: None, + path_lang: None, + replacement_path: None, + method: None, + }]) + .build(); + let rdap = domain.to_response(); + + // WHEN + let actual = is_redaction_registered(&rdap, &RedactedName::TechEmail); + + // THEN + assert!(actual); + } + + #[test] + fn GIVEN_redaction_type_WHEN_get_text_for_type_THEN_redacted_text_returned() { + // GIVEN + let domain = Domain::builder() + .ldh_name("example.com") + .redacted(vec![Redacted { + name: Name { + description: None, + type_field: Some(RedactedName::TechEmail.to_string()), + }, + reason: None, + pre_path: None, + post_path: None, + path_lang: None, + replacement_path: None, + method: None, + }]) + .build(); + let rdap = domain.to_response(); + + // WHEN + let actual = text_or_registered_redaction( + &rdap, + &RedactedName::TechEmail, + &Some("not_redacted".to_string()), + "redacted", + ); + + // THEN + assert_eq!(actual, Some("redacted".to_string())); + } + + #[test] + fn GIVEN_multiple_redaction_type_WHEN_search_for_one_of_the_types_THEN_true() { + // GIVEN + let domain = Domain::builder() + .ldh_name("example.com") + .redacted(vec![ + Redacted { + name: Name { + description: None, + type_field: Some(RedactedName::TechEmail.to_string()), + }, + reason: None, + pre_path: None, + post_path: None, + path_lang: None, + replacement_path: None, + method: None, + }, + Redacted { + name: Name { + description: None, + type_field: Some(RedactedName::RegistryRegistrantId.to_string()), + }, + reason: None, + pre_path: None, + post_path: None, + path_lang: None, + replacement_path: None, + method: None, + }, + ]) + .build(); + let rdap = domain.to_response(); + + // WHEN + let actual = is_redaction_registered(&rdap, &RedactedName::TechEmail); + + // THEN + assert!(actual); + } + + #[test] + fn GIVEN_multiple_redaction_type_WHEN_search_for_multiple_that_some_exist_THEN_true() { + // GIVEN + let domain = Domain::builder() + .ldh_name("example.com") + .redacted(vec![ + Redacted { + name: Name { + description: None, + type_field: Some(RedactedName::TechEmail.to_string()), + }, + reason: None, + pre_path: None, + post_path: None, + path_lang: None, + replacement_path: None, + method: None, + }, + Redacted { + name: Name { + description: None, + type_field: Some(RedactedName::RegistryRegistrantId.to_string()), + }, + reason: None, + pre_path: None, + post_path: None, + path_lang: None, + replacement_path: None, + method: None, + }, + ]) + .build(); + let rdap = domain.to_response(); + + // WHEN + let actual = are_redactions_registered( + &rdap, + &[&RedactedName::TechEmail, &RedactedName::RegistrantName], + ); + + // THEN + assert!(actual); + } + + #[test] + fn GIVEN_multiple_redaction_type_WHEN_search_for_multiple_that_not_exist_THEN_false() { + // GIVEN + let domain = Domain::builder() + .ldh_name("example.com") + .redacted(vec![ + Redacted { + name: Name { + description: None, + type_field: Some(RedactedName::TechEmail.to_string()), + }, + reason: None, + pre_path: None, + post_path: None, + path_lang: None, + replacement_path: None, + method: None, + }, + Redacted { + name: Name { + description: None, + type_field: Some(RedactedName::RegistryRegistrantId.to_string()), + }, + reason: None, + pre_path: None, + post_path: None, + path_lang: None, + replacement_path: None, + method: None, + }, + ]) + .build(); + let rdap = domain.to_response(); + + // WHEN + let actual = are_redactions_registered( + &rdap, + &[ + &RedactedName::RegistrantPhone, + &RedactedName::RegistrantName, + ], + ); + + // THEN + assert!(!actual); + } + + #[test] + fn GIVEN_no_redactions_WHEN_search_for_type_THEN_false() { + // GIVEN + let domain = Domain::builder().ldh_name("example.com").build(); + let rdap = domain.to_response(); + + // WHEN + let actual = is_redaction_registered(&rdap, &RedactedName::TechEmail); + + // THEN + assert!(!actual); + } + + #[test] + fn GIVEN_redaction_type_WHEN_search_for_wrong_type_THEN_false() { + // GIVEN + let domain = Domain::builder() + .ldh_name("example.com") + .redacted(vec![Redacted { + name: Name { + description: None, + type_field: Some(RedactedName::RegistryRegistrantId.to_string()), + }, + reason: None, + pre_path: None, + post_path: None, + path_lang: None, + replacement_path: None, + method: None, + }]) + .build(); + let rdap = domain.to_response(); + + // WHEN + let actual = is_redaction_registered(&rdap, &RedactedName::TechEmail); + + // THEN + assert!(!actual); + } + + #[test] + fn GIVEN_entity_and_redaction_type_WHEN_search_for_type_on_entity_with_role_THEN_true() { + // GIVEN + let domain = Domain::builder() + .ldh_name("example.com") + .redacted(vec![Redacted { + name: Name { + description: None, + type_field: Some(RedactedName::TechEmail.to_string()), + }, + reason: None, + pre_path: None, + post_path: None, + path_lang: None, + replacement_path: None, + method: None, + }]) + .build(); + let rdap = domain.to_response(); + let role = EntityRole::Technical.to_string(); + let entity = Entity::builder() + .handle("foo_bar") + .role(role.clone()) + .build(); + + // WHEN + let actual = is_redaction_registered_for_role( + &rdap, + &RedactedName::TechEmail, + &entity, + &EntityRole::Technical, + ); + + // THEN + assert!(actual); + } + + #[test] + fn GIVEN_entity_and_multiple_redaction_WHEN_search_for_multipe_type_on_entity_with_roles_THEN_true( + ) { + // GIVEN + let domain = Domain::builder() + .ldh_name("example.com") + .redacted(vec![ + Redacted { + name: Name { + description: None, + type_field: Some(RedactedName::TechEmail.to_string()), + }, + reason: None, + pre_path: None, + post_path: None, + path_lang: None, + replacement_path: None, + method: None, + }, + Redacted { + name: Name { + description: None, + type_field: Some(RedactedName::RegistryRegistrantId.to_string()), + }, + reason: None, + pre_path: None, + post_path: None, + path_lang: None, + replacement_path: None, + method: None, + }, + ]) + .build(); + let rdap = domain.to_response(); + let role = EntityRole::Technical.to_string(); + let entity = Entity::builder() + .handle("foo_bar") + .role(role.clone()) + .build(); + + // WHEN + let actual = are_redactions_registered_for_roles( + &rdap, + &[&RedactedName::TechEmail, &RedactedName::TechPhoneExt], + &entity, + &[&EntityRole::Technical, &EntityRole::Abuse], + ); + + // THEN + assert!(actual); + } + + #[test] + fn GIVEN_entity_and_multiple_redaction_WHEN_search_for_not_exist_type_on_entity_with_roles_THEN_false( + ) { + // GIVEN + let domain = Domain::builder() + .ldh_name("example.com") + .redacted(vec![ + Redacted { + name: Name { + description: None, + type_field: Some(RedactedName::TechEmail.to_string()), + }, + reason: None, + pre_path: None, + post_path: None, + path_lang: None, + replacement_path: None, + method: None, + }, + Redacted { + name: Name { + description: None, + type_field: Some(RedactedName::RegistryRegistrantId.to_string()), + }, + reason: None, + pre_path: None, + post_path: None, + path_lang: None, + replacement_path: None, + method: None, + }, + ]) + .build(); + let rdap = domain.to_response(); + let role = EntityRole::Technical.to_string(); + let entity = Entity::builder() + .handle("foo_bar") + .role(role.clone()) + .build(); + + // WHEN + let actual = are_redactions_registered_for_roles( + &rdap, + &[&RedactedName::TechPhone, &RedactedName::TechPhoneExt], + &entity, + &[&EntityRole::Technical, &EntityRole::Abuse], + ); + + // THEN + assert!(!actual); + } + + #[test] + fn GIVEN_entity_and_multiple_redaction_WHEN_search_for_type_on_entity_with_other_rolesroles_THEN_false( + ) { + // GIVEN + let domain = Domain::builder() + .ldh_name("example.com") + .redacted(vec![ + Redacted { + name: Name { + description: None, + type_field: Some(RedactedName::TechEmail.to_string()), + }, + reason: None, + pre_path: None, + post_path: None, + path_lang: None, + replacement_path: None, + method: None, + }, + Redacted { + name: Name { + description: None, + type_field: Some(RedactedName::RegistryRegistrantId.to_string()), + }, + reason: None, + pre_path: None, + post_path: None, + path_lang: None, + replacement_path: None, + method: None, + }, + ]) + .build(); + let rdap = domain.to_response(); + let role = EntityRole::Technical.to_string(); + let entity = Entity::builder() + .handle("foo_bar") + .role(role.clone()) + .build(); + + // WHEN + let actual = are_redactions_registered_for_roles( + &rdap, + &[&RedactedName::TechEmail, &RedactedName::TechPhoneExt], + &entity, + &[&EntityRole::Billing, &EntityRole::Abuse], + ); + + // THEN + assert!(!actual); + } + + #[test] + fn GIVEN_entity_and_redaction_type_WHEN_get_text_for_type_on_entity_with_role_THEN_redaction_text_returned( + ) { + // GIVEN + let domain = Domain::builder() + .ldh_name("example.com") + .redacted(vec![Redacted { + name: Name { + description: None, + type_field: Some(RedactedName::TechEmail.to_string()), + }, + reason: None, + pre_path: None, + post_path: None, + path_lang: None, + replacement_path: None, + method: None, + }]) + .build(); + let rdap = domain.to_response(); + let role = EntityRole::Technical.to_string(); + let entity = Entity::builder() + .handle("foo_bar") + .role(role.clone()) + .build(); + + // WHEN + let actual = text_or_registered_redaction_for_role( + &rdap, + &RedactedName::TechEmail, + &entity, + &EntityRole::Technical, + &Some("not_redacted".to_string()), + "redacted", + ); + + // THEN + assert_eq!(actual, Some("redacted".to_string())); + } +} diff --git a/icann-rdap-client/src/rdap/request.rs b/icann-rdap-client/src/rdap/request.rs new file mode 100644 index 0000000..30334a8 --- /dev/null +++ b/icann-rdap-client/src/rdap/request.rs @@ -0,0 +1,177 @@ +//! Functions to make RDAP requests. + +use { + icann_rdap_common::{httpdata::HttpData, iana::IanaRegistryType, response::RdapResponse}, + serde::{Deserialize, Serialize}, + serde_json::Value, +}; + +use crate::{ + http::{wrapped_request, Client}, + iana::bootstrap::{qtype_to_bootstrap_url, BootstrapStore}, + RdapClientError, +}; + +use super::qtype::QueryType; + +/// Makes an RDAP request with a full RDAP URL. +/// +/// This function takes the following parameters: +/// * url - a string reference of the URL +/// * client - a reference to a [reqwest::Client]. +/// +/// ```no_run +/// use icann_rdap_client::prelude::*; +/// use std::str::FromStr; +/// use tokio::main; +/// +/// #[tokio::main] +/// async fn main() -> Result<(), RdapClientError> { +/// +/// // create a client (from icann-rdap-common) +/// let config = ClientConfig::default(); +/// let client = create_client(&config)?; +/// +/// // issue the RDAP query +/// let response = +/// rdap_url_request( +/// "https://rdap-bootstrap.arin.net/bootstrap/ip/192.168.0.1", +/// &client, +/// ).await?; +/// +/// Ok(()) +/// } +/// ``` +pub async fn rdap_url_request(url: &str, client: &Client) -> Result { + let wrapped_response = wrapped_request(url, client).await?; + // for convenience purposes + let text = wrapped_response.text; + let http_data = wrapped_response.http_data; + + let json: Result = serde_json::from_str(&text); + if let Ok(rdap_json) = json { + let rdap = RdapResponse::try_from(rdap_json)?; + Ok(ResponseData { + http_data, + rdap_type: rdap.to_string(), + rdap, + }) + } else { + Err(RdapClientError::ParsingError(Box::new( + crate::ParsingErrorInfo { + text, + http_data, + error: json.err().unwrap(), + }, + ))) + } +} + +/// Makes an RDAP request with a base URL. +/// +/// This function takes the following parameters: +/// * base_url - a string reference of the base URL +/// * query_type - a reference to the RDAP query. +/// * client - a reference to a [reqwest::Client]. +/// +/// ```no_run +/// use icann_rdap_client::prelude::*; +/// use std::str::FromStr; +/// use tokio::main; +/// +/// #[tokio::main] +/// async fn main() -> Result<(), RdapClientError> { +/// +/// // create a query +/// let query = QueryType::from_str("192.168.0.1")?; +/// // or +/// let query = QueryType::from_str("icann.org")?; +/// +/// // create a client (from icann-rdap-common) +/// let config = ClientConfig::default(); +/// let client = create_client(&config)?; +/// +/// // issue the RDAP query +/// let response = +/// rdap_request( +/// "https://rdap-bootstrap.arin.net/bootstrap", +/// &query, +/// &client, +/// ).await?; +/// +/// Ok(()) +/// } +/// ``` +pub async fn rdap_request( + base_url: &str, + query_type: &QueryType, + client: &Client, +) -> Result { + let url = query_type.query_url(base_url)?; + rdap_url_request(&url, client).await +} + +/// Makes an RDAP request using bootstrapping. +/// +/// This function takes the following parameters: +/// * query_type - a reference to the RDAP query. +/// * client - a reference to a [reqwest::Client]. +/// * store - a reference to a [BootstrapStore]. +/// * callback - a closure that is called when an IANA registry is fetched. +/// +/// The [BootstrapStore] is responsible for holding IANA RDAP bootstrap registries. +/// It will be populated with IANA registries as needed. Ideally, the calling code +/// would be kept it in the same scope as `client`. When using the [crate::iana::bootstrap::MemoryBootstrapStore], +/// creating a new store for each request will result it fetching the appropriate IANA +/// registry with each request which is most likely not the desired behavior. +/// +/// ```no_run +/// use icann_rdap_client::prelude::*; +/// use std::str::FromStr; +/// use tokio::main; +/// +/// #[tokio::main] +/// async fn main() -> Result<(), RdapClientError> { +/// +/// // create a query +/// let query = QueryType::from_str("192.168.0.1")?; +/// // or +/// let query = QueryType::from_str("icann.org")?; +/// +/// // create a client (from icann-rdap-common) +/// let config = ClientConfig::default(); +/// let client = create_client(&config)?; +/// let store = MemoryBootstrapStore::new(); +/// +/// // issue the RDAP query +/// let response = +/// rdap_bootstrapped_request( +/// &query, +/// &client, +/// &store, +/// |reg| eprintln!("fetching {reg:?}") +/// ).await?; +/// +/// Ok(()) +/// } +/// ``` +pub async fn rdap_bootstrapped_request( + query_type: &QueryType, + client: &Client, + store: &dyn BootstrapStore, + callback: F, +) -> Result +where + F: FnOnce(&IanaRegistryType), +{ + let base_url = qtype_to_bootstrap_url(client, store, query_type, callback).await?; + rdap_request(&base_url, query_type, client).await +} + +/// The data returned from an rdap request. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ResponseData { + pub rdap: RdapResponse, + pub rdap_type: String, + pub http_data: HttpData, +} diff --git a/icann-rdap-client/src/rdap/rr.rs b/icann-rdap-client/src/rdap/rr.rs new file mode 100644 index 0000000..d7e94d6 --- /dev/null +++ b/icann-rdap-client/src/rdap/rr.rs @@ -0,0 +1,60 @@ +//! Structures that describe a request/response. + +use { + icann_rdap_common::check::Checks, + serde::{Deserialize, Serialize}, + strum_macros::Display, +}; + +use crate::rdap::request::ResponseData; + +/// Types of RDAP servers. +#[derive(Serialize, Deserialize, Display, Clone, Copy)] +pub enum SourceType { + #[strum(serialize = "Domain Registry")] + DomainRegistry, + #[strum(serialize = "Domain Registrar")] + DomainRegistrar, + #[strum(serialize = "Regional Internet Registry")] + RegionalInternetRegistry, + #[strum(serialize = "Local Internet Registry")] + LocalInternetRegistry, + #[strum(serialize = "Uncategorized Registry")] + UncategorizedRegistry, +} + +/// Represents meta data about the request. +#[derive(Serialize, Deserialize, Clone, Copy)] +pub struct RequestData<'a> { + /// The request number. That is, request 1, request 2, etc... + pub req_number: usize, + + /// A human-friendly name to identify the source of the information. + /// Examples might be "registry", "registrar", etc... + pub source_host: &'a str, + + /// Represents the type of source. + pub source_type: SourceType, +} + +/// Structure for serializing request and response data. +#[derive(Clone, Serialize)] +pub struct RequestResponse<'a> { + pub req_data: &'a RequestData<'a>, + pub res_data: &'a ResponseData, + pub checks: Checks, +} + +/// The primary purpose for this struct is to allow deserialization for testing. +/// If somebody can help get #[serde(borrow)] to work for the non-owned version, +/// that would be awesome. +#[derive(Clone, Deserialize)] +pub struct RequestResponseOwned<'a> { + #[serde(borrow)] + pub req_data: RequestData<'a>, + pub res_data: ResponseData, + pub checks: Checks, +} + +/// A [Vec] of [RequestResponse]. +pub type RequestResponses<'a> = Vec>; diff --git a/icann-rdap-client/src/test_files/example-1_empty_value-expected.json b/icann-rdap-client/src/test_files/example-1_empty_value-expected.json new file mode 100644 index 0000000..db51d65 --- /dev/null +++ b/icann-rdap-client/src/test_files/example-1_empty_value-expected.json @@ -0,0 +1,259 @@ +{ + "rdapConformance": [ + "rdap_level_0", + "redacted" + ], + "objectClassName": "domain", + "ldhName": "example-1.net", + "secureDNS": { + "delegationSigned": false + }, + "notices": [ + { + "title": "Terms of Use", + "description": [ + "Service subject to Terms of Use." + ], + "links": [ + { + "rel": "self", + "href": "https://www.example.com/terms-of-use", + "type": "text/html", + "value": "https://www.example.com/terms-of-use" + } + ] + } + ], + "nameservers": [ + { + "objectClassName": "nameserver", + "ldhName": "ns1.example.com" + }, + { + "objectClassName": "nameserver", + "ldhName": "ns2.example.com" + } + ], + "entities": [ + { + "objectClassName": "entity", + "handle": "123", + "roles": [ + "registrar" + ], + "publicIds": [ + { + "type": "IANA Registrar ID", + "identifier": "1" + } + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Example Registrar Inc." + ], + [ + "adr", + {}, + "text", + [ + "", + "Suite 100", + "123 Example Dr.", + "Dulles", + "VA", + "20166-6503", + "US" + ] + ], + [ + "email", + {}, + "text", + "contact@organization.example" + ], + [ + "tel", + { + "type": "voice" + }, + "uri", + "tel:+1.7035555555" + ], + [ + "tel", + { + "type": "fax" + }, + "uri", + "tel:+1.7035555556" + ] + ] + ], + "entities": [ + { + "objectClassName": "entity", + "roles": [ + "abuse" + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Abuse Contact" + ], + [ + "email", + {}, + "text", + "abuse@organization.example" + ], + [ + "tel", + { + "type": "voice" + }, + "uri", + "tel:+1.7035555555" + ] + ] + ] + } + ] + }, + { + "objectClassName": "entity", + "handle": "XXXX", + "roles": [ + "registrant" + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "*REDACTED*" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + "", + "", + "QC", + "", + "Canada" + ] + ] + ] + ] + }, + { + "objectClassName": "entity", + "handle": "YYYY", + "roles": [ + "technical" + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "" + ], + [ + "org", + {}, + "text", + "Example Inc." + ], + [ + "adr", + {}, + "text", + [ + "", + "Suite 1234", + "4321 Rue Somewhere", + "Quebec", + "QC", + "G1V 2M2", + "Canada" + ] + ] + ] + ] + } + ], + "events": [ + { + "eventAction": "registration", + "eventDate": "1997-06-03T00:00:00Z" + }, + { + "eventAction": "last changed", + "eventDate": "2020-05-28T01:35:00Z" + }, + { + "eventAction": "expiration", + "eventDate": "2021-06-03T04:00:00Z" + } + ], + "status": [ + "server delete prohibited", + "server update prohibited", + "server transfer prohibited", + "client transfer prohibited" + ], + "redacted": [ + { + "name": { + "description": "Registrant Name" + }, + "postPath": "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='fn')][3]", + "pathLang": "jsonpath", + "method": "emptyValue", + "reason": { + "description": "Server policy" + } + } + ] +} \ No newline at end of file diff --git a/icann-rdap-client/src/test_files/example-1_empty_value.json b/icann-rdap-client/src/test_files/example-1_empty_value.json new file mode 100644 index 0000000..d998719 --- /dev/null +++ b/icann-rdap-client/src/test_files/example-1_empty_value.json @@ -0,0 +1,240 @@ +{ + "rdapConformance": [ + "rdap_level_0", + "redacted" + ], + "objectClassName": "domain", + "ldhName": "example-1.net", + "secureDNS": { "delegationSigned": false }, + "notices": [ + { + "title": "Terms of Use", + "description": [ "Service subject to Terms of Use." ], + "links": [ + { + "rel": "self", + "href": "https://www.example.com/terms-of-use", + "type": "text/html", + "value": "https://www.example.com/terms-of-use" + } + ] + } + ], + "nameservers": [ + { + "objectClassName": "nameserver", "ldhName": "ns1.example.com" }, + { + "objectClassName": "nameserver", "ldhName": "ns2.example.com" } + ], + "entities": [ + { + "objectClassName": "entity", + "handle": "123", + "roles": [ "registrar" ], + "publicIds": [ + { "type": "IANA Registrar ID", "identifier": "1" } + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Example Registrar Inc." + ], + [ + "adr", + {}, + "text", + [ + "", + "Suite 100", + "123 Example Dr.", + "Dulles", + "VA", + "20166-6503", + "US" + ] + ], + [ + "email", + {}, + "text", + "contact@organization.example" + ], + [ + "tel", + { + "type": "voice" + }, + "uri", + "tel:+1.7035555555" + ], + [ + "tel", + { + "type": "fax" + }, + "uri", + "tel:+1.7035555556" + ] + ] + ], + "entities": [ + { + "objectClassName": "entity", + "roles": [ + "abuse" + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Abuse Contact" + ], + [ + "email", + {}, + "text", + "abuse@organization.example" + ], + [ + "tel", + { + "type": "voice" + }, + "uri", + "tel:+1.7035555555" + ] + ] + ] + } + ] + }, + { + "objectClassName": "entity", + "handle": "XXXX", + "roles": [ + "registrant" + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + "", + "", + "QC", + "", + "Canada" + ] + ] + ] + ] + }, + { + "objectClassName": "entity", + "handle": "YYYY", + "roles": [ + "technical" + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "" + ], + [ + "org", + {}, + "text", + "Example Inc." + ], + [ + "adr", + {}, + "text", + [ + "", + "Suite 1234", + "4321 Rue Somewhere", + "Quebec", + "QC", + "G1V 2M2", + "Canada" + ] + ] + ] + ] + } + ], + "events": [ + { + "eventAction": "registration", "eventDate": "1997-06-03T00:00:00Z" + }, + { + "eventAction": "last changed", "eventDate": "2020-05-28T01:35:00Z" + }, + { + "eventAction": "expiration", "eventDate": "2021-06-03T04:00:00Z" + } + ], + "status": [ + "server delete prohibited", "server update prohibited", "server transfer prohibited", "client transfer prohibited" + ], + "redacted": [ + { + "name": { + "description": "Registrant Name" + }, + "postPath": "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='fn')][3]", + "pathLang": "jsonpath", + "method": "emptyValue", + "reason": { + "description": "Server policy" + } + } + ] +} \ No newline at end of file diff --git a/icann-rdap-client/src/test_files/example-2_partial_value-expected.json b/icann-rdap-client/src/test_files/example-2_partial_value-expected.json new file mode 100644 index 0000000..3d6685f --- /dev/null +++ b/icann-rdap-client/src/test_files/example-2_partial_value-expected.json @@ -0,0 +1,302 @@ +{ + "rdapConformance": [ + "rdap_level_0", + "redacted" + ], + "objectClassName": "domain", + "ldhName": "example-3.net", + "secureDNS": { + "delegationSigned": false + }, + "notices": [ + { + "title": "Terms of Use", + "description": [ + "Service subject to Terms of Use." + ], + "links": [ + { + "rel": "self", + "href": "https://www.example.com/terms-of-use", + "type": "text/html", + "value": "https://www.example.com/terms-of-use" + } + ] + } + ], + "nameservers": [ + { + "objectClassName": "nameserver", + "ldhName": "ns1.example.com" + }, + { + "objectClassName": "nameserver", + "ldhName": "ns2.example.com" + } + ], + "entities": [ + { + "objectClassName": "entity", + "handle": "123", + "roles": [ + "registrar" + ], + "publicIds": [ + { + "type": "IANA Registrar ID", + "identifier": "1" + } + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Example Registrar Inc." + ], + [ + "adr", + {}, + "text", + [ + "", + "Suite 100", + "123 Example Dr.", + "Dulles", + "VA", + "20166-6503", + "US" + ] + ], + [ + "email", + {}, + "text", + "contact@organization.example" + ], + [ + "tel", + { + "type": "voice" + }, + "uri", + "tel:+1.7035555555" + ], + [ + "tel", + { + "type": "fax" + }, + "uri", + "tel:+1.7035555556" + ] + ] + ], + "entities": [ + { + "objectClassName": "entity", + "roles": [ + "abuse" + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Abuse Contact" + ], + [ + "email", + {}, + "text", + "abuse@organization.example" + ], + [ + "tel", + { + "type": "voice" + }, + "uri", + "tel:+1.7035555555" + ] + ] + ] + } + ] + }, + { + "objectClassName": "entity", + "handle": "XXXX", + "roles": [ + "registrant" + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "*REDACTED*" + ], + [ + "adr", + {}, + "text", + [ + "*REDACTED*", + "*REDACTED*", + "*REDACTED*", + "*REDACTED*", + "QC", + "*REDACTED*", + "Canada" + ] + ] + ] + ] + }, + { + "objectClassName": "entity", + "handle": "YYYY", + "roles": [ + "technical" + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "*REDACTED*" + ], + [ + "org", + {}, + "text", + "Example Inc." + ], + [ + "adr", + {}, + "text", + [ + "", + "Suite 1234", + "4321 Rue Somewhere", + "Quebec", + "QC", + "G1V 2M2", + "Canada" + ] + ] + ] + ] + } + ], + "events": [ + { + "eventAction": "registration", + "eventDate": "1997-06-03T00:00:00Z" + }, + { + "eventAction": "last changed", + "eventDate": "2020-05-28T01:35:00Z" + }, + { + "eventAction": "expiration", + "eventDate": "2021-06-03T04:00:00Z" + } + ], + "status": [ + "server delete prohibited", + "server update prohibited", + "server transfer prohibited", + "client transfer prohibited" + ], + "redacted": [ + { + "name": { + "description": "Registrant Name" + }, + "postPath": "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='fn')][3]", + "pathLang": "jsonpath", + "method": "partialValue", + "reason": { + "description": "Server policy" + } + }, + { + "name": { + "description": "Registrant Street" + }, + "postPath": "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='adr')][3][:3]", + "pathLang": "jsonpath", + "method": "partialValue", + "reason": { + "description": "Server policy" + } + }, + { + "name": { + "description": "Registrant City" + }, + "postPath": "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='adr')][3][3]", + "pathLang": "jsonpath", + "method": "partialValue", + "reason": { + "description": "Server policy" + } + }, + { + "name": { + "description": "Registrant Postal Code" + }, + "postPath": "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='adr')][3][5]", + "pathLang": "jsonpath", + "method": "partialValue", + "reason": { + "description": "Server policy" + } + }, + { + "name": { + "description": "Technical Name" + }, + "postPath": "$.entities[?(@.roles[0]=='technical')].vcardArray[1][?(@[0]=='fn')][3]", + "method": "partialValue", + "reason": { + "description": "Server policy" + } + } + ] +} \ No newline at end of file diff --git a/icann-rdap-client/src/test_files/example-2_partial_value.json b/icann-rdap-client/src/test_files/example-2_partial_value.json new file mode 100644 index 0000000..0024f3a --- /dev/null +++ b/icann-rdap-client/src/test_files/example-2_partial_value.json @@ -0,0 +1,283 @@ +{ + "rdapConformance": [ + "rdap_level_0", + "redacted" + ], + "objectClassName": "domain", + "ldhName": "example-3.net", + "secureDNS": { "delegationSigned": false }, + "notices": [ + { + "title": "Terms of Use", + "description": [ "Service subject to Terms of Use." ], + "links": [ + { + "rel": "self", + "href": "https://www.example.com/terms-of-use", + "type": "text/html", + "value": "https://www.example.com/terms-of-use" + } + ] + } + ], + "nameservers": [ + { + "objectClassName": "nameserver", "ldhName": "ns1.example.com" }, + { + "objectClassName": "nameserver", "ldhName": "ns2.example.com" } + ], + "entities": [ + { + "objectClassName": "entity", + "handle": "123", + "roles": [ "registrar" ], + "publicIds": [ + { "type": "IANA Registrar ID", "identifier": "1" } + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Example Registrar Inc." + ], + [ + "adr", + {}, + "text", + [ + "", + "Suite 100", + "123 Example Dr.", + "Dulles", + "VA", + "20166-6503", + "US" + ] + ], + [ + "email", + {}, + "text", + "contact@organization.example" + ], + [ + "tel", + { + "type": "voice" + }, + "uri", + "tel:+1.7035555555" + ], + [ + "tel", + { + "type": "fax" + }, + "uri", + "tel:+1.7035555556" + ] + ] + ], + "entities": [ + { + "objectClassName": "entity", + "roles": [ + "abuse" + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Abuse Contact" + ], + [ + "email", + {}, + "text", + "abuse@organization.example" + ], + [ + "tel", + { + "type": "voice" + }, + "uri", + "tel:+1.7035555555" + ] + ] + ] + } + ] + }, + { + "objectClassName": "entity", + "handle": "XXXX", + "roles": [ + "registrant" + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + "", + "", + "QC", + "", + "Canada" + ] + ] + ] + ] + }, + { + "objectClassName": "entity", + "handle": "YYYY", + "roles": [ + "technical" + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "" + ], + [ + "org", + {}, + "text", + "Example Inc." + ], + [ + "adr", + {}, + "text", + [ + "", + "Suite 1234", + "4321 Rue Somewhere", + "Quebec", + "QC", + "G1V 2M2", + "Canada" + ] + ] + ] + ] + } + ], + "events": [ + { + "eventAction": "registration", "eventDate": "1997-06-03T00:00:00Z" + }, + { + "eventAction": "last changed", "eventDate": "2020-05-28T01:35:00Z" + }, + { + "eventAction": "expiration", "eventDate": "2021-06-03T04:00:00Z" + } + ], + "status": [ + "server delete prohibited", "server update prohibited", "server transfer prohibited", "client transfer prohibited" + ], + "redacted": [ + { + "name": { + "description": "Registrant Name" + }, + "postPath": "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='fn')][3]", + "pathLang": "jsonpath", + "method": "partialValue", + "reason": { + "description": "Server policy" + } + }, + { + "name": { + "description": "Registrant Street" + }, + "postPath": "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='adr')][3][:3]", + "pathLang": "jsonpath", + "method": "partialValue", + "reason": { + "description": "Server policy" + } + }, + { + "name": { + "description": "Registrant City" + }, + "postPath": "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='adr')][3][3]", + "pathLang": "jsonpath", + "method": "partialValue", + "reason": { + "description": "Server policy" + } + }, + { + "name": { + "description": "Registrant Postal Code" + }, + "postPath": "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='adr')][3][5]", + "pathLang": "jsonpath", + "method": "partialValue", + "reason": { + "description": "Server policy" + } + }, + { + "name": { + "description": "Technical Name" + }, + "postPath": "$.entities[?(@.roles[0]=='technical')].vcardArray[1][?(@[0]=='fn')][3]", + "method": "partialValue", + "reason": { + "description": "Server policy" + } + } + ] +} \ No newline at end of file diff --git a/icann-rdap-client/src/test_files/example-3-dont_replace_redaction_of_a_number.json b/icann-rdap-client/src/test_files/example-3-dont_replace_redaction_of_a_number.json new file mode 100644 index 0000000..9f01e8a --- /dev/null +++ b/icann-rdap-client/src/test_files/example-3-dont_replace_redaction_of_a_number.json @@ -0,0 +1,212 @@ +{ + "rdapConformance": [ + "rdap_level_0", + "redacted" + ], + "objectClassName": "domain", + "handle": "PPP", + "ldhName": "0.2.192.in-addr.arpa", + "nameservers": [ + { + "objectClassName": "nameserver", + "ldhName": "ns1.rir.example" + }, + { + "objectClassName": "nameserver", + "ldhName": "ns2.rir.example" + } + ], + "secureDNS": { + "delegationSigned": true, + "dsData": [ + { + "keyTag": 25345, + "algorithm": 8, + "digestType": 2, + "digest": "2788970E18EA14...C890C85B8205B94" + } + ] + }, + "remarks": [ + { + "description": [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links": [ + { + "value": "https://example.net/domain/0.2.192.in-addr.arpa", + "rel": "self", + "href": "https://example.net/domain/0.2.192.in-addr.arpa", + "type": "application/rdap+json" + } + ], + "events": [ + { + "eventAction": "registration", + "eventDate": "1990-12-31T23:59:59Z" + }, + { + "eventAction": "last changed", + "eventDate": "1991-12-31T23:59:59Z", + "eventActor": "joe@example.com" + } + ], + "entities": [ + { + "objectClassName": "entity", + "handle": "XXXX", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Joe User" + ], + [ + "kind", + {}, + "text", + "individual" + ], + [ + "lang", + { + "pref": "1" + }, + "language-tag", + "fr" + ], + [ + "lang", + { + "pref": "2" + }, + "language-tag", + "en" + ], + [ + "org", + { + "type": "work" + }, + "text", + "Example" + ], + [ + "title", + {}, + "text", + "Research Scientist" + ], + [ + "role", + {}, + "text", + "Project Lead" + ], + [ + "adr", + { + "type": "work" + }, + "text", + [ + "", + "Suite 1234", + "4321 Rue Somewhere", + "Quebec", + "QC", + "G1V 2M2", + "Canada" + ] + ], + [ + "tel", + { + "type": [ + "work", + "voice" + ], + "pref": "1" + }, + "uri", + "tel:+1-555-555-1234;ext=102" + ], + [ + "email", + { + "type": "work" + }, + "text", + "joe.user@example.com" + ] + ] + ], + "roles": [ + "registrant" + ], + "remarks": [ + { + "description": [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links": [ + { + "value": "https://example.net/entity/XXXX", + "rel": "self", + "href": "https://example.net/entity/XXXX", + "type": "application/rdap+json" + } + ], + "events": [ + { + "eventAction": "registration", + "eventDate": "1990-12-31T23:59:59Z" + }, + { + "eventAction": "last changed", + "eventDate": "1991-12-31T23:59:59Z", + "eventActor": "joe@example.com" + } + ] + } + ], + "network": { + "objectClassName": "ip network", + "handle": "XXXX-RIR", + "startAddress": "192.0.2.0", + "endAddress": "192.0.2.255", + "ipVersion": "v4", + "name": "NET-RTR-1", + "type": "DIRECT ALLOCATION", + "country": "AU", + "parentHandle": "YYYY-RIR", + "status": [ + "active" + ] + }, + "redacted": [ + { + "name": { + "description": "Registrant keyTag" + }, + "postPath": "$['secureDNS']['dsData'][0]['keyTag']", + "pathLang": "jsonpath", + "method": "partialValue" + } + ] +} \ No newline at end of file diff --git a/icann-rdap-client/src/test_files/home.moscow-expected.gtld b/icann-rdap-client/src/test_files/home.moscow-expected.gtld new file mode 100644 index 0000000..6a65bb0 --- /dev/null +++ b/icann-rdap-client/src/test_files/home.moscow-expected.gtld @@ -0,0 +1,24 @@ + + +Domain Name: home.moscow +Registry Domain ID: 20211019192813345912_c936bef81d9614db04ffc278b29daf5a_domain-FIR +Creation Date: 2021-10-19T19:28:12Z +Updated Date: 2023-11-20T18:36:22Z +Registry Expiry Date: 2024-10-19T19:28:12Z +Domain Status: client transfer prohibited +Registrar Whois Server: whois.flexireg.net +Registrar: Limited Liability Company "Registrar of domain names REG.RU" +Registrar Street: +Registrar City: +Registrar State/Province: +Registrar Postal Code: +Registrar Country: +Registrar IANA ID: 1606 +Registrar Abuse Contact Email: abuse@reg.ru +Registrar Abuse Contact Phone: tel:+7 +Registrar Abuse Contact Phone: tel:495 +Registrar Abuse Contact Phone: tel:5801111 +Name Server: ns1.reg.ru +Name Server: ns2.reg.ru +URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/ +>>> Last update of RDAP database: 2024-06-18T22:01:39+03:00 <<< diff --git a/icann-rdap-client/src/test_files/home.moscow.json b/icann-rdap-client/src/test_files/home.moscow.json new file mode 100644 index 0000000..597b5e9 --- /dev/null +++ b/icann-rdap-client/src/test_files/home.moscow.json @@ -0,0 +1,345 @@ +{ + "rdapConformance": [ + "rdap_level_0", + "icann_rdap_response_profile_0", + "icann_rdap_technical_implementation_guide_0" + ], + "notices": [ + { + "title": "Terms of Use", + "description": [ + "Terms of Use page." + ], + "links": [ + { + "value": "https://flexireg.net/ru/whois-terms_of_use.en.html", + "rel": "related", + "href": "https://flexireg.net/ru/whois-terms_of_use.en.html", + "type": "text/html" + } + ] + }, + { + "title": "Status Codes", + "description": [ + "For more information on domain status codes, please visit https://icann.org/epp." + ], + "links": [ + { + "value": "https://icann.org/epp", + "rel": "related", + "href": "https://icann.org/epp", + "type": "text/html" + } + ] + }, + { + "title": "RDDS Inaccuracy Complaint Form", + "description": [ + "URL of the ICANN RDDS Inaccuracy Complaint Form:https://icann.org/wicf" + ], + "links": [ + { + "value": "https://www.icann.org/wicf", + "rel": "related", + "href": "https://www.icann.org/wicf", + "type": "text/html" + } + ] + } + ], + "objectClassName": "domain", + "handle": "20211019192813345912_c936bef81d9614db04ffc278b29daf5a_domain-FIR", + "links": [ + { + "value": "https://flexireg.net/moscow/rdap/domain/home.moscow", + "rel": "self", + "href": "https://flexireg.net/moscow/rdap/domain/home.moscow", + "type": "application/rdap+json" + } + ], + "events": [ + { + "eventAction": "registration", + "eventDate": "2021-10-19T19:28:12Z" + }, + { + "eventAction": "last changed", + "eventDate": "2023-11-20T18:36:22Z" + }, + { + "eventAction": "expiration", + "eventDate": "2024-10-19T19:28:12Z" + }, + { + "eventAction": "last update of RDAP database", + "eventDate": "2024-06-18T22:01:39+03:00" + } + ], + "status": [ + "client transfer prohibited" + ], + "port43": "whois.flexireg.net", + "entities": [ + { + "objectClassName": "entity", + "handle": "regru-msk-fir", + "links": [ + { + "value": "https://flexireg.net/moscow/rdap/entity/regru-msk-fir", + "rel": "self", + "href": "https://flexireg.net/moscow/rdap/entity/regru-msk-fir", + "type": "application/rdap+json" + } + ], + "entities": [ + { + "objectClassName": "entity", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Abuse contact" + ], + [ + "tel", + { + "pref": "1", + "type": [ + "work", + "voice" + ] + }, + "uri", + "tel:+7" + ], + [ + "tel", + { + "pref": "1", + "type": [ + "work", + "voice" + ] + }, + "uri", + "tel:495" + ], + [ + "tel", + { + "pref": "1", + "type": [ + "work", + "voice" + ] + }, + "uri", + "tel:5801111" + ], + [ + "email", + { + "type": "work" + }, + "text", + "abuse@reg.ru" + ] + ] + ], + "roles": [ + "abuse" + ] + } + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Limited Liability Company \"Registrar of domain names REG.RU\"" + ], + [ + "adr", + { + "type": "work" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "email", + { + "type": "work" + }, + "text", + "info@reg.ru" + ] + ] + ], + "roles": [ + "registrar" + ], + "publicIds": [ + { + "type": "IANA Registrar ID", + "identifier": "1606" + } + ] + }, + { + "objectClassName": "entity", + "handle": "20231120183619454758_28c280fc7dcaa0dff7cd2b0e9b87e263_contact-FIR", + "remarks": [ + { + "title": "REDACTED FOR PRIVACY", + "description": [ + "Some of the data in this object has been removed" + ] + }, + { + "title": "EMAIL REDACTED FOR PRIVACY", + "description": [ + "Please query the RDDS service of the Registrar of Record identified in this output for information on how to contact the Registrant of the queried domain name" + ] + } + ], + "links": [ + { + "value": "https://flexireg.net/moscow/rdap/entity/20231120183619454758_28c280fc7dcaa0dff7cd2b0e9b87e263_contact-FIR", + "rel": "self", + "href": "https://flexireg.net/moscow/rdap/entity/20231120183619454758_28c280fc7dcaa0dff7cd2b0e9b87e263_contact-FIR", + "type": "application/rdap+json" + } + ], + "roles": [ + "registrant" + ] + }, + { + "objectClassName": "entity", + "handle": "20231120183619841977_163830e2aa5d03e5d5f7d2a1b936acec_contact-FIR", + "remarks": [ + { + "title": "REDACTED FOR PRIVACY", + "description": [ + "Some of the data in this object has been removed" + ] + }, + { + "title": "EMAIL REDACTED FOR PRIVACY", + "description": [ + "Please query the RDDS service of the Registrar of Record identified in this output for information on how to contact the Registrant of the queried domain name" + ] + } + ], + "links": [ + { + "value": "https://flexireg.net/moscow/rdap/entity/20231120183619841977_163830e2aa5d03e5d5f7d2a1b936acec_contact-FIR", + "rel": "self", + "href": "https://flexireg.net/moscow/rdap/entity/20231120183619841977_163830e2aa5d03e5d5f7d2a1b936acec_contact-FIR", + "type": "application/rdap+json" + } + ], + "roles": [ + "administrative" + ] + }, + { + "objectClassName": "entity", + "handle": "20231120183621248371_e50abb5e0ee9095975084ceb57105843_contact-FIR", + "remarks": [ + { + "title": "REDACTED FOR PRIVACY", + "description": [ + "Some of the data in this object has been removed" + ] + }, + { + "title": "EMAIL REDACTED FOR PRIVACY", + "description": [ + "Please query the RDDS service of the Registrar of Record identified in this output for information on how to contact the Registrant of the queried domain name" + ] + } + ], + "links": [ + { + "value": "https://flexireg.net/moscow/rdap/entity/20231120183621248371_e50abb5e0ee9095975084ceb57105843_contact-FIR", + "rel": "self", + "href": "https://flexireg.net/moscow/rdap/entity/20231120183621248371_e50abb5e0ee9095975084ceb57105843_contact-FIR", + "type": "application/rdap+json" + } + ], + "roles": [ + "technical" + ] + } + ], + "ldhName": "home.moscow", + "secureDNS": { + "delegationSigned": false + }, + "nameservers": [ + { + "objectClassName": "nameserver", + "links": [ + { + "value": "https://flexireg.net/moscow/rdap/nameserver/ns1.reg.ru", + "rel": "self", + "href": "https://flexireg.net/moscow/rdap/nameserver/ns1.reg.ru", + "type": "application/rdap+json" + } + ], + "events": [ + { + "eventAction": "registration", + "eventDate": "2015-02-05T12:41:57Z" + } + ], + "ldhName": "ns1.reg.ru" + }, + { + "objectClassName": "nameserver", + "links": [ + { + "value": "https://flexireg.net/moscow/rdap/nameserver/ns2.reg.ru", + "rel": "self", + "href": "https://flexireg.net/moscow/rdap/nameserver/ns2.reg.ru", + "type": "application/rdap+json" + } + ], + "events": [ + { + "eventAction": "registration", + "eventDate": "2015-02-05T12:41:57Z" + } + ], + "ldhName": "ns2.reg.ru" + } + ] +} \ No newline at end of file diff --git a/icann-rdap-client/src/test_files/lemonde.fr-expected.gtld b/icann-rdap-client/src/test_files/lemonde.fr-expected.gtld new file mode 100644 index 0000000..563c8a4 --- /dev/null +++ b/icann-rdap-client/src/test_files/lemonde.fr-expected.gtld @@ -0,0 +1,52 @@ + + +Domain Name: lemonde.fr +Registry Domain ID: DOM000000024309-FRNIC +Creation Date: 2005-08-02T14:16:36Z +Registry Expiry Date: 2025-06-09T22:08:09Z +Updated Date: 2024-06-12T22:09:39.881923Z +Domain Status: server update prohibited +Domain Status: server transfer prohibited +Domain Status: server delete prohibited +Domain Status: server recover prohibited +Registrar Whois Server: whois.nameshield.net +Registrar: NAMESHIELD +Registrar Street: 39 boulevard des Capucines +Registrar City: PARIS +Registrar State/Province: +Registrar Postal Code: 75002 +Registrar Country: FR +Registrar IANA ID: 1251 +Technical Name: NAMESHIELD +Technical Organization: TECHNICAL department +Technical Street: 79 rue Desjardins +Technical City: ANGERS +Technical State/Province: +Technical Postal Code: 49100 +Technical Country: FR +Technical Email: technical@nameshield.net +Technical Phone: +33.241182828 +Technical Fax: +33.241182829 +Registrant Name: SOCIETE EDITRICE du monde +Registrant Organization: SOCIETE EDITRICE DU MONDE +Registrant Street: 67-69 avenue Pierre Mendes-France +Registrant City: PARIS +Registrant State/Province: +Registrant Postal Code: 75013 +Registrant Country: FR +Registrant Email: domain_names@lemonde.fr +Registrant Phone: +33.157282224 +Administrative Name: SOCIETE EDITRICE du monde +Administrative Organization: SOCIETE EDITRICE DU MONDE +Admin Street: 67-69 avenue Pierre Mendes-France +Admin City: PARIS +Admin State/Province: +Admin Postal Code: 75013 +Admin Country: FR +Administrative Email: domain_names@lemonde.fr +Administrative Phone: +33.157282224 +Name Server: ns-cloud-b4.googledomains.com +Name Server: ns-cloud-b2.googledomains.com +Name Server: ns-cloud-b3.googledomains.com +Name Server: ns-cloud-b1.googledomains.com +URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/ diff --git a/icann-rdap-client/src/test_files/lemonde.fr.json b/icann-rdap-client/src/test_files/lemonde.fr.json new file mode 100644 index 0000000..8674289 --- /dev/null +++ b/icann-rdap-client/src/test_files/lemonde.fr.json @@ -0,0 +1,1007 @@ +{ + "rdapConformance": [ + "rdap_level_0", + "icann_rdap_technical_implementation_guide_0", + "icann_rdap_response_profile_0" + ], + "objectClassName": "domain", + "handle": "DOM000000024309-FRNIC", + "links": [ + { + "value": "https://rdap.nic.fr/domain/lemonde.fr", + "rel": "self", + "href": "https://rdap.nic.fr/domain/lemonde.fr" + } + ], + "events": [ + { + "eventAction": "registration", + "eventDate": "2005-08-02T14:16:36Z" + }, + { + "eventAction": "expiration", + "eventDate": "2025-06-09T22:08:09Z" + }, + { + "eventAction": "last changed", + "eventDate": "2024-06-12T22:09:39.881923Z" + }, + { + "eventAction": "transfer", + "eventDate": "2022-06-09T22:08:09Z" + } + ], + "status": [ + "server update prohibited", + "server transfer prohibited", + "server delete prohibited", + "server recover prohibited" + ], + "port43": "whois.nameshield.net", + "entities": [ + { + "objectClassName": "entity", + "handle": "RAR24-FRNIC", + "remarks": [ + { + "description": [ + "No" + ] + } + ], + "links": [ + { + "value": "https://rdap.nic.fr/entity/RAR24-FRNIC", + "rel": "self", + "href": "https://rdap.nic.fr/entity/RAR24-FRNIC" + } + ], + "events": [ + { + "eventAction": "registration", + "eventDate": "1997-12-29T00:00:00Z" + }, + { + "eventAction": "last changed", + "eventDate": "2022-10-01T11:41:28.622961Z" + } + ], + "status": [ + "active" + ], + "port43": "whois.nameshield.net", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "NAMESHIELD" + ], + [ + "tel", + {}, + "text", + "+33.241182828" + ], + [ + "tel", + { + "type": "fax" + }, + "text", + "+33.241182829" + ], + [ + "email", + {}, + "text", + "registrar@nameshield.net" + ], + [ + "url", + {}, + "uri", + "https://www.nameshield.com/contact-nameshield/" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + "39 boulevard des Capucines", + "PARIS", + "", + "75002", + "FR" + ] + ] + ] + ], + "roles": [ + "sponsor", + "registrar" + ], + "publicIds": [ + { + "type": "IANA Registrar ID", + "identifier": "1251" + } + ] + }, + { + "objectClassName": "entity", + "handle": "N17024-FRNIC", + "remarks": [ + { + "description": [ + "NO" + ] + }, + { + "description": [ + "NO" + ] + }, + { + "description": [ + "ORGANIZATION" + ] + }, + { + "description": [ + "NO" + ] + }, + { + "description": [ + "NotIdentified" + ] + }, + { + "description": [ + "NAMESHIELD" + ] + } + ], + "links": [ + { + "value": "https://rdap.nic.fr/entity/N17024-FRNIC", + "rel": "self", + "href": "https://rdap.nic.fr/entity/N17024-FRNIC" + } + ], + "events": [ + { + "eventAction": "registration", + "eventDate": "2017-11-21T09:50:30Z" + }, + { + "eventAction": "last changed", + "eventDate": "2024-06-13T12:35:12.350131Z" + } + ], + "status": [ + "associated", + "active" + ], + "port43": "whois.nameshield.net", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "NAMESHIELD" + ], + [ + "org", + {}, + "text", + "TECHNICAL department" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + "79 rue Desjardins", + "ANGERS", + "", + "49100", + "FR" + ] + ], + [ + "email", + {}, + "text", + "technical@nameshield.net" + ], + [ + "tel", + {}, + "text", + "+33.241182828" + ], + [ + "tel", + { + "type": "fax" + }, + "text", + "+33.241182829" + ] + ] + ], + "roles": [ + "technical" + ] + }, + { + "objectClassName": "entity", + "handle": "SEDM254-FRNIC", + "remarks": [ + { + "description": [ + "NO" + ] + }, + { + "description": [ + "NO" + ] + }, + { + "description": [ + "ORGANIZATION" + ] + }, + { + "description": [ + "NO" + ] + }, + { + "description": [ + "NotIdentified" + ] + }, + { + "description": [ + "NAMESHIELD" + ] + } + ], + "links": [ + { + "value": "https://rdap.nic.fr/entity/SEDM254-FRNIC", + "rel": "self", + "href": "https://rdap.nic.fr/entity/SEDM254-FRNIC" + } + ], + "events": [ + { + "eventAction": "registration", + "eventDate": "2022-04-04T07:21:16Z" + } + ], + "status": [ + "server update prohibited", + "associated" + ], + "port43": "whois.nameshield.net", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "SOCIETE EDITRICE du monde" + ], + [ + "org", + {}, + "text", + "SOCIETE EDITRICE DU MONDE" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + "67-69 avenue Pierre Mendes-France", + "PARIS", + "", + "75013", + "FR" + ] + ], + [ + "email", + {}, + "text", + "domain_names@lemonde.fr" + ], + [ + "tel", + {}, + "text", + "+33.157282224" + ] + ] + ], + "roles": [ + "registrant" + ] + }, + { + "objectClassName": "entity", + "handle": "SEDM255-FRNIC", + "remarks": [ + { + "description": [ + "NO" + ] + }, + { + "description": [ + "NO" + ] + }, + { + "description": [ + "ORGANIZATION" + ] + }, + { + "description": [ + "NO" + ] + }, + { + "description": [ + "NotIdentified" + ] + }, + { + "description": [ + "NAMESHIELD" + ] + } + ], + "links": [ + { + "value": "https://rdap.nic.fr/entity/SEDM255-FRNIC", + "rel": "self", + "href": "https://rdap.nic.fr/entity/SEDM255-FRNIC" + } + ], + "events": [ + { + "eventAction": "registration", + "eventDate": "2022-04-04T07:21:17Z" + }, + { + "eventAction": "last changed", + "eventDate": "2022-11-01T07:38:07.668742Z" + } + ], + "status": [ + "associated", + "active" + ], + "port43": "whois.nameshield.net", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "SOCIETE EDITRICE du monde" + ], + [ + "org", + {}, + "text", + "SOCIETE EDITRICE DU MONDE" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + "67-69 avenue Pierre Mendes-France", + "PARIS", + "", + "75013", + "FR" + ] + ], + [ + "email", + {}, + "text", + "domain_names@lemonde.fr" + ], + [ + "tel", + {}, + "text", + "+33.157282224" + ] + ] + ], + "roles": [ + "administrative" + ] + } + ], + "ldhName": "lemonde.fr", + "nameservers": [ + { + "objectClassName": "nameserver", + "handle": "HOST65144-FRNIC", + "remarks": [ + { + "title": "result set truncated due to authorization", + "description": [ + "The list of results does not contain all results due to lack of authorization.", + "This may indicate to some clients that proper authorization will yield a longer result set." + ] + } + ], + "links": [ + { + "value": "https://rdap.nic.fr/nameserver/ns-cloud-b4.googledomains.com", + "rel": "self", + "href": "https://rdap.nic.fr/nameserver/ns-cloud-b4.googledomains.com" + } + ], + "status": [ + "associated", + "active" + ], + "port43": "whois.nic.fr", + "entities": [ + { + "objectClassName": "entity", + "handle": "RAR939-FRNIC", + "remarks": [ + { + "description": [ + "No" + ] + } + ], + "links": [ + { + "value": "https://rdap.nic.fr/entity/RAR939-FRNIC", + "rel": "self", + "href": "https://rdap.nic.fr/entity/RAR939-FRNIC" + } + ], + "events": [ + { + "eventAction": "registration", + "eventDate": "2014-07-29T15:35:33Z" + }, + { + "eventAction": "last changed", + "eventDate": "2022-10-12T18:54:01.785057Z" + } + ], + "status": [ + "active" + ], + "port43": "whois.nic.fr", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Registry Operations" + ], + [ + "tel", + {}, + "text", + "+33.139308300" + ], + [ + "tel", + { + "type": "fax" + }, + "text", + "+33.139308301" + ], + [ + "email", + {}, + "text", + "support@afnic.fr" + ], + [ + "url", + {}, + "uri", + "https://www.afnic.fr" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + [ + "AFNIC", + "immeuble le Stephenson", + "1, rue Stephenson" + ], + "Montigny-Le-Bretonneux", + "", + "78180", + "FR" + ] + ] + ] + ], + "roles": [ + "sponsor", + "registrar" + ], + "publicIds": [ + { + "type": "IANA Registrar ID", + "identifier": "9999" + } + ] + } + ], + "ldhName": "ns-cloud-b4.googledomains.com", + "ipAddresses": {} + }, + { + "objectClassName": "nameserver", + "handle": "HOST65142-FRNIC", + "remarks": [ + { + "title": "result set truncated due to authorization", + "description": [ + "The list of results does not contain all results due to lack of authorization.", + "This may indicate to some clients that proper authorization will yield a longer result set." + ] + } + ], + "links": [ + { + "value": "https://rdap.nic.fr/nameserver/ns-cloud-b2.googledomains.com", + "rel": "self", + "href": "https://rdap.nic.fr/nameserver/ns-cloud-b2.googledomains.com" + } + ], + "status": [ + "associated", + "active" + ], + "port43": "whois.nic.fr", + "entities": [ + { + "objectClassName": "entity", + "handle": "RAR939-FRNIC", + "remarks": [ + { + "description": [ + "No" + ] + } + ], + "links": [ + { + "value": "https://rdap.nic.fr/entity/RAR939-FRNIC", + "rel": "self", + "href": "https://rdap.nic.fr/entity/RAR939-FRNIC" + } + ], + "events": [ + { + "eventAction": "registration", + "eventDate": "2014-07-29T15:35:33Z" + }, + { + "eventAction": "last changed", + "eventDate": "2022-10-12T18:54:01.785057Z" + } + ], + "status": [ + "active" + ], + "port43": "whois.nic.fr", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Registry Operations" + ], + [ + "tel", + {}, + "text", + "+33.139308300" + ], + [ + "tel", + { + "type": "fax" + }, + "text", + "+33.139308301" + ], + [ + "email", + {}, + "text", + "support@afnic.fr" + ], + [ + "url", + {}, + "uri", + "https://www.afnic.fr" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + [ + "AFNIC", + "immeuble le Stephenson", + "1, rue Stephenson" + ], + "Montigny-Le-Bretonneux", + "", + "78180", + "FR" + ] + ] + ] + ], + "roles": [ + "sponsor", + "registrar" + ], + "publicIds": [ + { + "type": "IANA Registrar ID", + "identifier": "9999" + } + ] + } + ], + "ldhName": "ns-cloud-b2.googledomains.com", + "ipAddresses": {} + }, + { + "objectClassName": "nameserver", + "handle": "HOST65143-FRNIC", + "remarks": [ + { + "title": "result set truncated due to authorization", + "description": [ + "The list of results does not contain all results due to lack of authorization.", + "This may indicate to some clients that proper authorization will yield a longer result set." + ] + } + ], + "links": [ + { + "value": "https://rdap.nic.fr/nameserver/ns-cloud-b3.googledomains.com", + "rel": "self", + "href": "https://rdap.nic.fr/nameserver/ns-cloud-b3.googledomains.com" + } + ], + "status": [ + "associated", + "active" + ], + "port43": "whois.nic.fr", + "entities": [ + { + "objectClassName": "entity", + "handle": "RAR939-FRNIC", + "remarks": [ + { + "description": [ + "No" + ] + } + ], + "links": [ + { + "value": "https://rdap.nic.fr/entity/RAR939-FRNIC", + "rel": "self", + "href": "https://rdap.nic.fr/entity/RAR939-FRNIC" + } + ], + "events": [ + { + "eventAction": "registration", + "eventDate": "2014-07-29T15:35:33Z" + }, + { + "eventAction": "last changed", + "eventDate": "2022-10-12T18:54:01.785057Z" + } + ], + "status": [ + "active" + ], + "port43": "whois.nic.fr", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Registry Operations" + ], + [ + "tel", + {}, + "text", + "+33.139308300" + ], + [ + "tel", + { + "type": "fax" + }, + "text", + "+33.139308301" + ], + [ + "email", + {}, + "text", + "support@afnic.fr" + ], + [ + "url", + {}, + "uri", + "https://www.afnic.fr" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + [ + "AFNIC", + "immeuble le Stephenson", + "1, rue Stephenson" + ], + "Montigny-Le-Bretonneux", + "", + "78180", + "FR" + ] + ] + ] + ], + "roles": [ + "sponsor", + "registrar" + ], + "publicIds": [ + { + "type": "IANA Registrar ID", + "identifier": "9999" + } + ] + } + ], + "ldhName": "ns-cloud-b3.googledomains.com", + "ipAddresses": {} + }, + { + "objectClassName": "nameserver", + "handle": "HOST65141-FRNIC", + "remarks": [ + { + "title": "result set truncated due to authorization", + "description": [ + "The list of results does not contain all results due to lack of authorization.", + "This may indicate to some clients that proper authorization will yield a longer result set." + ] + } + ], + "links": [ + { + "value": "https://rdap.nic.fr/nameserver/ns-cloud-b1.googledomains.com", + "rel": "self", + "href": "https://rdap.nic.fr/nameserver/ns-cloud-b1.googledomains.com" + } + ], + "status": [ + "associated", + "active" + ], + "port43": "whois.nic.fr", + "entities": [ + { + "objectClassName": "entity", + "handle": "RAR939-FRNIC", + "remarks": [ + { + "description": [ + "No" + ] + } + ], + "links": [ + { + "value": "https://rdap.nic.fr/entity/RAR939-FRNIC", + "rel": "self", + "href": "https://rdap.nic.fr/entity/RAR939-FRNIC" + } + ], + "events": [ + { + "eventAction": "registration", + "eventDate": "2014-07-29T15:35:33Z" + }, + { + "eventAction": "last changed", + "eventDate": "2022-10-12T18:54:01.785057Z" + } + ], + "status": [ + "active" + ], + "port43": "whois.nic.fr", + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Registry Operations" + ], + [ + "tel", + {}, + "text", + "+33.139308300" + ], + [ + "tel", + { + "type": "fax" + }, + "text", + "+33.139308301" + ], + [ + "email", + {}, + "text", + "support@afnic.fr" + ], + [ + "url", + {}, + "uri", + "https://www.afnic.fr" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + [ + "AFNIC", + "immeuble le Stephenson", + "1, rue Stephenson" + ], + "Montigny-Le-Bretonneux", + "", + "78180", + "FR" + ] + ] + ] + ], + "roles": [ + "sponsor", + "registrar" + ], + "publicIds": [ + { + "type": "IANA Registrar ID", + "identifier": "9999" + } + ] + } + ], + "ldhName": "ns-cloud-b1.googledomains.com", + "ipAddresses": {} + } + ] +} diff --git a/icann-rdap-client/src/test_files/microsoft.click-expected.gtld b/icann-rdap-client/src/test_files/microsoft.click-expected.gtld new file mode 100644 index 0000000..52bf0b7 --- /dev/null +++ b/icann-rdap-client/src/test_files/microsoft.click-expected.gtld @@ -0,0 +1,41 @@ + + +Domain Name: microsoft.click +Registry Domain ID: DO_a7aec7e93f5797ee898b23cefe340fe3-UR +Creation Date: 2014-11-12T19:15:55.283Z +Registry Expiry Date: 2024-11-12T19:15:55.283Z +Updated Date: 2023-10-17T10:47:21.733Z +Domain Status: client update prohibited +Domain Status: client transfer prohibited +Domain Status: client delete prohibited +Registrar: MarkMonitor Inc. +Registrar Street: 3540 East Longwing Lane, Suite 300 +Registrar City: Meridian +Registrar State/Province: ID +Registrar Postal Code: 83646 +Registrar Country: US +Registrar Abuse Contact Email: abusecomplaints@markmonitor.com +Registrar Abuse Contact Phone: tel:+1.2083895740 +Registrar Abuse Contact Phone: tel:+1.2083895771 +Registrant Organization: Microsoft Corporation +Registrant Street: +Registrant City: +Registrant State/Province: WA +Registrant Postal Code: +Registrant Country: US +Admin Street: +Admin City: +Admin State/Province: +Admin Postal Code: +Admin Country: +Technical Street: +Technical City: +Technical State/Province: +Technical Postal Code: +Technical Country: +Name Server: ns4-08.azure-dns.info +Name Server: ns2-08.azure-dns.net +Name Server: ns3-08.azure-dns.org +Name Server: ns1-08.azure-dns.com +URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/ +>>> Last update of RDAP database: 2024-06-18T14:25:27.257Z <<< diff --git a/icann-rdap-client/src/test_files/microsoft.click.json b/icann-rdap-client/src/test_files/microsoft.click.json new file mode 100644 index 0000000..b6fbd46 --- /dev/null +++ b/icann-rdap-client/src/test_files/microsoft.click.json @@ -0,0 +1,455 @@ +{ + "rdapConformance": [ + "icann_rdap_technical_implementation_guide_0", + "ur_domain_check_0" + ], + "notices": [ + { + "title": "Status Codes", + "description": [ + "For more information on domain status codes, please visit https://icann.org/epp" + ], + "links": [ + { + "href": "https://icann.org/epp" + } + ] + }, + { + "title": "RDDS Inaccuracy Complaint Form", + "description": [ + "URL of the ICANN RDDS Inaccuracy Complaint Form: https://www.icann.org/wicf/" + ], + "links": [ + { + "href": "https://www.icann.org/wicf/" + } + ] + }, + { + "title": "Terms of service", + "description": [ + "The WHOIS information provided in this page has been redacted", + "in compliance with ICANN's Temporary Specification for gTLD", + "Registration Data.", + "", + "The data in this record is provided by Uniregistry for informational", + "purposes only, and it does not guarantee its accuracy. Uniregistry is", + "authoritative for whois information in top-level domains it operates", + "under contract with the Internet Corporation for Assigned Names and", + "Numbers. Whois information from other top-level domains is provided by", + "a third-party under license to Uniregistry.", + "", + "This service is intended only for query-based access. By using this", + "service, you agree that you will use any data presented only for lawful", + "purposes and that, under no circumstances will you use (a) data", + "acquired for the purpose of allowing, enabling, or otherwise supporting", + "the transmission by e-mail, telephone, facsimile or other", + "communications mechanism of mass unsolicited, commercial advertising", + "or solicitations to entities other than your existing customers; or", + "(b) this service to enable high volume, automated, electronic processes", + "that send queries or data to the systems of any Registrar or any", + "Registry except as reasonably necessary to register domain names or", + "modify existing domain name registrations.", + "", + "Uniregistry reserves the right to modify these terms at any time. By", + "submitting this query, you agree to abide by this policy. All rights", + "reserved.", + "" + ], + "links": [ + { + "href": "https://whois.uniregistry.net/" + } + ] + } + ], + "objectClassName": "domain", + "handle": "DO_a7aec7e93f5797ee898b23cefe340fe3-UR", + "events": [ + { + "eventAction": "registration", + "eventActor": "markmonitor", + "eventDate": "2014-11-12T19:15:55.283Z" + }, + { + "eventAction": "expiration", + "eventDate": "2024-11-12T19:15:55.283Z" + }, + { + "eventAction": "last changed", + "eventDate": "2023-10-17T10:47:21.733Z" + }, + { + "eventAction": "last update of RDAP database", + "eventDate": "2024-06-18T14:25:27.257Z" + } + ], + "status": [ + "client update prohibited", + "client transfer prohibited", + "client delete prohibited" + ], + "entities": [ + { + "objectClassName": "entity", + "remarks": [ + { + "title": "REDACTED FOR PRIVACY", + "description": [ + "Some of the data in this object has been removed" + ] + }, + { + "title": "EMAIL REDACTED FOR PRIVACY", + "description": [ + "Please query the RDDS service of the Registrar of Record identified in this output for information on how to contact the Registrant, Admin, or Tech contact of the queried domain name." + ] + } + ], + "events": [ + { + "eventAction": "last update of RDAP database", + "eventDate": "2024-06-18T14:30:59.585Z" + } + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "org", + {}, + "text", + "Microsoft Corporation" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + "", + "", + "WA", + "", + "US" + ] + ] + ] + ], + "roles": [ + "registrant" + ] + }, + { + "objectClassName": "entity", + "remarks": [ + { + "title": "REDACTED FOR PRIVACY", + "description": [ + "Some of the data in this object has been removed" + ] + }, + { + "title": "EMAIL REDACTED FOR PRIVACY", + "description": [ + "Please query the RDDS service of the Registrar of Record identified in this output for information on how to contact the Registrant, Admin, or Tech contact of the queried domain name." + ] + } + ], + "events": [ + { + "eventAction": "last update of RDAP database", + "eventDate": "2024-06-18T14:30:59.585Z" + } + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + "", + "", + "" + ] + ] + ] + ], + "roles": [ + "administrative" + ] + }, + { + "objectClassName": "entity", + "remarks": [ + { + "title": "REDACTED FOR PRIVACY", + "description": [ + "Some of the data in this object has been removed" + ] + }, + { + "title": "EMAIL REDACTED FOR PRIVACY", + "description": [ + "Please query the RDDS service of the Registrar of Record identified in this output for information on how to contact the Registrant, Admin, or Tech contact of the queried domain name." + ] + } + ], + "events": [ + { + "eventAction": "last update of RDAP database", + "eventDate": "2024-06-18T14:30:59.585Z" + } + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + "", + "", + "" + ] + ] + ] + ], + "roles": [ + "technical" + ] + }, + { + "objectClassName": "entity", + "handle": "292", + "events": [ + { + "eventAction": "last update of RDAP database", + "eventDate": "2024-06-18T14:30:59.585Z" + } + ], + "entities": [ + { + "objectClassName": "entity", + "handle": "CO_03a1e5a41de9801039c48cce9ea7414f-UR", + "events": [ + { + "eventAction": "last update of RDAP database", + "eventDate": "2024-06-18T14:30:59.585Z" + } + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Markmonitor" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + [ + "2150 S Bonito Way, Suite 150", + "", + "" + ], + "Meridian", + "ID", + "83642", + "US" + ] + ], + [ + "tel", + { + "type": "voice" + }, + "uri", + "tel:+1.2083895740" + ], + [ + "tel", + { + "type": "fax" + }, + "uri", + "tel:+1.2083895771" + ], + [ + "email", + {}, + "text", + "abusecomplaints@markmonitor.com" + ] + ] + ], + "roles": [ + "abuse" + ] + } + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "MarkMonitor Inc." + ], + [ + "adr", + {}, + "text", + [ + "", + "", + "3540 East Longwing Lane, Suite 300", + "Meridian", + "ID", + "83646", + "US" + ] + ], + [ + "tel", + { + "type": "voice" + }, + "uri", + "tel:+1.208389574" + ], + [ + "tel", + { + "type": "fax" + }, + "uri", + "tel:+1.2083895771" + ], + [ + "email", + {}, + "text", + "ccops@markmonitor.com" + ] + ] + ], + "roles": [ + "registrar" + ] + } + ], + "ldhName": "microsoft.click", + "unicodeName": "microsoft.click", + "secureDNS": { + "delegationSigned": false + }, + "nameservers": [ + { + "objectClassName": "nameserver", + "handle": "HO_6d2f0b70100a174318954d7e2af08b36-UR", + "events": [ + { + "eventAction": "last update of RDAP database", + "eventDate": "2024-06-18T14:30:59.585Z" + } + ], + "status": [ + "associated" + ], + "ldhName": "ns4-08.azure-dns.info", + "unicodeName": "ns4-08.azure-dns.info" + }, + { + "objectClassName": "nameserver", + "handle": "HO_a639e2ec1bd022f8dcf45d00dfc1cf7d-UR", + "events": [ + { + "eventAction": "last update of RDAP database", + "eventDate": "2024-06-18T14:30:59.585Z" + } + ], + "status": [ + "associated" + ], + "ldhName": "ns2-08.azure-dns.net", + "unicodeName": "ns2-08.azure-dns.net" + }, + { + "objectClassName": "nameserver", + "handle": "HO_bcdf18efa72577e5ac61514bce694770-UR", + "events": [ + { + "eventAction": "last update of RDAP database", + "eventDate": "2024-06-18T14:30:59.585Z" + } + ], + "status": [ + "associated" + ], + "ldhName": "ns3-08.azure-dns.org", + "unicodeName": "ns3-08.azure-dns.org" + }, + { + "objectClassName": "nameserver", + "handle": "HO_8fa40a46321cfbe1b88b1590e5bd9cea-UR", + "events": [ + { + "eventAction": "last update of RDAP database", + "eventDate": "2024-06-18T14:30:59.585Z" + } + ], + "status": [ + "associated" + ], + "ldhName": "ns1-08.azure-dns.com", + "unicodeName": "ns1-08.azure-dns.com" + } + ] +} diff --git a/icann-rdap-common/Cargo.toml b/icann-rdap-common/Cargo.toml new file mode 100644 index 0000000..28fa2ef --- /dev/null +++ b/icann-rdap-common/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "icann-rdap-common" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +description = """ +Common RDAP data structures. +""" + +[dependencies] +chrono.workspace = true +cidr.workspace = true +const_format.workspace = true +buildstructor.workspace = true +idna.workspace = true +ipnet.workspace = true +prefix-trie.workspace = true +serde.workspace = true +serde_json.workspace = true +strum.workspace = true +strum_macros.workspace = true +thiserror.workspace = true + +[dev-dependencies] + +# fixture testings +rstest = "0.17.0" diff --git a/icann-rdap-common/README.md b/icann-rdap-common/README.md new file mode 100644 index 0000000..7622a87 --- /dev/null +++ b/icann-rdap-common/README.md @@ -0,0 +1,177 @@ +ICANN RDAP Common +================= + +This is a common component library for the Registration Data Access Protocol (RDAP) written and sponsored +by the Internet Corporation for Assigned Names and Numbers [(ICANN)](https://www.icann.org). +RDAP is standard of the [IETF](https://ietf.org/), and extensions +to RDAP are a current work activity of the IETF's [REGEXT working group](https://datatracker.ietf.org/wg/regext/documents/). +More information on ICANN's role in RDAP can be found [here](https://www.icann.org/rdap). +General information on RDAP can be found [here](https://rdap.rcode3.com/). + + +Installation +------------ + +Add the library to your Cargo.toml: `cargo add icann-rdap-common`. + +This library can be compiled for WASM targets. + +Usage +----- + +Create some RDAP objects: + +```rust +// create an entity +use icann_rdap_common::response::Entity; +let holder = Entity::builder().handle("foo-BAR").build(); + +// create an RDAP domain +use icann_rdap_common::response::Domain; +let domain = Domain::builder().ldh_name("example.com").entity(holder.clone()).build(); + +// create an IP network +use icann_rdap_common::response::Network; +let net = Network::builder().cidr("10.0.0.0/16").entity(holder.clone()).build().unwrap(); + +// create a nameserver +use icann_rdap_common::response::Nameserver; +let ns = Nameserver::builder().ldh_name("ns1.example.com").entity(holder.clone()).build().unwrap(); + +// create an autnum +use icann_rdap_common::response::Autnum; +let autnum = Autnum::builder().autnum_range(700..700).entity(holder).build(); +``` + +Parse RDAP JSON: + +```rust +use icann_rdap_common::prelude::*; + +let json = r#" + { + "objectClassName": "ip network", + "links": [ + { + "value": "http://localhost:3000/rdap/ip/10.0.0.0/16", + "rel": "self", + "href": "http://localhost:3000/rdap/ip/10.0.0.0/16", + "type": "application/rdap+json" + } + ], + "events": [ + { + "eventAction": "registration", + "eventDate": "2023-06-16T22:56:49.594173356+00:00" + }, + { + "eventAction": "last changed", + "eventDate": "2023-06-16T22:56:49.594189140+00:00" + } + ], + "startAddress": "10.0.0.0", + "endAddress": "10.0.255.255", + "ipVersion": "v4" + } +"#; + +let rdap: RdapResponse = serde_json::from_str(json).unwrap(); +assert!(matches!(rdap, RdapResponse::Network(_))); +``` + +RDAP uses jCard, the JSON version of vCard, to model "contact information" +(e.g. postal addresses, phone numbers, etc...). Because jCard is difficult +to use and there might be other contact models standardized by the IETF, +this library includes the [`contact::Contact`] struct. This struct can be +converted to and from jCard/vCard with the [`contact::Contact::from_vcard`] +and [`contact::Contact::to_vcard`] functions. + +[`contact::Contact`] structs can be built using the builder. + +```rust +use icann_rdap_common::contact::Contact; + +let contact = Contact::builder() + .kind("individual") + .full_name("Bob Smurd") + .build(); +``` + +Once built, a Contact struct can be converted to an array of [serde_json::Value]'s, +which can be used with serde to serialize to JSON. + +```rust +use icann_rdap_common::contact::Contact; +use serde::Serialize; +use serde_json::Value; + +let contact = Contact::builder() + .kind("individual") + .full_name("Bob Smurd") + .build(); + +let v = contact.to_vcard(); +let json = serde_json::to_string(&v); +``` + +To deserialize, use the `from_vcard` function. + +```rust +use icann_rdap_common::contact::Contact; +use serde::Deserialize; +use serde_json::Value; + +let json = r#" +[ + "vcard", + [ + ["version", {}, "text", "4.0"], + ["fn", {}, "text", "Joe User"], + ["kind", {}, "text", "individual"], + ["org", { + "type":"work" + }, "text", "Example"], + ["title", {}, "text", "Research Scientist"], + ["role", {}, "text", "Project Lead"], + ["adr", + { "type":"work" }, + "text", + [ + "", + "Suite 1234", + "4321 Rue Somewhere", + "Quebec", + "QC", + "G1V 2M2", + "Canada" + ] + ], + ["tel", + { "type":["work", "voice"], "pref":"1" }, + "uri", "tel:+1-555-555-1234;ext=102" + ], + ["email", + { "type":"work" }, + "text", "joe.user@example.com" + ] + ] +]"#; + +let data: Vec = serde_json::from_str(json).unwrap(); +let contact = Contact::from_vcard(&data); +``` + +License +------- + +Licensed under either of +* Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) +* MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT) at your option. + +Contribution +------------ + +Unless you explicitly state otherwise, any contribution, as defined in the Apache-2.0 license, +intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, +shall be dual licensed pursuant to the Apache License, Version 2.0 or the MIT License referenced +as above, at ICANN’s option, without any additional terms or conditions. diff --git a/icann-rdap-common/src/check/autnum.rs b/icann-rdap-common/src/check/autnum.rs new file mode 100644 index 0000000..62b627a --- /dev/null +++ b/icann-rdap-common/src/check/autnum.rs @@ -0,0 +1,192 @@ +use std::any::TypeId; + +use crate::response::autnum::Autnum; + +use super::{ + string::StringCheck, Check, CheckParams, Checks, GetChecks, GetSubChecks, RdapStructure, +}; + +impl GetChecks for Autnum { + fn get_checks(&self, params: CheckParams) -> super::Checks { + let sub_checks = if params.do_subchecks { + let mut sub_checks: Vec = self + .common + .get_sub_checks(params.from_parent(TypeId::of::())); + sub_checks.append( + &mut self + .object_common + .get_sub_checks(params.from_parent(TypeId::of::())), + ); + sub_checks + } else { + vec![] + }; + + let mut items = vec![]; + + if self.start_autnum.is_none() || self.end_autnum.is_none() { + items.push(Check::AutnumMissing.check_item()) + } + + if let Some(start_num) = &self.start_autnum.as_ref().and_then(|n| n.as_u32()) { + if let Some(end_num) = &self.end_autnum.as_ref().and_then(|n| n.as_u32()) { + if start_num > end_num { + items.push(Check::AutnumEndBeforeStart.check_item()) + } + if is_autnum_reserved(*start_num) || is_autnum_reserved(*end_num) { + items.push(Check::AutnumReserved.check_item()) + } + if is_autnum_documentation(*start_num) || is_autnum_documentation(*end_num) { + items.push(Check::AutnumDocumentation.check_item()) + } + if is_autnum_private_use(*start_num) || is_autnum_private_use(*end_num) { + items.push(Check::AutnumPrivateUse.check_item()) + } + } + } + + if let Some(name) = &self.name { + if name.is_whitespace_or_empty() { + items.push(Check::NetworkOrAutnumNameIsEmpty.check_item()) + } + } + + if let Some(autnum_type) = &self.autnum_type { + if autnum_type.is_whitespace_or_empty() { + items.push(Check::NetworkOrAutnumTypeIsEmpty.check_item()) + } + } + + Checks { + rdap_struct: RdapStructure::Autnum, + items, + sub_checks, + } + } +} + +/// Returns true if the autnum is reserved. +pub fn is_autnum_reserved(autnum: u32) -> bool { + autnum == 0 || autnum == 65535 || autnum == 4294967295 || (65552..=131071).contains(&autnum) +} + +/// Returns true if the autnum is for documentation. +pub fn is_autnum_documentation(autnum: u32) -> bool { + (64496..=64511).contains(&autnum) || (65536..=65551).contains(&autnum) +} + +/// Returns true if the autnum is private use. +pub fn is_autnum_private_use(autnum: u32) -> bool { + (64512..=65534).contains(&autnum) || (4200000000..=4294967294).contains(&autnum) +} + +#[cfg(test)] +mod tests { + + use rstest::rstest; + + use crate::prelude::ToResponse; + + use crate::{ + check::{Check, CheckParams, GetChecks}, + response::autnum::Autnum, + }; + + use super::*; + + #[test] + fn check_autnum_with_empty_name() { + // GIVEN + let mut autnum = Autnum::builder().autnum_range(700..700).build(); + autnum.name = Some("".to_string()); + let rdap = autnum.to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(checks + .items + .iter() + .any(|c| c.check == Check::NetworkOrAutnumNameIsEmpty)); + } + + #[test] + fn check_autnum_with_empty_type() { + // GIVEN + let mut autnum = Autnum::builder().autnum_range(700..700).build(); + autnum.autnum_type = Some("".to_string()); + let rdap = autnum.to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(checks + .items + .iter() + .any(|c| c.check == Check::NetworkOrAutnumTypeIsEmpty)); + } + + #[rstest] + #[case(0, true)] + #[case(65535, true)] + #[case(65552, true)] + #[case(131071, true)] + #[case(4294967295, true)] + #[case(1, false)] + #[case(65534, false)] + #[case(65551, false)] + #[case(131072, false)] + #[case(4294967294, false)] + fn check_autnum_is_reserved(#[case] autnum: u32, #[case] expected: bool) { + // GIVEN in parameters + + // WHEN + let actual = is_autnum_reserved(autnum); + + // THEN + assert_eq!(actual, expected); + } + + #[rstest] + #[case(64496, true)] + #[case(64511, true)] + #[case(65536, true)] + #[case(65551, true)] + #[case(64495, false)] + #[case(64512, false)] + #[case(65535, false)] + #[case(65552, false)] + fn check_autnum_is_documentation(#[case] autnum: u32, #[case] expected: bool) { + // GIVEN in parameters + + // WHEN + let actual = is_autnum_documentation(autnum); + + // THEN + assert_eq!(actual, expected); + } + + #[rstest] + #[case(64512, true)] + #[case(65534, true)] + #[case(4200000000, true)] + #[case(4294967294, true)] + #[case(65534, true)] + #[case(64511, false)] + #[case(65535, false)] + #[case(4199999999, false)] + #[case(4294967295, false)] + fn check_autnum_is_private_use(#[case] autnum: u32, #[case] expected: bool) { + // GIVEN in parameters + + // WHEN + let actual = is_autnum_private_use(autnum); + + // THEN + assert_eq!(actual, expected); + } +} diff --git a/icann-rdap-common/src/check/domain.rs b/icann-rdap-common/src/check/domain.rs new file mode 100644 index 0000000..2d957fa --- /dev/null +++ b/icann-rdap-common/src/check/domain.rs @@ -0,0 +1,648 @@ +use std::any::TypeId; + +use crate::response::domain::{Domain, SecureDns}; + +use super::{string::StringCheck, Check, CheckParams, Checks, GetChecks, GetSubChecks}; + +impl GetChecks for Domain { + fn get_checks(&self, params: CheckParams) -> super::Checks { + let sub_checks = if params.do_subchecks { + let mut sub_checks: Vec = self + .common + .get_sub_checks(params.from_parent(TypeId::of::())); + sub_checks.append( + &mut self + .object_common + .get_sub_checks(params.from_parent(TypeId::of::())), + ); + if let Some(public_ids) = &self.public_ids { + sub_checks.append(&mut public_ids.get_sub_checks(params)); + } + if let Some(secure_dns) = &self.secure_dns { + sub_checks.append(&mut secure_dns.get_sub_checks(params)); + } + sub_checks + } else { + vec![] + }; + + let mut items = vec![]; + + // check variants + if let Some(variants) = &self.variants { + let empty_count = variants + .iter() + .filter(|v| { + v.relations.is_none() && v.idn_table.is_none() && v.variant_names.is_none() + }) + .count(); + if empty_count != 0 { + items.push(Check::VariantEmptyDomain.check_item()); + }; + }; + + // check ldh + if let Some(ldh) = &self.ldh_name { + if !ldh.is_ldh_domain_name() { + items.push(Check::LdhNameInvalid.check_item()); + } + let name = ldh.trim_end_matches('.'); + if name.eq("example") + || name.ends_with(".example") + || name.eq("example.com") + || name.ends_with(".example.com") + || name.eq("example.net") + || name.ends_with(".example.net") + || name.eq("example.org") + || name.ends_with(".example.org") + { + items.push(Check::LdhNameDocumentation.check_item()) + } + + // if there is also a unicodeName + if let Some(unicode_name) = &self.unicode_name { + let expected = idna::domain_to_ascii(unicode_name); + if let Ok(expected) = expected { + if !expected.eq_ignore_ascii_case(ldh) { + items.push(Check::LdhNameDoesNotMatchUnicode.check_item()) + } + } + } + } + + // check unicode_name + if let Some(unicode_name) = &self.unicode_name { + if !unicode_name.is_unicode_domain_name() { + items.push(Check::UnicodeNameInvalidDomain.check_item()); + } + let expected = idna::domain_to_ascii(unicode_name); + if expected.is_err() { + items.push(Check::UnicodeNameInvalidUnicode.check_item()); + } + } + + Checks { + rdap_struct: super::RdapStructure::Domain, + items, + sub_checks, + } + } +} + +impl GetSubChecks for SecureDns { + fn get_sub_checks(&self, _params: CheckParams) -> Vec { + let mut sub_checks = Vec::new(); + if let Some(delegation_signed) = &self.delegation_signed { + if delegation_signed.is_string() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::SecureDns, + items: vec![Check::DelegationSignedIsString.check_item()], + sub_checks: vec![], + }); + } + } + if let Some(zone_signed) = &self.zone_signed { + if zone_signed.is_string() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::SecureDns, + items: vec![Check::ZoneSignedIsString.check_item()], + sub_checks: vec![], + }); + } + } + if let Some(max_sig_life) = &self.max_sig_life { + if max_sig_life.is_string() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::SecureDns, + items: vec![Check::MaxSigLifeIsString.check_item()], + sub_checks: vec![], + }); + } + } + + if let Some(key_data) = &self.key_data { + for key_datum in key_data { + if let Some(alg) = &key_datum.algorithm { + if alg.is_string() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::SecureDns, + items: vec![Check::KeyDatumAlgorithmIsString.check_item()], + sub_checks: vec![], + }); + } + if alg.as_u8().is_none() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::SecureDns, + items: vec![Check::KeyDatumAlgorithmIsOutOfRange.check_item()], + sub_checks: vec![], + }); + } + } + if let Some(flags) = &key_datum.flags { + if flags.is_string() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::SecureDns, + items: vec![Check::KeyDatumFlagsIsString.check_item()], + sub_checks: vec![], + }); + } + if flags.as_u16().is_none() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::SecureDns, + items: vec![Check::KeyDatumFlagsIsOutOfRange.check_item()], + sub_checks: vec![], + }); + } + } + if let Some(protocol) = &key_datum.protocol { + if protocol.is_string() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::SecureDns, + items: vec![Check::KeyDatumProtocolIsString.check_item()], + sub_checks: vec![], + }); + } + if protocol.as_u8().is_none() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::SecureDns, + items: vec![Check::KeyDatumProtocolIsOutOfRange.check_item()], + sub_checks: vec![], + }); + } + } + } + } + + if let Some(ds_data) = &self.ds_data { + for ds_datum in ds_data { + if let Some(alg) = &ds_datum.algorithm { + if alg.is_string() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::SecureDns, + items: vec![Check::DsDatumAlgorithmIsString.check_item()], + sub_checks: vec![], + }); + } + if alg.as_u8().is_none() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::SecureDns, + items: vec![Check::DsDatumAlgorithmIsOutOfRange.check_item()], + sub_checks: vec![], + }); + } + } + if let Some(key_tag) = &ds_datum.key_tag { + if key_tag.is_string() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::SecureDns, + items: vec![Check::DsDatumKeyTagIsString.check_item()], + sub_checks: vec![], + }); + } + if key_tag.as_u32().is_none() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::SecureDns, + items: vec![Check::DsDatumKeyTagIsOutOfRange.check_item()], + sub_checks: vec![], + }); + } + } + if let Some(digest_type) = &ds_datum.digest_type { + if digest_type.is_string() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::SecureDns, + items: vec![Check::DsDatumDigestTypeIsString.check_item()], + sub_checks: vec![], + }); + } + if digest_type.as_u8().is_none() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::SecureDns, + items: vec![Check::DsDatumDigestTypeIsOutOfRange.check_item()], + sub_checks: vec![], + }); + } + } + } + } + + sub_checks + } +} + +#[cfg(test)] +mod tests { + use std::any::TypeId; + + use { + crate::{ + check::{is_checked, is_checked_item, GetSubChecks}, + prelude::ToResponse, + response::domain::{Domain, SecureDns}, + }, + rstest::rstest, + }; + + use crate::check::{Check, CheckParams, GetChecks}; + + #[rstest] + #[case("")] + #[case(" ")] + #[case("_.")] + fn test_check_for_bad_ldh(#[case] ldh: &str) { + // GIVEN + let domain = Domain::builder().ldh_name(ldh).build(); + let rdap = domain.to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(is_checked_item(Check::LdhNameInvalid, &checks)); + } + + #[rstest] + #[case("")] + #[case(" ")] + fn test_check_for_bad_unicode(#[case] unicode: &str) { + // GIVEN + let domain = Domain::idn().unicode_name(unicode).build(); + let rdap = domain.to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(is_checked_item(Check::UnicodeNameInvalidDomain, &checks)); + } + + #[test] + fn test_check_for_ldh_unicode_mismatch() { + // GIVEN + let domain = Domain::idn() + .unicode_name("foo.com") + .ldh_name("xn--foo.com") + .build(); + let rdap = domain.to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(is_checked_item(Check::LdhNameDoesNotMatchUnicode, &checks)); + } + + #[test] + fn test_delegation_signed_as_string() { + // GIVEN + let secure_dns = serde_json::from_str::( + r#"{ + "delegationSigned": "true" + }"#, + ) + .unwrap(); + + // WHEN + let checks = secure_dns.get_sub_checks(CheckParams { + do_subchecks: false, + root: &Domain::builder() + .ldh_name("example.com") + .build() + .to_response(), + parent_type: TypeId::of::(), + allow_unreg_ext: false, + }); + + // THEN + assert_eq!(checks.len(), 1); + assert!(is_checked(Check::DelegationSignedIsString, &checks)); + } + + #[test] + fn test_delegation_signed_as_bool() { + // GIVEN + let secure_dns = serde_json::from_str::( + r#"{ + "delegationSigned": true + }"#, + ) + .unwrap(); + + // WHEN + let checks = secure_dns.get_sub_checks(CheckParams { + do_subchecks: false, + root: &Domain::builder() + .ldh_name("example.com") + .build() + .to_response(), + parent_type: TypeId::of::(), + allow_unreg_ext: false, + }); + + // THEN + assert!(checks.is_empty()); + } + + #[test] + fn test_zone_signed_as_string() { + // GIVEN + let secure_dns = serde_json::from_str::( + r#"{ + "zoneSigned": "false" + }"#, + ) + .unwrap(); + + // WHEN + let checks = secure_dns.get_sub_checks(CheckParams { + do_subchecks: false, + root: &Domain::builder() + .ldh_name("example.com") + .build() + .to_response(), + parent_type: TypeId::of::(), + allow_unreg_ext: false, + }); + + // THEN + assert_eq!(checks.len(), 1); + assert!(is_checked(Check::ZoneSignedIsString, &checks)); + } + + #[test] + fn test_zone_signed_as_bool() { + // GIVEN + let secure_dns = serde_json::from_str::( + r#"{ + "zoneSigned": true + }"#, + ) + .unwrap(); + + // WHEN + let checks = secure_dns.get_sub_checks(CheckParams { + do_subchecks: false, + root: &Domain::builder() + .ldh_name("example.com") + .build() + .to_response(), + parent_type: TypeId::of::(), + allow_unreg_ext: false, + }); + + // THEN + assert!(checks.is_empty()); + } + + #[test] + fn test_max_sig_life_as_string() { + // GIVEN + let secure_dns = serde_json::from_str::( + r#"{ + "maxSigLife": "123" + }"#, + ) + .unwrap(); + + // WHEN + let checks = secure_dns.get_sub_checks(CheckParams { + do_subchecks: false, + root: &Domain::builder() + .ldh_name("example.com") + .build() + .to_response(), + parent_type: TypeId::of::(), + allow_unreg_ext: false, + }); + + // THEN + assert_eq!(checks.len(), 1); + assert!(is_checked(Check::MaxSigLifeIsString, &checks)); + } + + #[test] + fn test_max_sig_life_as_number() { + // GIVEN + let secure_dns = serde_json::from_str::( + r#"{ + "maxSigLife": 123 + }"#, + ) + .unwrap(); + + // WHEN + let checks = secure_dns.get_sub_checks(CheckParams { + do_subchecks: false, + root: &Domain::builder() + .ldh_name("example.com") + .build() + .to_response(), + parent_type: TypeId::of::(), + allow_unreg_ext: false, + }); + + // THEN + assert!(checks.is_empty()); + } + + #[test] + fn test_key_data_attributes_as_string() { + // GIVEN + let secure_dns = serde_json::from_str::( + r#"{ + "keyData": [ + { + "algorithm": "13", + "flags": "13", + "protocol": "13" + } + ] + }"#, + ) + .unwrap(); + + // WHEN + let checks = secure_dns.get_sub_checks(CheckParams { + do_subchecks: false, + root: &Domain::builder() + .ldh_name("example.com") + .build() + .to_response(), + parent_type: TypeId::of::(), + allow_unreg_ext: false, + }); + + // THEN + assert_eq!(checks.len(), 3); + assert!(is_checked(Check::KeyDatumAlgorithmIsString, &checks)); + assert!(is_checked(Check::KeyDatumFlagsIsString, &checks)); + assert!(is_checked(Check::KeyDatumProtocolIsString, &checks)); + } + + #[test] + fn test_key_data_attributes_as_number() { + // GIVEN + let secure_dns = serde_json::from_str::( + r#"{ + "keyData": [ + { + "algorithm": 13, + "flags": 13, + "protocol": 13 + } + ] + }"#, + ) + .unwrap(); + + // WHEN + let checks = secure_dns.get_sub_checks(CheckParams { + do_subchecks: false, + root: &Domain::builder() + .ldh_name("example.com") + .build() + .to_response(), + parent_type: TypeId::of::(), + allow_unreg_ext: false, + }); + + // THEN + assert!(checks.is_empty()); + } + + #[test] + fn test_key_data_attributes_out_of_range() { + // GIVEN + let secure_dns = serde_json::from_str::( + r#"{ + "keyData": [ + { + "algorithm": 1300, + "flags": 130000, + "protocol": 1300 + } + ] + }"#, + ) + .unwrap(); + + // WHEN + let checks = secure_dns.get_sub_checks(CheckParams { + do_subchecks: false, + root: &Domain::builder() + .ldh_name("example.com") + .build() + .to_response(), + parent_type: TypeId::of::(), + allow_unreg_ext: false, + }); + + // THEN + assert_eq!(checks.len(), 3); + assert!(is_checked(Check::KeyDatumAlgorithmIsOutOfRange, &checks)); + assert!(is_checked(Check::KeyDatumFlagsIsOutOfRange, &checks)); + assert!(is_checked(Check::KeyDatumProtocolIsOutOfRange, &checks)); + } + + #[test] + fn test_ds_data_attributes_as_string() { + // GIVEN + let secure_dns = serde_json::from_str::( + r#"{ + "dsData": [ + { + "algorithm": "13", + "keyTag": "13", + "digestType": "13" + } + ] + }"#, + ) + .unwrap(); + + // WHEN + let checks = secure_dns.get_sub_checks(CheckParams { + do_subchecks: false, + root: &Domain::builder() + .ldh_name("example.com") + .build() + .to_response(), + parent_type: TypeId::of::(), + allow_unreg_ext: false, + }); + + // THEN + assert_eq!(checks.len(), 3); + assert!(is_checked(Check::DsDatumAlgorithmIsString, &checks)); + assert!(is_checked(Check::DsDatumKeyTagIsString, &checks)); + assert!(is_checked(Check::DsDatumDigestTypeIsString, &checks)); + } + + #[test] + fn test_ds_data_attributes_as_number() { + // GIVEN + let secure_dns = serde_json::from_str::( + r#"{ + "dsData": [ + { + "algorithm": 13, + "keyTag": 13, + "digestType": 13 + } + ] + }"#, + ) + .unwrap(); + + // WHEN + let checks = secure_dns.get_sub_checks(CheckParams { + do_subchecks: false, + root: &Domain::builder() + .ldh_name("example.com") + .build() + .to_response(), + parent_type: TypeId::of::(), + allow_unreg_ext: false, + }); + + // THEN + assert!(checks.is_empty()); + } + + #[test] + fn test_ds_data_attributes_out_of_range() { + // GIVEN + let secure_dns = serde_json::from_str::( + r#"{ + "dsData": [ + { + "algorithm": 1300, + "keyTag": 13000000000, + "digestType": 1300 + } + ] + }"#, + ) + .unwrap(); + + // WHEN + let checks = secure_dns.get_sub_checks(CheckParams { + do_subchecks: false, + root: &Domain::builder() + .ldh_name("example.com") + .build() + .to_response(), + parent_type: TypeId::of::(), + allow_unreg_ext: false, + }); + + // THEN + assert_eq!(checks.len(), 3); + assert!(is_checked(Check::DsDatumAlgorithmIsOutOfRange, &checks)); + assert!(is_checked(Check::DsDatumKeyTagIsOutOfRange, &checks)); + assert!(is_checked(Check::DsDatumDigestTypeIsOutOfRange, &checks)); + } +} diff --git a/icann-rdap-common/src/check/entity.rs b/icann-rdap-common/src/check/entity.rs new file mode 100644 index 0000000..8237caf --- /dev/null +++ b/icann-rdap-common/src/check/entity.rs @@ -0,0 +1,71 @@ +use std::{any::TypeId, str::FromStr}; + +use crate::{ + contact::Contact, + response::entity::{Entity, EntityRole}, +}; + +use super::{ + string::{StringCheck, StringListCheck}, + Check, CheckParams, Checks, GetChecks, GetSubChecks, RdapStructure, +}; + +impl GetChecks for Entity { + fn get_checks(&self, params: CheckParams) -> super::Checks { + let sub_checks = if params.do_subchecks { + let mut sub_checks: Vec = self + .common + .get_sub_checks(params.from_parent(TypeId::of::())); + sub_checks.append( + &mut self + .object_common + .get_sub_checks(params.from_parent(TypeId::of::())), + ); + if let Some(public_ids) = &self.public_ids { + sub_checks.append(&mut public_ids.get_sub_checks(params)); + } + sub_checks + } else { + vec![] + }; + + let mut items = vec![]; + + if let Some(roles) = &self.roles { + if roles.is_string() { + items.push(Check::RoleIsString.check_item()); + } + let roles = roles.vec(); + if roles.is_empty_or_any_empty_or_whitespace() { + items.push(Check::RoleIsEmpty.check_item()); + } else { + for role in roles { + let r = EntityRole::from_str(role); + if r.is_err() { + items.push(Check::UnknownRole.check_item()); + } + } + } + } + + if let Some(vcard) = &self.vcard_array { + if let Some(contact) = Contact::from_vcard(vcard) { + if let Some(full_name) = contact.full_name { + if full_name.is_whitespace_or_empty() { + items.push(Check::VcardFnIsEmpty.check_item()) + } + } else { + items.push(Check::VcardHasNoFn.check_item()) + } + } else { + items.push(Check::VcardArrayIsEmpty.check_item()) + } + } + + Checks { + rdap_struct: RdapStructure::Entity, + items, + sub_checks, + } + } +} diff --git a/icann-rdap-common/src/check/error.rs b/icann-rdap-common/src/check/error.rs new file mode 100644 index 0000000..456dbb3 --- /dev/null +++ b/icann-rdap-common/src/check/error.rs @@ -0,0 +1,23 @@ +use std::any::TypeId; + +use crate::response::error::Rfc9083Error; + +use super::{CheckParams, Checks, GetChecks, GetSubChecks}; + +impl GetChecks for Rfc9083Error { + fn get_checks(&self, params: CheckParams) -> super::Checks { + let sub_checks = if params.do_subchecks { + let sub_checks: Vec = self + .common + .get_sub_checks(params.from_parent(TypeId::of::())); + sub_checks + } else { + vec![] + }; + Checks { + rdap_struct: super::RdapStructure::Error, + items: vec![], + sub_checks, + } + } +} diff --git a/icann-rdap-common/src/check/help.rs b/icann-rdap-common/src/check/help.rs new file mode 100644 index 0000000..fae8180 --- /dev/null +++ b/icann-rdap-common/src/check/help.rs @@ -0,0 +1,23 @@ +use std::any::TypeId; + +use crate::response::help::Help; + +use super::{CheckParams, Checks, GetChecks, GetSubChecks}; + +impl GetChecks for Help { + fn get_checks(&self, params: CheckParams) -> super::Checks { + let sub_checks = if params.do_subchecks { + let sub_checks: Vec = self + .common + .get_sub_checks(params.from_parent(TypeId::of::())); + sub_checks + } else { + vec![] + }; + Checks { + rdap_struct: super::RdapStructure::Help, + items: vec![], + sub_checks, + } + } +} diff --git a/icann-rdap-common/src/check/httpdata.rs b/icann-rdap-common/src/check/httpdata.rs new file mode 100644 index 0000000..387ec0b --- /dev/null +++ b/icann-rdap-common/src/check/httpdata.rs @@ -0,0 +1,346 @@ +use crate::{httpdata::HttpData, media_types::RDAP_MEDIA_TYPE, response::types::ExtensionId}; + +use super::{Check, Checks, GetChecks}; + +impl GetChecks for HttpData { + fn get_checks(&self, params: crate::check::CheckParams) -> crate::check::Checks { + let mut items = vec![]; + + // RFC checks + if let Some(allow_origin) = &self.access_control_allow_origin { + if !allow_origin.eq("*") { + items.push(Check::CorsAllowOriginStarRecommended.check_item()) + } + } else { + items.push(Check::CorsAllowOriginRecommended.check_item()) + } + if self.access_control_allow_credentials.is_some() { + items.push(Check::CorsAllowCredentialsNotRecommended.check_item()) + } + if let Some(content_type) = &self.content_type { + if !content_type.starts_with(RDAP_MEDIA_TYPE) { + items.push(Check::ContentTypeIsNotRdap.check_item()); + } + } else { + items.push(Check::ContentTypeIsAbsent.check_item()); + } + + // checks for ICANN profile + if params + .root + .has_extension_id(ExtensionId::IcannRdapTechnicalImplementationGuide0) + || params + .root + .has_extension_id(ExtensionId::IcannRdapTechnicalImplementationGuide1) + { + if let Some(scheme) = &self.scheme { + if !scheme.eq_ignore_ascii_case("HTTPS") { + items.push(Check::MustUseHttps.check_item()); + } + } else { + items.push(Check::MustUseHttps.check_item()); + } + if let Some(allow_origin) = &self.access_control_allow_origin { + if !allow_origin.eq("*") { + items.push(Check::AllowOriginNotStar.check_item()) + } + } else { + items.push(Check::AllowOriginNotStar.check_item()) + } + } + + Checks { + rdap_struct: super::RdapStructure::HttpData, + items, + sub_checks: vec![], + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + check::{Check, CheckParams, GetChecks}, + httpdata::HttpData, + media_types::{JSON_MEDIA_TYPE, RDAP_MEDIA_TYPE}, + prelude::{Common, ObjectCommon, ToResponse}, + response::{domain::Domain, types::ExtensionId}, + }; + + #[test] + fn check_not_rdap_media() { + // GIVEN an rdap response + let domain = Domain { + common: Common::level0() + .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension()) + .build(), + object_common: ObjectCommon::domain().build(), + ldh_name: Some("foo.example".to_string()), + unicode_name: None, + variants: None, + secure_dns: None, + nameservers: None, + public_ids: None, + network: None, + }; + let rdap = domain.to_response(); + + // and GIVEN httpdata with content type that is not RDAP media type + let http_data = HttpData::example().content_type(JSON_MEDIA_TYPE).build(); + + // WHEN checks are run + let checks = http_data.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN incorrect media type check is found + assert!(checks + .items + .iter() + .any(|c| c.check == Check::ContentTypeIsNotRdap)); + } + + #[test] + fn check_exactly_rdap_media() { + // GIVEN an rdap response + let domain = Domain { + common: Common::level0() + .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension()) + .build(), + object_common: ObjectCommon::domain().build(), + ldh_name: Some("foo.example".to_string()), + unicode_name: None, + variants: None, + secure_dns: None, + nameservers: None, + public_ids: None, + network: None, + }; + let rdap = domain.to_response(); + + // and GIVEN httpdata with content type that is not RDAP media type + let http_data = HttpData::example().content_type(RDAP_MEDIA_TYPE).build(); + + // WHEN checks are run + let checks = http_data.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN incorrect media type check is not found + assert!(!checks + .items + .iter() + .any(|c| c.check == Check::ContentTypeIsNotRdap)); + } + + #[test] + fn check_rdap_media_with_charset_parameter() { + // GIVEN an rdap response + let domain = Domain { + common: Common::level0() + .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension()) + .build(), + object_common: ObjectCommon::domain().build(), + ldh_name: Some("foo.example".to_string()), + unicode_name: None, + variants: None, + secure_dns: None, + nameservers: None, + public_ids: None, + network: None, + }; + let rdap = domain.to_response(); + + // and GIVEN httpdata with content type that is not RDAP media type with charset parameter + let mt = format!("{RDAP_MEDIA_TYPE};charset=UTF-8"); + let http_data = HttpData::example().content_type(mt).build(); + + // WHEN checks are run + let checks = http_data.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN incorrect media type check is not found + assert!(!checks + .items + .iter() + .any(|c| c.check == Check::ContentTypeIsNotRdap)); + } + + #[test] + fn check_media_type_absent() { + // GIVEN an rdap response + let domain = Domain { + common: Common::level0() + .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension()) + .build(), + object_common: ObjectCommon::domain().build(), + ldh_name: Some("foo.example".to_string()), + unicode_name: None, + variants: None, + secure_dns: None, + nameservers: None, + public_ids: None, + network: None, + }; + let rdap = domain.to_response(); + + // and GIVEN httpdata no content type + let http_data = HttpData::example().build(); + + // WHEN checks are run + let checks = http_data.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN no media type check is found + assert!(checks + .items + .iter() + .any(|c| c.check == Check::ContentTypeIsAbsent)); + } + + #[test] + fn check_cors_header_with_tig() { + // GIVEN a response with gtld tig + let domain = Domain { + common: Common::level0() + .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension()) + .build(), + object_common: ObjectCommon::domain().build(), + ldh_name: Some("foo.example".to_string()), + unicode_name: None, + variants: None, + secure_dns: None, + nameservers: None, + public_ids: None, + network: None, + }; + let rdap = domain.to_response(); + + // and GIVEN a cors header with * + let http_data = HttpData::example().access_control_allow_origin("*").build(); + + // WHEN running checks + let checks = http_data.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN no check is given + assert!(!checks + .items + .iter() + .any(|c| c.check == Check::AllowOriginNotStar)); + } + + #[test] + fn check_cors_header_with_foo_and_tig() { + // GIVEN a response with gtld tig extension + let domain = Domain { + common: Common::level0() + .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension()) + .build(), + object_common: ObjectCommon::domain().build(), + ldh_name: Some("foo.example".to_string()), + unicode_name: None, + variants: None, + secure_dns: None, + nameservers: None, + public_ids: None, + network: None, + }; + let rdap = domain.to_response(); + + // and GIVEN response with cors header of "foo" (not "*") + let http_data = HttpData::example() + .access_control_allow_origin("foo") + .build(); + + // WHEN running checks + let checks = http_data.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN the check is found + assert!(checks + .items + .iter() + .any(|c| c.check == Check::AllowOriginNotStar)); + } + + #[test] + fn check_no_cors_header_and_tig() { + // GIVEN domain response with gtld tig extension id + let domain = Domain { + common: Common::level0() + .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension()) + .build(), + object_common: ObjectCommon::domain().build(), + ldh_name: Some("foo.example".to_string()), + unicode_name: None, + variants: None, + secure_dns: None, + nameservers: None, + public_ids: None, + network: None, + }; + let rdap = domain.to_response(); + + // and GIVEN a response with no cors header + let http_data = HttpData::example().build(); + + // WHEN running checks + let checks = http_data.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN check for missing cors is found + dbg!(&checks); + assert!(checks + .items + .iter() + .any(|c| c.check == Check::AllowOriginNotStar)); + } + + #[test] + fn given_response_is_over_https_and_tig() { + // GIVEN response with gtld tig extension + let domain = Domain { + common: Common::level0() + .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension()) + .build(), + object_common: ObjectCommon::domain().build(), + ldh_name: Some("foo.example".to_string()), + unicode_name: None, + variants: None, + secure_dns: None, + nameservers: None, + public_ids: None, + network: None, + }; + let rdap = domain.to_response(); + + // and GIVEN response is over https + let http_data = HttpData::now().scheme("https").host("example.com").build(); + + // WHEN running checks + let checks = http_data.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN then check for must use https is not present + assert!(!checks.items.iter().any(|c| c.check == Check::MustUseHttps)); + } + + #[test] + fn response_over_htttp_and_tig() { + // GIVEN domain response with gtld tig extension + let domain = Domain { + common: Common::level0() + .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension()) + .build(), + object_common: ObjectCommon::domain().build(), + ldh_name: Some("foo.example".to_string()), + unicode_name: None, + variants: None, + secure_dns: None, + nameservers: None, + public_ids: None, + network: None, + }; + let rdap = domain.to_response(); + + // and GIVEN response is with http (not https) + let http_data = HttpData::now().scheme("http").host("example.com").build(); + + // WHEN running checks + let checks = http_data.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN check for must use https is found + assert!(checks.items.iter().any(|c| c.check == Check::MustUseHttps)); + } +} diff --git a/icann-rdap-common/src/check/mod.rs b/icann-rdap-common/src/check/mod.rs new file mode 100644 index 0000000..7ca23d7 --- /dev/null +++ b/icann-rdap-common/src/check/mod.rs @@ -0,0 +1,754 @@ +//! Conformance checks of RDAP structures. + +use std::{any::TypeId, sync::LazyLock}; + +use { + crate::response::RdapResponse, + serde::{Deserialize, Serialize}, + strum::{EnumMessage, IntoEnumIterator}, + strum_macros::{Display, EnumIter, EnumMessage, EnumString, FromRepr}, +}; + +#[doc(inline)] +pub use string::*; + +mod autnum; +mod domain; +mod entity; +mod error; +mod help; +mod httpdata; +mod nameserver; +mod network; +mod search; +mod string; +mod types; + +/// The max length of the check class string representations. +pub static CHECK_CLASS_LEN: LazyLock = LazyLock::new(|| { + CheckClass::iter() + .max_by_key(|x| x.to_string().len()) + .map_or(8, |x| x.to_string().len()) +}); + +/// Describes the classes of checks. +#[derive( + EnumIter, + EnumString, + Debug, + Display, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize, + Clone, + Copy, +)] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum CheckClass { + /// Informational + /// + /// This class represents informational items. + #[strum(serialize = "Info")] + Informational, + + /// Specification Note + /// + /// This class represents notes about the RDAP response with respect to + /// the various RDAP and RDAP related specifications. + #[strum(serialize = "SpecNote")] + SpecificationNote, + + /// STD 95 Warnings + /// + /// This class represents warnings that may cause some clients to be unable + /// to conduct some operations. + #[strum(serialize = "StdWarn")] + StdWarning, + + /// STD 95 Errors + /// + /// This class represetns errors in the RDAP with respect to STD 95. + #[strum(serialize = "StdErr")] + StdError, + + /// Cidr0 Errors + /// + /// This class represents errors with respect to CIDR0. + #[strum(serialize = "Cidr0Err")] + Cidr0Error, + + /// ICANN Profile Errors + /// + /// This class represents errors with respect to the gTLD RDAP profile. + #[strum(serialize = "IcannErr")] + IcannError, +} + +/// Represents the name of an RDAP structure for which a check appears. +/// +/// An RDAP data structure is not the same as a Rust struct in that RDAP +/// data structures may consist of arrays and sometimes structured data +/// within a string. +#[derive( + Debug, Serialize, Deserialize, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Display, EnumString, +)] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum RdapStructure { + Autnum, + Cidr0, + Domain, + DomainSearchResults, + Entity, + EntitySearchResults, + Events, + Error, + Help, + Handle, + HttpData, + IpNetwork, + Link, + Links, + Nameserver, + NameserverSearchResults, + NoticeOrRemark, + Notices, + PublidIds, + Port43, + RdapConformance, + Redacted, + Remarks, + SecureDns, + Status, +} + +/// Contains many [CheckItem] structures and sub checks. +/// +/// Checks are found on object classes and structures defined in [RdapStructure]. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, PartialOrd, Eq, Ord)] +pub struct Checks { + pub rdap_struct: RdapStructure, + pub items: Vec, + pub sub_checks: Vec, +} + +impl Checks { + pub fn sub(&self, rdap_struct: RdapStructure) -> Option<&Self> { + self.sub_checks + .iter() + .find(|check| check.rdap_struct == rdap_struct) + } +} + +/// A specific check item. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct CheckItem { + pub check_class: CheckClass, + pub check: Check, +} + +impl std::fmt::Display for CheckItem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!( + "{}:({:0>4}) {}", + self.check_class, + self.check as usize, + self.check + .get_message() + .unwrap_or("[Check has no description]"), + )) + } +} + +/// Trait for an item that can get checks. +pub trait GetChecks { + fn get_checks(&self, params: CheckParams) -> Checks; +} + +/// Parameters for finding checks. +#[derive(Clone, Copy)] +pub struct CheckParams<'a> { + pub do_subchecks: bool, + pub root: &'a RdapResponse, + pub parent_type: TypeId, + pub allow_unreg_ext: bool, +} + +impl CheckParams<'_> { + pub fn from_parent(&self, parent_type: TypeId) -> Self { + Self { + do_subchecks: self.do_subchecks, + root: self.root, + parent_type, + allow_unreg_ext: self.allow_unreg_ext, + } + } + + pub fn for_rdap(rdap: &RdapResponse) -> CheckParams<'_> { + CheckParams { + do_subchecks: true, + root: rdap, + parent_type: rdap.get_type(), + allow_unreg_ext: false, + } + } +} + +impl GetChecks for RdapResponse { + fn get_checks(&self, params: CheckParams) -> Checks { + match &self { + Self::Entity(e) => e.get_checks(params), + Self::Domain(d) => d.get_checks(params), + Self::Nameserver(n) => n.get_checks(params), + Self::Autnum(a) => a.get_checks(params), + Self::Network(n) => n.get_checks(params), + Self::DomainSearchResults(r) => r.get_checks(params), + Self::EntitySearchResults(r) => r.get_checks(params), + Self::NameserverSearchResults(r) => r.get_checks(params), + Self::ErrorResponse(e) => e.get_checks(params), + Self::Help(h) => h.get_checks(params), + } + } +} + +/// Trait to get checks for structures below that of the object class. +pub trait GetSubChecks { + fn get_sub_checks(&self, params: CheckParams) -> Vec; +} + +/// Traverse the checks, and return true if one is found. +pub fn traverse_checks( + checks: &Checks, + classes: &[CheckClass], + parent_tree: Option, + f: &mut F, +) -> bool +where + F: FnMut(&str, &CheckItem), +{ + let mut found = false; + let struct_tree = format!( + "{}/{}", + parent_tree.unwrap_or_else(|| "[ROOT]".to_string()), + checks.rdap_struct + ); + for item in &checks.items { + if classes.contains(&item.check_class) { + f(&struct_tree, item); + found = true; + } + } + for sub_checks in &checks.sub_checks { + if traverse_checks(sub_checks, classes, Some(struct_tree.clone()), f) { + found = true + } + } + found +} + +/// Returns true if the check is in a check list +pub fn is_checked(check: Check, checks: &[Checks]) -> bool { + checks.iter().any(|c| is_checked_item(check, c)) +} + +/// Returns true if the check is in a list of check items. +pub fn is_checked_item(check: Check, checks: &Checks) -> bool { + checks.items.iter().any(|c| c.check == check) +} + +/// The variant check types. +#[derive( + Debug, + EnumMessage, + EnumString, + Display, + Serialize, + Deserialize, + PartialEq, + PartialOrd, + Eq, + Ord, + Clone, + Copy, + FromRepr, +)] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum Check { + // RDAP Conformance 100 - 199 + #[strum(message = "RFC 9083 requires 'rdapConformance' on the root object.")] + RdapConformanceMissing = 100, + #[strum(message = "'rdapConformance' can only appear at the top of response.")] + RdapConformanceInvalidParent = 101, + #[strum(message = "declared extension may not be registered.")] + UnknownExtention = 102, + + // Link 200 - 299 + #[strum(message = "'value' property not found in Link structure as required by RFC 9083")] + LinkMissingValueProperty = 200, + #[strum(message = "'rel' property not found in Link structure as required by RFC 9083")] + LinkMissingRelProperty = 201, + #[strum(message = "ambiguous follow because related link has no 'type' property")] + LinkRelatedHasNoType = 202, + #[strum(message = "ambiguous follow because related link does not have RDAP media type")] + LinkRelatedIsNotRdap = 203, + #[strum(message = "self link has no 'type' property")] + LinkSelfHasNoType = 204, + #[strum(message = "self link does not have RDAP media type")] + LinkSelfIsNotRdap = 205, + #[strum(message = "RFC 9083 recommends self links for all object classes")] + LinkObjectClassHasNoSelf = 206, + #[strum(message = "'href' property not found in Link structure as required by RFC 9083")] + LinkMissingHrefProperty = 207, + + // Domain Variant 300 - 399 + #[strum(message = "empty domain variant is ambiguous")] + VariantEmptyDomain = 300, + + // Event 400 - 499 + #[strum(message = "event date is absent")] + EventDateIsAbsent = 400, + #[strum(message = "event date is not RFC 3339 compliant")] + EventDateIsNotRfc3339 = 401, + #[strum(message = "event action is absent")] + EventActionIsAbsent = 402, + + // Notice Or Remark 500 - 599 + #[strum(message = "RFC 9083 requires a description in a notice or remark")] + NoticeOrRemarkDescriptionIsAbsent = 500, + #[strum(message = "RFC 9083 requires a description to be an array of strings")] + NoticeOrRemarkDescriptionIsString = 501, + + // Handle 600 - 699 + #[strum(message = "handle appears to be empty or only whitespace")] + HandleIsEmpty = 600, + + // Status 700 - 799 + #[strum(message = "status appears to be empty or only whitespace")] + StatusIsEmpty = 700, + + // Role 800 - 899 + #[strum(message = "role appears to be empty or only whitespace")] + RoleIsEmpty = 800, + #[strum(message = "entity role may not be registered")] + UnknownRole = 801, + #[strum(message = "role is a string, not array of strings")] + RoleIsString = 802, + + // LDH Name 900 - 999 + #[strum(message = "ldhName does not appear to be an LDH name")] + LdhNameInvalid = 900, + #[strum(message = "Documentation domain name. See RFC 6761")] + LdhNameDocumentation = 901, + #[strum(message = "Unicode name does not match LDH")] + LdhNameDoesNotMatchUnicode = 902, + + // Unicode Nmae 1000 - 1099 + #[strum(message = "unicodeName does not appear to be a domain name")] + UnicodeNameInvalidDomain = 1000, + #[strum(message = "unicodeName does not appear to be valid Unicode")] + UnicodeNameInvalidUnicode = 1001, + + // Network Or Autnum Name 1100 - 1199 + #[strum(message = "name appears to be empty or only whitespace")] + NetworkOrAutnumNameIsEmpty = 1100, + + // Network or Autnum Type 1200 - 1299 + #[strum(message = "type appears to be empty or only whitespace")] + NetworkOrAutnumTypeIsEmpty = 1200, + + // IP Address 1300 - 1399 + #[strum(message = "start or end IP address is missing")] + IpAddressMissing = 1300, + #[strum(message = "IP address is malformed")] + IpAddressMalformed = 1301, + #[strum(message = "end IP address comes before start IP address")] + IpAddressEndBeforeStart = 1302, + #[strum(message = "IP version does not match IP address")] + IpAddressVersionMismatch = 1303, + #[strum(message = "IP version is malformed")] + IpAddressMalformedVersion = 1304, + #[strum(message = "IP address list is empty")] + IpAddressListIsEmpty = 1305, + #[strum(message = "\"This network.\" See RFC 791")] + IpAddressThisNetwork = 1306, + #[strum(message = "Private use. See RFC 1918")] + IpAddressPrivateUse = 1307, + #[strum(message = "Shared NAT network. See RFC 6598")] + IpAddressSharedNat = 1308, + #[strum(message = "Loopback network. See RFC 1122")] + IpAddressLoopback = 1309, + #[strum(message = "Link local network. See RFC 3927")] + IpAddressLinkLocal = 1310, + #[strum(message = "Unique local network. See RFC 8190")] + IpAddressUniqueLocal = 1311, + #[strum(message = "Documentation network. See RFC 5737")] + IpAddressDocumentationNet = 1312, + #[strum(message = "Reserved network. See RFC 1112")] + IpAddressReservedNet = 1313, + #[strum(message = "IP address array is a string.")] + IpAddressArrayIsString = 1314, + + // Autnum 1400 - 1499 + #[strum(message = "start or end autnum is missing")] + AutnumMissing = 1400, + #[strum(message = "end AS number comes before start AS number")] + AutnumEndBeforeStart = 1401, + #[strum(message = "Private use. See RFC 6996")] + AutnumPrivateUse = 1402, + #[strum(message = "Documentation AS number. See RFC 5398")] + AutnumDocumentation = 1403, + #[strum(message = "Reserved AS number. See RFC 6996")] + AutnumReserved = 1404, + + // Vcard 1500 - 1599 + #[strum(message = "vCard array does not contain a vCard")] + VcardArrayIsEmpty = 1500, + #[strum(message = "vCard has no fn property")] + VcardHasNoFn = 1501, + #[strum(message = "vCard fn property is empty")] + VcardFnIsEmpty = 1502, + + // Port 43 1600 - 1699 + #[strum(message = "port43 appears to be empty or only whitespace")] + Port43IsEmpty = 1600, + + // Public Id 1700 - 1799 + #[strum(message = "publicId type is absent")] + PublicIdTypeIsAbsent = 1700, + #[strum(message = "publicId identifier is absent")] + PublicIdIdentifierIsAbsent = 1701, + + // HTTP 1800 - 1899 + #[strum(message = "Use of access-control-allow-origin is recommended.")] + CorsAllowOriginRecommended = 1800, + #[strum(message = "Use of access-control-allow-origin with asterisk is recommended.")] + CorsAllowOriginStarRecommended = 1801, + #[strum(message = "Use of access-control-allow-credentials is not recommended.")] + CorsAllowCredentialsNotRecommended = 1802, + #[strum(message = "No content-type header received.")] + ContentTypeIsAbsent = 1803, + #[strum(message = "Content-type is not application/rdap+json.")] + ContentTypeIsNotRdap = 1804, + + // Cidr0 1900 - 1999 + #[strum(message = "Cidr0 v4 prefix is absent")] + Cidr0V4PrefixIsAbsent = 1900, + #[strum(message = "Cidr0 v4 length is absent")] + Cidr0V4LengthIsAbsent = 1901, + #[strum(message = "Cidr0 v6 prefix is absent")] + Cidr0V6PrefixIsAbsent = 1902, + #[strum(message = "Cidr0 v6 length is absent")] + Cidr0V6LengthIsAbsent = 1903, + + // ICANN Profile 2000 - 2099 + #[strum(message = "RDAP Service Must use HTTPS.")] + MustUseHttps = 2000, + #[strum(message = "access-control-allow-origin is not asterisk")] + AllowOriginNotStar = 2001, + + // Explicit Testing Errors 2100 - 2199 + #[strum(message = "CNAME without A records.")] + CnameWithoutARecords = 2100, + #[strum(message = "CNAME without AAAA records.")] + CnameWithoutAAAARecords = 2101, + #[strum(message = "No A records.")] + NoARecords = 2102, + #[strum(message = "No AAAA records.")] + NoAAAARecords = 2103, + #[strum(message = "Expected extension not found.")] + ExpectedExtensionNotFound = 2104, + #[strum(message = "IPv6 Support Required.")] + Ipv6SupportRequiredByIcann = 2105, + + // Secure DNS 2200 - 2299 + #[strum(message = "delegationSigned is a string not a bool.")] + DelegationSignedIsString = 2200, + #[strum(message = "zoneSigned is a string not a bool.")] + ZoneSignedIsString = 2201, + #[strum(message = "maxSigLife is a string not a number.")] + MaxSigLifeIsString = 2202, + // key data + #[strum(message = "keyData algorithm is a string not a number.")] + KeyDatumAlgorithmIsString = 2203, + #[strum(message = "keyData algorithm is out of range.")] + KeyDatumAlgorithmIsOutOfRange = 2204, + #[strum(message = "keyData flags is a string not a number.")] + KeyDatumFlagsIsString = 2205, + #[strum(message = "keyData flags is out of range.")] + KeyDatumFlagsIsOutOfRange = 2206, + #[strum(message = "keyData protocol is a string not a number.")] + KeyDatumProtocolIsString = 2207, + #[strum(message = "keyData protocol is out of range.")] + KeyDatumProtocolIsOutOfRange = 2208, + // ds data + #[strum(message = "dsData algorithm is a string not a number.")] + DsDatumAlgorithmIsString = 2213, + #[strum(message = "dsData algorithm is out of range.")] + DsDatumAlgorithmIsOutOfRange = 2214, + #[strum(message = "dsData keyTag is a string not a number.")] + DsDatumKeyTagIsString = 2215, + #[strum(message = "dsData keyTag is out of range.")] + DsDatumKeyTagIsOutOfRange = 2216, + #[strum(message = "dsData digestType is a string not a number.")] + DsDatumDigestTypeIsString = 2217, + #[strum(message = "dsData digestType is out of range.")] + DsDatumDigestTypeIsOutOfRange = 2218, +} + +impl Check { + pub fn check_item(self) -> CheckItem { + let check_class = match self { + Self::RdapConformanceMissing | Self::RdapConformanceInvalidParent => { + CheckClass::StdError + } + Self::UnknownExtention => CheckClass::StdWarning, + + Self::LinkMissingValueProperty | Self::LinkMissingRelProperty => CheckClass::StdError, + Self::LinkRelatedHasNoType + | Self::LinkRelatedIsNotRdap + | Self::LinkSelfHasNoType + | Self::LinkSelfIsNotRdap => CheckClass::StdWarning, + Self::LinkObjectClassHasNoSelf => CheckClass::SpecificationNote, + Self::LinkMissingHrefProperty => CheckClass::StdError, + + Self::VariantEmptyDomain => CheckClass::StdWarning, + + Self::EventDateIsAbsent + | Self::EventDateIsNotRfc3339 + | Self::EventActionIsAbsent + | Self::NoticeOrRemarkDescriptionIsAbsent + | Self::NoticeOrRemarkDescriptionIsString => CheckClass::StdError, + + Self::HandleIsEmpty => CheckClass::StdWarning, + + Self::StatusIsEmpty | Self::RoleIsEmpty => CheckClass::StdError, + Self::UnknownRole => CheckClass::StdWarning, + Self::RoleIsString | Self::LdhNameInvalid => CheckClass::StdError, + Self::LdhNameDocumentation => CheckClass::Informational, + Self::LdhNameDoesNotMatchUnicode => CheckClass::StdWarning, + + Self::UnicodeNameInvalidDomain | Self::UnicodeNameInvalidUnicode => { + CheckClass::StdError + } + + Self::NetworkOrAutnumNameIsEmpty + | Self::NetworkOrAutnumTypeIsEmpty + | Self::IpAddressMissing => CheckClass::StdWarning, + Self::IpAddressMalformed => CheckClass::StdError, + Self::IpAddressEndBeforeStart | Self::IpAddressVersionMismatch => { + CheckClass::StdWarning + } + Self::IpAddressMalformedVersion | Self::IpAddressListIsEmpty => CheckClass::StdError, + Self::IpAddressThisNetwork + | Self::IpAddressPrivateUse + | Self::IpAddressSharedNat + | Self::IpAddressLoopback + | Self::IpAddressLinkLocal + | Self::IpAddressUniqueLocal + | Self::IpAddressDocumentationNet + | Self::IpAddressReservedNet => CheckClass::Informational, + Self::IpAddressArrayIsString => CheckClass::StdError, + + Self::AutnumMissing | Self::AutnumEndBeforeStart => CheckClass::StdWarning, + Self::AutnumPrivateUse | Self::AutnumDocumentation | Self::AutnumReserved => { + CheckClass::Informational + } + + Self::VcardArrayIsEmpty | Self::VcardHasNoFn => CheckClass::StdError, + Self::VcardFnIsEmpty => CheckClass::SpecificationNote, + + Self::Port43IsEmpty | Self::PublicIdTypeIsAbsent | Self::PublicIdIdentifierIsAbsent => { + CheckClass::StdError + } + + Self::CorsAllowOriginRecommended + | Self::CorsAllowOriginStarRecommended + | Self::CorsAllowCredentialsNotRecommended => CheckClass::StdWarning, + Self::ContentTypeIsAbsent | Self::ContentTypeIsNotRdap => CheckClass::StdError, + + Self::Cidr0V4PrefixIsAbsent + | Self::Cidr0V4LengthIsAbsent + | Self::Cidr0V6PrefixIsAbsent + | Self::Cidr0V6LengthIsAbsent => CheckClass::Cidr0Error, + + Self::MustUseHttps | Self::AllowOriginNotStar => CheckClass::IcannError, + + Self::CnameWithoutARecords | Self::CnameWithoutAAAARecords => CheckClass::StdError, + Self::NoARecords | Self::NoAAAARecords => CheckClass::SpecificationNote, + Self::ExpectedExtensionNotFound => CheckClass::StdError, + Self::Ipv6SupportRequiredByIcann => CheckClass::IcannError, + + Self::DelegationSignedIsString + | Self::ZoneSignedIsString + | Self::MaxSigLifeIsString + | Self::KeyDatumAlgorithmIsString + | Self::KeyDatumAlgorithmIsOutOfRange + | Self::KeyDatumFlagsIsString + | Self::KeyDatumFlagsIsOutOfRange + | Self::KeyDatumProtocolIsString + | Self::KeyDatumProtocolIsOutOfRange + | Self::DsDatumAlgorithmIsString + | Self::DsDatumAlgorithmIsOutOfRange + | Self::DsDatumKeyTagIsString + | Self::DsDatumKeyTagIsOutOfRange + | Self::DsDatumDigestTypeIsString + | Self::DsDatumDigestTypeIsOutOfRange => CheckClass::StdError, + }; + CheckItem { + check_class, + check: self, + } + } +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use crate::check::RdapStructure; + + use super::{traverse_checks, Check, CheckClass, CheckItem, Checks}; + + #[test] + fn GIVEN_info_checks_WHEN_traversed_for_info_THEN_found() { + // GIVEN + let checks = Checks { + rdap_struct: RdapStructure::Entity, + items: vec![CheckItem { + check_class: CheckClass::Informational, + check: Check::VariantEmptyDomain, + }], + sub_checks: vec![], + }; + + // WHEN + let found = traverse_checks( + &checks, + &[CheckClass::Informational], + None, + &mut |struct_tree, check_item| println!("{struct_tree} -> {check_item}"), + ); + + // THEN + assert!(found); + } + + #[test] + fn GIVEN_specwarn_checks_WHEN_traversed_for_info_THEN_not_found() { + // GIVEN + let checks = Checks { + rdap_struct: RdapStructure::Entity, + items: vec![CheckItem { + check_class: CheckClass::StdWarning, + check: Check::VariantEmptyDomain, + }], + sub_checks: vec![], + }; + + // WHEN + let found = traverse_checks( + &checks, + &[CheckClass::Informational], + None, + &mut |struct_tree, check_item| println!("{struct_tree} -> {check_item}"), + ); + + // THEN + assert!(!found); + } + + #[test] + fn GIVEN_info_subchecks_WHEN_traversed_for_info_THEN_found() { + // GIVEN + let checks = Checks { + rdap_struct: RdapStructure::Entity, + items: vec![], + sub_checks: vec![Checks { + rdap_struct: RdapStructure::Autnum, + items: vec![CheckItem { + check_class: CheckClass::Informational, + check: Check::VariantEmptyDomain, + }], + sub_checks: vec![], + }], + }; + + // WHEN + let found = traverse_checks( + &checks, + &[CheckClass::Informational], + None, + &mut |struct_tree, check_item| println!("{struct_tree} -> {check_item}"), + ); + + // THEN + assert!(found); + } + + #[test] + fn GIVEN_specwarn_subchecks_WHEN_traversed_for_info_THEN_not_found() { + // GIVEN + let checks = Checks { + rdap_struct: RdapStructure::Entity, + items: vec![], + sub_checks: vec![Checks { + rdap_struct: RdapStructure::Autnum, + items: vec![CheckItem { + check_class: CheckClass::StdWarning, + check: Check::VariantEmptyDomain, + }], + sub_checks: vec![], + }], + }; + + // WHEN + let found = traverse_checks( + &checks, + &[CheckClass::Informational], + None, + &mut |struct_tree, check_item| println!("{struct_tree} -> {check_item}"), + ); + + // THEN + assert!(!found); + } + + #[test] + fn GIVEN_checks_and_subchecks_WHEN_traversed_THEN_tree_structure_shows_tree() { + // GIVEN + let checks = Checks { + rdap_struct: RdapStructure::Entity, + items: vec![CheckItem { + check_class: CheckClass::Informational, + check: Check::RdapConformanceInvalidParent, + }], + sub_checks: vec![Checks { + rdap_struct: RdapStructure::Autnum, + items: vec![CheckItem { + check_class: CheckClass::Informational, + check: Check::VariantEmptyDomain, + }], + sub_checks: vec![], + }], + }; + + // WHEN + let mut structs: Vec = vec![]; + let found = traverse_checks( + &checks, + &[CheckClass::Informational], + None, + &mut |struct_tree, _check_item| structs.push(struct_tree.to_string()), + ); + + // THEN + assert!(found); + dbg!(&structs); + assert!(structs.contains(&"[ROOT]/entity".to_string())); + assert!(structs.contains(&"[ROOT]/entity/autnum".to_string())); + } +} diff --git a/icann-rdap-common/src/check/nameserver.rs b/icann-rdap-common/src/check/nameserver.rs new file mode 100644 index 0000000..effef0b --- /dev/null +++ b/icann-rdap-common/src/check/nameserver.rs @@ -0,0 +1,184 @@ +use std::{any::TypeId, net::IpAddr, str::FromStr}; + +use crate::response::nameserver::Nameserver; + +use super::{ + string::{StringCheck, StringListCheck}, + Check, CheckParams, Checks, GetChecks, GetSubChecks, +}; + +impl GetChecks for Nameserver { + fn get_checks(&self, params: CheckParams) -> super::Checks { + let sub_checks = if params.do_subchecks { + let mut sub_checks: Vec = self + .common + .get_sub_checks(params.from_parent(TypeId::of::())); + sub_checks.append( + &mut self + .object_common + .get_sub_checks(params.from_parent(TypeId::of::())), + ); + sub_checks + } else { + vec![] + }; + + let mut items = vec![]; + + // check ldh + if let Some(ldh) = &self.ldh_name { + if !ldh.is_ldh_domain_name() { + items.push(Check::LdhNameInvalid.check_item()); + } + } + + if let Some(ip_addresses) = &self.ip_addresses { + if let Some(v6_addrs) = &ip_addresses.v6 { + if v6_addrs.is_string() { + items.push(Check::IpAddressArrayIsString.check_item()) + } + if v6_addrs.is_empty_or_any_empty_or_whitespace() { + items.push(Check::IpAddressListIsEmpty.check_item()) + } + if v6_addrs + .vec() + .iter() + .any(|ip| IpAddr::from_str(ip).is_err()) + { + items.push(Check::IpAddressMalformed.check_item()) + } + } + if let Some(v4_addrs) = &ip_addresses.v4 { + if v4_addrs.is_string() { + items.push(Check::IpAddressArrayIsString.check_item()) + } + if v4_addrs.is_empty_or_any_empty_or_whitespace() { + items.push(Check::IpAddressListIsEmpty.check_item()) + } + if v4_addrs + .vec() + .iter() + .any(|ip| IpAddr::from_str(ip).is_err()) + { + items.push(Check::IpAddressMalformed.check_item()) + } + } + } + + Checks { + rdap_struct: super::RdapStructure::Nameserver, + items, + sub_checks, + } + } +} + +#[cfg(test)] +mod tests { + use {crate::prelude::*, rstest::rstest}; + + use crate::check::{Check, CheckParams, GetChecks}; + + #[rstest] + #[case("")] + #[case(" ")] + #[case("_.")] + fn check_nameserver_with_bad_ldh(#[case] ldh: &str) { + // GIVEN + let rdap = Nameserver::builder() + .ldh_name(ldh) + .build() + .unwrap() + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(checks + .items + .iter() + .any(|c| c.check == Check::LdhNameInvalid)); + } + + #[test] + fn check_nameserver_with_empty_v6s() { + // GIVEN + let ns = Nameserver::illegal() + .ldh_name("ns1.example.com") + .ip_addresses(IpAddresses::illegal().v6(vec![]).build()) + .build() + .to_response(); + + // WHEN + let checks = ns.get_checks(CheckParams::for_rdap(&ns)); + + // THEN + dbg!(&checks); + assert!(checks + .items + .iter() + .any(|c| c.check == Check::IpAddressListIsEmpty)); + } + + #[test] + fn check_nameserver_with_empty_v4s() { + // GIVEN + let ns = Nameserver::illegal() + .ldh_name("ns1.example.com") + .ip_addresses(IpAddresses::illegal().v4(vec![]).build()) + .build() + .to_response(); + + // WHEN + let checks = ns.get_checks(CheckParams::for_rdap(&ns)); + + // THEN + dbg!(&checks); + assert!(checks + .items + .iter() + .any(|c| c.check == Check::IpAddressListIsEmpty)); + } + + #[test] + fn check_nameserver_with_bad_v6s() { + // GIVEN + let ns = Nameserver::illegal() + .ldh_name("ns1.example.com") + .ip_addresses(IpAddresses::illegal().v6(vec!["__".to_string()]).build()) + .build() + .to_response(); + + // WHEN + let checks = ns.get_checks(CheckParams::for_rdap(&ns)); + + // THEN + dbg!(&checks); + assert!(checks + .items + .iter() + .any(|c| c.check == Check::IpAddressMalformed)); + } + + #[test] + fn check_nameserver_with_bad_v4s() { + // GIVEN + let ns = Nameserver::illegal() + .ldh_name("ns1.example.com") + .ip_addresses(IpAddresses::illegal().v4(vec!["___".to_string()]).build()) + .build() + .to_response(); + + // WHEN + let checks = ns.get_checks(CheckParams::for_rdap(&ns)); + + // THEN + dbg!(&checks); + assert!(checks + .items + .iter() + .any(|c| c.check == Check::IpAddressMalformed)); + } +} diff --git a/icann-rdap-common/src/check/network.rs b/icann-rdap-common/src/check/network.rs new file mode 100644 index 0000000..a53c35d --- /dev/null +++ b/icann-rdap-common/src/check/network.rs @@ -0,0 +1,482 @@ +use std::{any::TypeId, net::IpAddr, str::FromStr}; + +use cidr::IpCidr; + +use crate::response::network::{Cidr0Cidr, Network}; + +use super::{string::StringCheck, Check, CheckParams, Checks, GetChecks, GetSubChecks}; + +impl GetChecks for Network { + fn get_checks(&self, params: CheckParams) -> super::Checks { + let sub_checks = if params.do_subchecks { + let mut sub_checks: Vec = self + .common + .get_sub_checks(params.from_parent(TypeId::of::())); + sub_checks.append( + &mut self + .object_common + .get_sub_checks(params.from_parent(TypeId::of::())), + ); + if let Some(cidr0) = &self.cidr0_cidrs { + cidr0.iter().for_each(|cidr| match cidr { + Cidr0Cidr::V4Cidr(v4) => { + if v4.v4prefix.is_none() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::Cidr0, + items: vec![Check::Cidr0V4PrefixIsAbsent.check_item()], + sub_checks: vec![], + }) + } + if v4.length.is_none() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::Cidr0, + items: vec![Check::Cidr0V4LengthIsAbsent.check_item()], + sub_checks: vec![], + }) + } + } + Cidr0Cidr::V6Cidr(v6) => { + if v6.v6prefix.is_none() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::Cidr0, + items: vec![Check::Cidr0V6PrefixIsAbsent.check_item()], + sub_checks: vec![], + }) + } + if v6.length.is_none() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::Cidr0, + items: vec![Check::Cidr0V6LengthIsAbsent.check_item()], + sub_checks: vec![], + }) + } + } + }) + } + sub_checks + } else { + vec![] + }; + + let mut items = vec![]; + + if let Some(name) = &self.name { + if name.is_whitespace_or_empty() { + items.push(Check::NetworkOrAutnumNameIsEmpty.check_item()) + } + } + + if let Some(network_type) = &self.network_type { + if network_type.is_whitespace_or_empty() { + items.push(Check::NetworkOrAutnumTypeIsEmpty.check_item()) + } + } + + if self.start_address.is_none() || self.end_address.is_none() { + items.push(Check::IpAddressMissing.check_item()) + } + + if let Some(start_ip) = &self.start_address { + let start_addr = IpAddr::from_str(start_ip); + if start_addr.is_err() { + items.push(Check::IpAddressMalformed.check_item()) + } else if self.end_address.is_some() { + let Ok(start_addr) = start_addr else { + panic!("ip result did not work") + }; + let Some(end_ip) = &self.end_address else { + panic!("end address unwrap failed") + }; + if let Ok(end_addr) = IpAddr::from_str(end_ip) { + if start_addr > end_addr { + items.push(Check::IpAddressEndBeforeStart.check_item()) + } + if let Some(ip_version) = &self.ip_version { + if (ip_version == "v4" && (start_addr.is_ipv6() || end_addr.is_ipv6())) + || (ip_version == "v6" && (start_addr.is_ipv4() || end_addr.is_ipv4())) + { + items.push(Check::IpAddressVersionMismatch.check_item()) + } else if ip_version != "v4" && ip_version != "v6" { + items.push(Check::IpAddressMalformedVersion.check_item()) + } + } + let this_network = + IpCidr::from_str("0.0.0.0/8").expect("incorrect this netowrk cidr"); + if this_network.contains(&start_addr) && this_network.contains(&end_addr) { + items.push(Check::IpAddressThisNetwork.check_item()) + } + let private_10 = IpCidr::from_str("10.0.0.0/8").expect("incorrect net 10 cidr"); + let private_172 = + IpCidr::from_str("172.16.0.0/12").expect("incorrect net 172.16 cidr"); + let private_192 = + IpCidr::from_str("192.168.0.0/16").expect("incorrect net 192.168 cidr"); + if (private_10.contains(&start_addr) && private_10.contains(&end_addr)) + || (private_172.contains(&start_addr) && private_172.contains(&end_addr)) + || (private_192.contains(&start_addr) && private_192.contains(&end_addr)) + { + items.push(Check::IpAddressPrivateUse.check_item()) + } + let shared_nat = + IpCidr::from_str("100.64.0.0/10").expect("incorrect net 100 cidr"); + if shared_nat.contains(&start_addr) && shared_nat.contains(&end_addr) { + items.push(Check::IpAddressSharedNat.check_item()) + } + let loopback = + IpCidr::from_str("127.0.0.0/8").expect("incorrect loopback cidr"); + if loopback.contains(&start_addr) && loopback.contains(&end_addr) { + items.push(Check::IpAddressLoopback.check_item()) + } + let linklocal1 = + IpCidr::from_str("169.254.0.0/16").expect("incorrect linklocal1 cidr"); + let linklocal2 = + IpCidr::from_str("fe80::/10").expect("incorrect linklocal2 cidr"); + if (linklocal1.contains(&start_addr) && linklocal1.contains(&end_addr)) + || (linklocal2.contains(&start_addr) && linklocal2.contains(&end_addr)) + { + items.push(Check::IpAddressLinkLocal.check_item()) + } + let uniquelocal = + IpCidr::from_str("fe80::/10").expect("incorrect unique local cidr"); + if uniquelocal.contains(&start_addr) && uniquelocal.contains(&end_addr) { + items.push(Check::IpAddressUniqueLocal.check_item()) + } + let doc1 = IpCidr::from_str("192.0.2.0/24").expect("incorrect doc1 cidr"); + let doc2 = IpCidr::from_str("198.51.100.0/24").expect("incorrect doc2 cidr"); + let doc3 = IpCidr::from_str("203.0.113.0/24").expect("incorrect doc3 cidr"); + let doc4 = IpCidr::from_str("2001:db8::/32").expect("incorrect doc4 cidr"); + if (doc1.contains(&start_addr) && doc1.contains(&end_addr)) + || (doc2.contains(&start_addr) && doc2.contains(&end_addr)) + || (doc3.contains(&start_addr) && doc3.contains(&end_addr)) + || (doc4.contains(&start_addr) && doc4.contains(&end_addr)) + { + items.push(Check::IpAddressDocumentationNet.check_item()) + } + let reserved = + IpCidr::from_str("240.0.0.0/4").expect("incorrect reserved cidr"); + if reserved.contains(&start_addr) && reserved.contains(&end_addr) { + items.push(Check::IpAddressLinkLocal.check_item()) + } + } + } + } + + if let Some(end_ip) = &self.end_address { + let addr = IpAddr::from_str(end_ip); + if addr.is_err() { + items.push(Check::IpAddressMalformed.check_item()) + } + } + + Checks { + rdap_struct: super::RdapStructure::IpNetwork, + items, + sub_checks, + } + } +} + +#[cfg(test)] +mod tests { + + use rstest::rstest; + + use crate::{ + prelude::{Numberish, ToResponse}, + response::network::{Cidr0Cidr, Network, V4Cidr, V6Cidr}, + }; + + use crate::check::{Check, CheckParams, GetChecks}; + + #[test] + fn check_network_with_empty_name() { + // GIVEN + let mut network = Network::builder() + .cidr("10.0.0.0/8") + .build() + .expect("invalid ip cidr"); + network.name = Some("".to_string()); + let rdap = network.to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(checks + .items + .iter() + .any(|c| c.check == Check::NetworkOrAutnumNameIsEmpty)); + } + + #[test] + fn check_network_with_empty_type() { + // GIVEN + let mut network = Network::builder() + .cidr("10.0.0.0/8") + .build() + .expect("invalid ip cidr"); + network.network_type = Some("".to_string()); + let rdap = network.to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(checks + .items + .iter() + .any(|c| c.check == Check::NetworkOrAutnumTypeIsEmpty)); + } + + #[test] + fn check_network_with_no_start() { + // GIVEN + let mut network = Network::builder() + .cidr("10.0.0.0/8") + .build() + .expect("invalid ip cidr"); + network.start_address = None; + let rdap = network.to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(checks + .items + .iter() + .any(|c| c.check == Check::IpAddressMissing)); + } + + #[test] + fn check_network_with_no_end() { + // GIVEN + let mut network = Network::builder() + .cidr("10.0.0.0/8") + .build() + .expect("invalid ip cidr"); + network.end_address = None; + let rdap = network.to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(checks + .items + .iter() + .any(|c| c.check == Check::IpAddressMissing)); + } + + #[test] + fn check_network_with_bad_start() { + // GIVEN + let mut network = Network::builder() + .cidr("10.0.0.0/8") + .build() + .expect("invalid ip cidr"); + network.start_address = Some("____".to_string()); + let rdap = network.to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(checks + .items + .iter() + .any(|c| c.check == Check::IpAddressMalformed)); + } + + #[test] + fn check_network_with_bad_end() { + // GIVEN + let mut network = Network::builder() + .cidr("10.0.0.0/8") + .build() + .expect("invalid ip cidr"); + network.end_address = Some("___".to_string()); + let rdap = network.to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(checks + .items + .iter() + .any(|c| c.check == Check::IpAddressMalformed)); + } + + #[test] + fn check_network_with_end_before_start() { + // GIVEN + let mut network = Network::builder() + .cidr("10.0.0.0/8") + .build() + .expect("invalid ip cidr"); + let swap = network.end_address.clone(); + network.end_address = network.start_address.clone(); + network.start_address = swap; + let rdap = network.to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(checks + .items + .iter() + .any(|c| c.check == Check::IpAddressEndBeforeStart)); + } + + #[rstest] + #[case("10.0.0.0/8", "v6")] + #[case("2000::/64", "v4")] + fn check_network_with_ip_version(#[case] cidr: &str, #[case] version: &str) { + // GIVEN + let mut network = Network::builder() + .cidr(cidr) + .build() + .expect("invalid ip cidr"); + network.ip_version = Some(version.to_string()); + let rdap = network.to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(checks + .items + .iter() + .any(|c| c.check == Check::IpAddressVersionMismatch)); + } + + #[rstest] + #[case("10.0.0.0/8", "__")] + #[case("2000::/64", "__")] + #[case("10.0.0.0/8", "")] + #[case("2000::/64", "")] + fn check_network_with_bad_ip_version(#[case] cidr: &str, #[case] version: &str) { + // GIVEN + let mut network = Network::builder() + .cidr(cidr) + .build() + .expect("invalid ip cidr"); + network.ip_version = Some(version.to_string()); + let rdap = network.to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(checks + .items + .iter() + .any(|c| c.check == Check::IpAddressMalformedVersion)); + } + + #[test] + fn check_cidr0_with_v4_prefixex() { + // GIVEN + let network = Network::illegal() + .cidr0_cidrs(vec![Cidr0Cidr::V4Cidr(V4Cidr { + v4prefix: None, + length: Some(Numberish::::from(0)), + })]) + .build(); + let rdap = network.to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(checks + .sub(crate::check::RdapStructure::Cidr0) + .expect("Cidr0") + .items + .iter() + .any(|c| c.check == Check::Cidr0V4PrefixIsAbsent)); + } + + #[test] + fn check_cidr0_with_v6_prefixex() { + // GIVEN + let network = Network::illegal() + .cidr0_cidrs(vec![Cidr0Cidr::V6Cidr(V6Cidr { + v6prefix: None, + length: Some(Numberish::::from(0)), + })]) + .build(); + let rdap = network.to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(checks + .sub(crate::check::RdapStructure::Cidr0) + .expect("Cidr0") + .items + .iter() + .any(|c| c.check == Check::Cidr0V6PrefixIsAbsent)); + } + + #[test] + fn check_cidr0_with_v4_length() { + // GIVEN + let network = Network::illegal() + .cidr0_cidrs(vec![Cidr0Cidr::V4Cidr(V4Cidr { + v4prefix: Some("0.0.0.0".to_string()), + length: None, + })]) + .build(); + let rdap = network.to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(checks + .sub(crate::check::RdapStructure::Cidr0) + .expect("Cidr0") + .items + .iter() + .any(|c| c.check == Check::Cidr0V4LengthIsAbsent)); + } + + #[test] + fn check_cidr0_with_v6_length() { + // GIVEN + let network = Network::illegal() + .cidr0_cidrs(vec![Cidr0Cidr::V6Cidr(V6Cidr { + v6prefix: Some("0.0.0.0".to_string()), + length: None, + })]) + .build(); + let rdap = network.to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(checks + .sub(crate::check::RdapStructure::Cidr0) + .expect("Cidr0") + .items + .iter() + .any(|c| c.check == Check::Cidr0V6LengthIsAbsent)); + } +} diff --git a/icann-rdap-common/src/check/search.rs b/icann-rdap-common/src/check/search.rs new file mode 100644 index 0000000..b669abd --- /dev/null +++ b/icann-rdap-common/src/check/search.rs @@ -0,0 +1,68 @@ +use std::any::TypeId; + +use crate::response::search::{DomainSearchResults, EntitySearchResults, NameserverSearchResults}; + +use super::{CheckParams, Checks, GetChecks, GetSubChecks}; + +impl GetChecks for DomainSearchResults { + fn get_checks(&self, params: CheckParams) -> super::Checks { + let sub_checks: Vec = if params.do_subchecks { + let mut sub_checks = self + .common + .get_sub_checks(params.from_parent(TypeId::of::())); + self.results.iter().for_each(|result| { + sub_checks.push(result.get_checks(params.from_parent(TypeId::of::()))) + }); + sub_checks + } else { + vec![] + }; + Checks { + rdap_struct: super::RdapStructure::DomainSearchResults, + items: vec![], + sub_checks, + } + } +} + +impl GetChecks for NameserverSearchResults { + fn get_checks(&self, params: CheckParams) -> super::Checks { + let sub_checks: Vec = if params.do_subchecks { + let mut sub_checks: Vec = self + .common + .get_sub_checks(params.from_parent(TypeId::of::())); + self.results.iter().for_each(|result| { + sub_checks.push(result.get_checks(params.from_parent(TypeId::of::()))) + }); + sub_checks + } else { + vec![] + }; + Checks { + rdap_struct: super::RdapStructure::NameserverSearchResults, + items: vec![], + sub_checks, + } + } +} + +impl GetChecks for EntitySearchResults { + fn get_checks(&self, params: CheckParams) -> super::Checks { + let sub_checks: Vec = if params.do_subchecks { + let mut sub_checks: Vec = self + .common + .get_sub_checks(params.from_parent(TypeId::of::())); + self.results.iter().for_each(|result| { + sub_checks.push(result.get_checks(params.from_parent(TypeId::of::()))) + }); + sub_checks + } else { + vec![] + }; + Checks { + rdap_struct: super::RdapStructure::EntitySearchResults, + items: vec![], + sub_checks, + } + } +} diff --git a/icann-rdap-common/src/check/string.rs b/icann-rdap-common/src/check/string.rs new file mode 100644 index 0000000..df2c2ca --- /dev/null +++ b/icann-rdap-common/src/check/string.rs @@ -0,0 +1,295 @@ +/// Functions for types that can be turned into strings. +/// +/// Example: +/// ```rust +/// use icann_rdap_common::check::*; +/// +/// let s = " "; +/// assert!(s.is_whitespace_or_empty()); +/// ``` +pub trait StringCheck { + /// Tests if the string is empty, including for if the string only has whitespace. + fn is_whitespace_or_empty(&self) -> bool; + + /// Tests if the string contains only letters, digits, or hyphens and is not empty. + fn is_ldh_string(&self) -> bool; + + /// Tests if a string is an LDH doamin name. This is not to be confused with [StringCheck::is_ldh_string], + /// which checks individual domain labels. + fn is_ldh_domain_name(&self) -> bool; + + /// Tests if a string is a Unicode domain name. + fn is_unicode_domain_name(&self) -> bool; + + /// Tests if a string is begins with a period and only has one label. + fn is_tld(&self) -> bool; +} + +impl StringCheck for T { + fn is_whitespace_or_empty(&self) -> bool { + let s = self.to_string(); + s.is_empty() || s.chars().all(char::is_whitespace) + } + + fn is_ldh_string(&self) -> bool { + let s = self.to_string(); + !s.is_empty() && s.chars().all(char::is_ldh) + } + + fn is_ldh_domain_name(&self) -> bool { + let s = self.to_string(); + s == "." || (!s.is_empty() && s.split_terminator('.').all(|s| s.is_ldh_string())) + } + + fn is_unicode_domain_name(&self) -> bool { + let s = self.to_string(); + s == "." + || (!s.is_empty() + && s.split_terminator('.').all(|s| { + s.chars() + .all(|c| c == '-' || (!c.is_ascii_punctuation() && !c.is_whitespace())) + })) + } + + fn is_tld(&self) -> bool { + let s = self.to_string(); + s.starts_with('.') + && s.len() > 2 + && s.matches('.').count() == 1 + && s.split_terminator('.').all(|s| { + s.chars() + .all(|c| !c.is_ascii_punctuation() && !c.is_whitespace()) + }) + } +} + +/// Functions for types that can be turned into arrays of strings. +/// +/// Example: +/// ```rust +/// use icann_rdap_common::check::*; +/// +/// let a: &[&str] = &["foo",""]; +/// assert!(a.is_empty_or_any_empty_or_whitespace()); +/// ``` +pub trait StringListCheck { + /// Tests if a list of strings is empty, or if any of the + /// elemeents of the list are empty or whitespace. + fn is_empty_or_any_empty_or_whitespace(&self) -> bool; + + /// Tests if a list of strings ard LDH strings. See [CharCheck::is_ldh]. + fn is_ldh_string_list(&self) -> bool; +} + +impl StringListCheck for &[T] { + fn is_empty_or_any_empty_or_whitespace(&self) -> bool { + self.is_empty() || self.iter().any(|s| s.to_string().is_whitespace_or_empty()) + } + + fn is_ldh_string_list(&self) -> bool { + !self.is_empty() && self.iter().all(|s| s.to_string().is_ldh_string()) + } +} + +impl StringListCheck for Vec { + fn is_empty_or_any_empty_or_whitespace(&self) -> bool { + self.is_empty() || self.iter().any(|s| s.to_string().is_whitespace_or_empty()) + } + + fn is_ldh_string_list(&self) -> bool { + !self.is_empty() && self.iter().all(|s| s.to_string().is_ldh_string()) + } +} + +/// Functions for chars. +/// +/// Example: +/// ```rust +/// use icann_rdap_common::check::*; +/// +/// let c = 'a'; +/// assert!(c.is_ldh()); +/// ``` +pub trait CharCheck { + /// Checks if the character is a letter, digit or a hyphen + #[allow(clippy::wrong_self_convention)] + fn is_ldh(self) -> bool; +} + +impl CharCheck for char { + fn is_ldh(self) -> bool { + matches!(self, 'A'..='Z' | 'a'..='z' | '0'..='9' | '-') + } +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use rstest::rstest; + + use crate::check::string::{CharCheck, StringListCheck}; + + use super::StringCheck; + + #[rstest] + #[case("foo", false)] + #[case("", true)] + #[case(" ", true)] + #[case("foo bar", false)] + fn GIVEN_string_WHEN_is_whitespace_or_empty_THEN_correct_result( + #[case] test_string: &str, + #[case] expected: bool, + ) { + // GIVEN in parameters + + // WHEN + let actual = test_string.is_whitespace_or_empty(); + + // THEN + assert_eq!(actual, expected); + } + + #[rstest] + #[case(&[], true)] + #[case(&["foo"], false)] + #[case(&["foo",""], true)] + #[case(&["foo","bar"], false)] + #[case(&["foo","bar baz"], false)] + #[case(&[""], true)] + #[case(&[" "], true)] + fn GIVEN_string_list_WHEN_is_whitespace_or_empty_THEN_correct_result( + #[case] test_list: &[&str], + #[case] expected: bool, + ) { + // GIVEN in parameters + + // WHEN + let actual = test_list.is_empty_or_any_empty_or_whitespace(); + + // THEN + assert_eq!(actual, expected); + } + + #[rstest] + #[case('a', true)] + #[case('l', true)] + #[case('z', true)] + #[case('A', true)] + #[case('L', true)] + #[case('Z', true)] + #[case('0', true)] + #[case('3', true)] + #[case('9', true)] + #[case('-', true)] + #[case('_', false)] + #[case('.', false)] + fn GIVEN_char_WHEN_is_ldh_THEN_correct_result(#[case] test_char: char, #[case] expected: bool) { + // GIVEN in parameters + + // WHEN + let actual = test_char.is_ldh(); + + // THEN + assert_eq!(actual, expected); + } + + #[rstest] + #[case("foo", true)] + #[case("", false)] + #[case("foo-bar", true)] + #[case("foo bar", false)] + fn GIVEN_string_WHEN_is_ldh_string_THEN_correct_result( + #[case] test_string: &str, + #[case] expected: bool, + ) { + // GIVEN in parameters + + // WHEN + let actual = test_string.is_ldh_string(); + + // THEN + assert_eq!(actual, expected); + } + + #[rstest] + #[case("foo", false)] + #[case("", false)] + #[case("foo-bar", false)] + #[case("foo bar", false)] + #[case(".", false)] + #[case(".foo.bar", false)] + #[case(".foo", true)] + fn GIVEN_string_WHEN_is_tld_THEN_correct_result( + #[case] test_string: &str, + #[case] expected: bool, + ) { + // GIVEN in parameters + + // WHEN + let actual = test_string.is_tld(); + + // THEN + assert_eq!(actual, expected); + } + + #[rstest] + #[case(&[], false)] + #[case(&["foo"], true)] + #[case(&["foo",""], false)] + #[case(&["foo","bar"], true)] + #[case(&["foo","bar baz"], false)] + #[case(&[""], false)] + #[case(&[" "], false)] + fn GIVEN_string_list_WHEN_is_ldh_string_list_THEN_correct_result( + #[case] test_list: &[&str], + #[case] expected: bool, + ) { + // GIVEN in parameters + + // WHEN + let actual = test_list.is_ldh_string_list(); + + // THEN + assert_eq!(actual, expected); + } + + #[rstest] + #[case("foo", true)] + #[case("", false)] + #[case(".", true)] + #[case("foo.bar", true)] + #[case("foo.bar.", true)] + fn GIVEN_string_WHEN_is_ldh_domain_name_THEN_correct_result( + #[case] test_string: &str, + #[case] expected: bool, + ) { + // GIVEN in parameters + + // WHEN + let actual = test_string.is_ldh_domain_name(); + + // THEN + assert_eq!(actual, expected); + } + + #[rstest] + #[case("foo", true)] + #[case("", false)] + #[case(".", true)] + #[case("foo.bar", true)] + #[case("foo.bar.", true)] + #[case("fo_o.bar.", false)] + #[case("fo o.bar.", false)] + fn GIVEN_string_WHEN_is_unicode_domain_name_THEN_correct_result( + #[case] test_string: &str, + #[case] expected: bool, + ) { + // GIVEN in parameters + + // WHEN + let actual = test_string.is_unicode_domain_name(); + + // THEN + assert_eq!(actual, expected); + } +} diff --git a/icann-rdap-common/src/check/types.rs b/icann-rdap-common/src/check/types.rs new file mode 100644 index 0000000..8697838 --- /dev/null +++ b/icann-rdap-common/src/check/types.rs @@ -0,0 +1,1011 @@ +use { + crate::prelude::ObjectCommon, + std::{any::TypeId, str::FromStr, sync::LazyLock}, +}; + +use { + crate::{ + media_types::RDAP_MEDIA_TYPE, + prelude::Common, + response::{ + autnum::Autnum, + domain::Domain, + entity::Entity, + nameserver::Nameserver, + network::Network, + types::{ + ExtensionId, Link, Links, NoticeOrRemark, Notices, PublicIds, RdapConformance, + Remarks, + }, + }, + }, + chrono::DateTime, +}; + +use super::{ + string::{StringCheck, StringListCheck}, + Check, CheckItem, CheckParams, Checks, GetChecks, GetSubChecks, +}; + +impl GetChecks for RdapConformance { + fn get_checks(&self, params: CheckParams) -> Checks { + let mut items = vec![]; + if params.parent_type != params.root.get_type() { + items.push(Check::RdapConformanceInvalidParent.check_item()) + }; + for ext in self { + if !params.allow_unreg_ext { + let id = ExtensionId::from_str(ext); + if id.is_err() { + items.push(Check::UnknownExtention.check_item()) + } + } + } + Checks { + rdap_struct: super::RdapStructure::RdapConformance, + items, + sub_checks: vec![], + } + } +} + +impl GetChecks for Links { + fn get_checks(&self, params: CheckParams) -> Checks { + let mut sub_checks: Vec = vec![]; + if params.do_subchecks { + self.iter() + .for_each(|link| sub_checks.push(link.get_checks(params))); + } + Checks { + rdap_struct: super::RdapStructure::Links, + items: vec![], + sub_checks, + } + } +} + +static RELATED_AND_SELF_LINK_PARENTS: LazyLock> = LazyLock::new(|| { + vec![ + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + ] +}); + +impl GetChecks for Link { + fn get_checks(&self, params: CheckParams) -> Checks { + let mut items: Vec = vec![]; + if self.value.is_none() { + items.push(Check::LinkMissingValueProperty.check_item()) + }; + if self.href.is_none() { + items.push(Check::LinkMissingHrefProperty.check_item()) + }; + if let Some(rel) = &self.rel { + if rel.eq("related") { + if let Some(media_type) = &self.media_type { + if !media_type.eq(RDAP_MEDIA_TYPE) + && RELATED_AND_SELF_LINK_PARENTS.contains(¶ms.parent_type) + { + items.push(Check::LinkRelatedIsNotRdap.check_item()) + } + } else { + items.push(Check::LinkRelatedHasNoType.check_item()) + } + } else if rel.eq("self") { + if let Some(media_type) = &self.media_type { + if !media_type.eq(RDAP_MEDIA_TYPE) { + items.push(Check::LinkSelfIsNotRdap.check_item()) + } + } else { + items.push(Check::LinkSelfHasNoType.check_item()) + } + } else if RELATED_AND_SELF_LINK_PARENTS.contains(¶ms.parent_type) && + // because some registries do not model nameservers directly, + // they can be embedded in other objects but aren't first class + // objects themself (see RIR example in RFC 9083). Therefore, + // it only matters that a nameserver has no self link if it is + // the top most object (i.e. a first class object). + params.root.get_type() != TypeId::of::() + { + items.push(Check::LinkObjectClassHasNoSelf.check_item()) + } + } else { + items.push(Check::LinkMissingRelProperty.check_item()) + } + Checks { + rdap_struct: super::RdapStructure::Link, + items, + sub_checks: vec![], + } + } +} + +impl GetChecks for Notices { + fn get_checks(&self, params: CheckParams) -> Checks { + let mut sub_checks: Vec = vec![]; + if params.do_subchecks { + self.iter() + .for_each(|note| sub_checks.push(note.0.get_checks(params))); + } + Checks { + rdap_struct: super::RdapStructure::Notices, + items: vec![], + sub_checks, + } + } +} + +impl GetChecks for Remarks { + fn get_checks(&self, params: CheckParams) -> Checks { + let mut sub_checks: Vec = vec![]; + if params.do_subchecks { + self.iter() + .for_each(|remark| sub_checks.push(remark.0.get_checks(params))); + } + Checks { + rdap_struct: super::RdapStructure::Remarks, + items: vec![], + sub_checks, + } + } +} + +impl GetChecks for NoticeOrRemark { + fn get_checks(&self, params: CheckParams) -> Checks { + let mut items: Vec = vec![]; + if let Some(description) = &self.description { + if description.is_string() { + items.push(Check::NoticeOrRemarkDescriptionIsString.check_item()) + } + } else { + items.push(Check::NoticeOrRemarkDescriptionIsAbsent.check_item()) + }; + let mut sub_checks: Vec = vec![]; + if params.do_subchecks { + if let Some(links) = &self.links { + links.iter().for_each(|link| { + sub_checks + .push(link.get_checks(params.from_parent(TypeId::of::()))) + }); + }; + }; + // TODO checks on 'type' + Checks { + rdap_struct: super::RdapStructure::NoticeOrRemark, + items, + sub_checks, + } + } +} + +impl GetSubChecks for PublicIds { + fn get_sub_checks(&self, _params: CheckParams) -> Vec { + let mut sub_checks: Vec = vec![]; + self.iter().for_each(|pid| { + if pid.id_type.is_none() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::PublidIds, + items: vec![Check::PublicIdTypeIsAbsent.check_item()], + sub_checks: vec![], + }); + } + if pid.identifier.is_none() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::PublidIds, + items: vec![Check::PublicIdIdentifierIsAbsent.check_item()], + sub_checks: vec![], + }); + } + }); + sub_checks + } +} + +impl GetSubChecks for Common { + fn get_sub_checks(&self, params: CheckParams) -> Vec { + let mut sub_checks: Vec = vec![]; + if params.do_subchecks { + if let Some(rdap_conformance) = &self.rdap_conformance { + sub_checks.push(rdap_conformance.get_checks(params)) + }; + if let Some(notices) = &self.notices { + sub_checks.push(notices.get_checks(params)) + }; + }; + if params.parent_type == params.root.get_type() && self.rdap_conformance.is_none() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::RdapConformance, + items: vec![Check::RdapConformanceMissing.check_item()], + sub_checks: vec![], + }); + } + sub_checks + } +} + +impl GetSubChecks for ObjectCommon { + fn get_sub_checks(&self, params: CheckParams) -> Vec { + let mut sub_checks: Vec = vec![]; + + // entities + if params.do_subchecks { + if let Some(entities) = &self.entities { + entities + .iter() + .for_each(|e| sub_checks.push(e.get_checks(params))) + }; + } + + // links + if let Some(links) = &self.links { + sub_checks.push(links.get_checks(params)); + } else if params.root.get_type() != TypeId::of::() + && params.parent_type != TypeId::of::() + // because some registries do not model nameservers directly, + // they can be embedded in other objects but aren't first class + // objects themself (see RIR example in RFC 9083). Therefore, + // it only matters that a nameserver has no self link if it is + // the top most object (i.e. a first class object). + { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::Links, + items: vec![Check::LinkObjectClassHasNoSelf.check_item()], + sub_checks: vec![], + }) + }; + + // remarks + if let Some(remarks) = &self.remarks { + sub_checks.push(remarks.get_checks(params)) + }; + + // events + if let Some(events) = &self.events { + events.iter().for_each(|e| { + if let Some(date) = &e.event_date { + let date = DateTime::parse_from_rfc3339(date); + if date.is_err() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::Events, + items: vec![Check::EventDateIsNotRfc3339.check_item()], + sub_checks: vec![], + }) + } + } else { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::Events, + items: vec![Check::EventDateIsAbsent.check_item()], + sub_checks: vec![], + }) + } + if e.event_action.is_none() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::Events, + items: vec![Check::EventActionIsAbsent.check_item()], + sub_checks: vec![], + }) + } + }); + } + + // handle + if let Some(handle) = &self.handle { + if handle.is_whitespace_or_empty() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::Handle, + items: vec![Check::HandleIsEmpty.check_item()], + sub_checks: vec![], + }) + } + } + + // Status + if let Some(status) = &self.status { + // TODO add check for status is string + let status = status.vec(); + if status.is_empty_or_any_empty_or_whitespace() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::Status, + items: vec![Check::StatusIsEmpty.check_item()], + sub_checks: vec![], + }) + } + } + + // Port 43 + if let Some(port43) = &self.port_43 { + if port43.is_whitespace_or_empty() { + sub_checks.push(Checks { + rdap_struct: super::RdapStructure::Port43, + items: vec![Check::Port43IsEmpty.check_item()], + sub_checks: vec![], + }) + } + } + + sub_checks + } +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use rstest::rstest; + + use crate::{ + check::Checks, + prelude::{ToResponse, VectorStringish}, + response::{ + domain::Domain, + nameserver::Nameserver, + types::{Event, Link, Notice, NoticeOrRemark, PublicId, Remark}, + RdapResponse, + }, + }; + + use crate::check::{Check, CheckParams, GetChecks}; + + #[test] + fn check_link_with_no_rel_property() { + // GIVEN + let rdap = Domain::builder() + .ldh_name("example.com") + .link( + Link::illegal() + .href("https://foo") + .value("https://foo") + .build(), + ) + .build() + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + checks + .sub(crate::check::RdapStructure::Links) + .expect("Links not found") + .sub(crate::check::RdapStructure::Link) + .expect("Link not found") + .items + .iter() + .find(|c| c.check == Check::LinkMissingRelProperty) + .expect("link rel missing check"); + } + + #[test] + fn check_link_with_no_val_property() { + // GIVEN + let rdap = Domain::builder() + .ldh_name("example.com") + .link(Link::illegal().href("https://foo").rel("about").build()) + .build() + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + checks + .sub(crate::check::RdapStructure::Links) + .expect("Links not found") + .sub(crate::check::RdapStructure::Link) + .expect("Link not found") + .items + .iter() + .find(|c| c.check == Check::LinkMissingValueProperty) + .expect("link missing check"); + } + + #[test] + fn check_link_with_no_href_property() { + // GIVEN + let rdap = Domain::builder() + .ldh_name("example.com") + .link(Link::illegal().value("https://foo").rel("about").build()) + .build() + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + checks + .sub(crate::check::RdapStructure::Links) + .expect("Links not found") + .sub(crate::check::RdapStructure::Link) + .expect("Link not found") + .items + .iter() + .find(|c| c.check == Check::LinkMissingHrefProperty) + .expect("link missing check"); + } + + #[test] + fn test_related_link_with_no_type_property() { + // GIVEN + let rdap = Domain::builder() + .ldh_name("example.com") + .link( + Link::builder() + .href("https://foo") + .value("https://foo") + .rel("related") + .build(), + ) + .build() + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + checks + .sub(crate::check::RdapStructure::Links) + .expect("Links not found") + .sub(crate::check::RdapStructure::Link) + .expect("Link not found") + .items + .iter() + .find(|c| c.check == Check::LinkRelatedHasNoType) + .expect("link missing check"); + } + + #[test] + fn test_object_related_link_with_non_rdap_type() { + // GIVEN + let rdap = Domain::builder() + .ldh_name("example.com") + .link( + Link::builder() + .href("https://foo") + .value("https://foo") + .rel("related") + .media_type("foo") + .build(), + ) + .build() + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + checks + .sub(crate::check::RdapStructure::Links) + .expect("Links not found") + .sub(crate::check::RdapStructure::Link) + .expect("Link not found") + .items + .iter() + .find(|c| c.check == Check::LinkRelatedIsNotRdap) + .expect("link missing check"); + } + + #[test] + fn test_self_link_with_no_type_property() { + // GIVEN + let rdap = Domain::builder() + .ldh_name("example.com") + .link( + Link::builder() + .href("https://foo") + .value("https://foo") + .rel("self") + .build(), + ) + .build() + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + checks + .sub(crate::check::RdapStructure::Links) + .expect("Links not found") + .sub(crate::check::RdapStructure::Link) + .expect("Link not found") + .items + .iter() + .find(|c| c.check == Check::LinkSelfHasNoType) + .expect("link missing check"); + } + + #[test] + fn test_self_link_with_non_rdap_type() { + // GIVEN + let rdap = Domain::builder() + .ldh_name("example.com") + .link( + Link::builder() + .href("https://foo") + .value("https://foo") + .rel("self") + .media_type("foo") + .build(), + ) + .build() + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + assert!(find_any_check(&checks, Check::LinkSelfIsNotRdap)); + } + + #[test] + fn test_domain_with_self_link() { + // GIVEN + let rdap = Domain::builder() + .ldh_name("example.com") + .link( + Link::builder() + .href("https://foo") + .value("https://foo") + .rel("self") + .media_type("application/rdap+json") + .build(), + ) + .build() + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(!find_any_check(&checks, Check::LinkObjectClassHasNoSelf)); + } + + #[test] + fn test_nameserver_with_self_link() { + // GIVEN + let rdap = Nameserver::builder() + .ldh_name("example.com") + .link( + Link::builder() + .href("https://foo") + .value("https://foo") + .rel("self") + .media_type("application/rdap+json") + .build(), + ) + .build() + .expect("unable to build nameserver") + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + assert!(!find_any_check(&checks, Check::LinkObjectClassHasNoSelf)); + } + + #[test] + /// Issue #59 + fn test_nameserver_with_self_link_and_notice() { + // GIVEN + let rdap = Nameserver::builder() + .ldh_name("example.com") + .notice(Notice( + NoticeOrRemark::builder() + .description_entry("a notice") + .link( + Link::builder() + .href("https://tos") + .value("https://tos") + .rel("terms-of-service") + .media_type("text/html") + .build(), + ) + .build(), + )) + .link( + Link::builder() + .href("https://foo") + .value("https://foo") + .rel("self") + .media_type("application/rdap+json") + .build(), + ) + .build() + .expect("build nameserver") + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(!find_any_check(&checks, Check::LinkObjectClassHasNoSelf)); + } + + #[test] + /// Issue #59 + fn test_nameserver_with_self_link_and_remark() { + // GIVEN + let rdap = Nameserver::builder() + .ldh_name("exapmle.com") + .remark(Remark( + NoticeOrRemark::builder() + .description_entry("a notice") + .links(vec![Link::builder() + .href("https://tos") + .value("https://tos") + .rel("terms-of-service") + .media_type("text/html") + .build()]) + .build(), + )) + .link( + Link::builder() + .href("https://foo") + .value("https://foo") + .rel("self") + .media_type("application/rdap+json") + .build(), + ) + .build() + .expect("building nameserver") + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + assert!(!find_any_check(&checks, Check::LinkObjectClassHasNoSelf)); + } + + #[test] + fn test_domain_with_no_self_link() { + // GIVEN + let rdap = Domain::builder() + .ldh_name("example.com") + .link( + Link::builder() + .href("https://foo") + .value("https://foo") + .rel("no_self") + .media_type("foo") + .build(), + ) + .build() + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + checks + .sub(crate::check::RdapStructure::Links) + .expect("Links not found") + .sub(crate::check::RdapStructure::Link) + .expect("Link not found") + .items + .iter() + .find(|c| c.check == Check::LinkObjectClassHasNoSelf) + .expect("link missing check"); + } + + #[test] + fn test_domain_with_no_links() { + // GIVEN + let rdap = Domain::builder() + .ldh_name("example.com") + .build() + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&rdap); + checks + .sub(crate::check::RdapStructure::Links) + .expect("Links not found") + .items + .iter() + .find(|c| c.check == Check::LinkObjectClassHasNoSelf) + .expect("link missing check"); + } + + #[test] + fn test_event_with_no_date() { + // GIVEN + let rdap = Domain::builder() + .ldh_name("example.com") + .event(Event::illegal().event_action("foo").build()) + .build() + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + checks + .sub(crate::check::RdapStructure::Events) + .expect("Events not found") + .items + .iter() + .find(|c| c.check == Check::EventDateIsAbsent) + .expect("event missing check"); + } + + #[test] + fn test_event_with_no_action() { + // GIVEN + let rdap = Domain::builder() + .ldh_name("example.com") + .event(Event::illegal().event_date("1990-12-31T23:59:59Z").build()) + .build() + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + checks + .sub(crate::check::RdapStructure::Events) + .expect("Events not found") + .items + .iter() + .find(|c| c.check == Check::EventActionIsAbsent) + .expect("event missing check"); + } + + #[test] + fn test_event_with_bad_date() { + // GIVEN + let rdap = Domain::builder() + .ldh_name("example.com") + .event( + Event::builder() + .event_action("foo") + .event_date("bar") + .build(), + ) + .build() + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + checks + .sub(crate::check::RdapStructure::Events) + .expect("Events not found") + .items + .iter() + .find(|c| c.check == Check::EventDateIsNotRfc3339) + .expect("event missing check"); + } + + #[test] + fn test_public_id_with_no_type() { + // GIVEN + let rdap = Domain::builder() + .ldh_name("example.com") + .public_id(PublicId::illegal().identifier("thing").build()) + .build() + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + checks + .sub(crate::check::RdapStructure::PublidIds) + .expect("Public Ids not found") + .items + .iter() + .find(|c| c.check == Check::PublicIdTypeIsAbsent) + .expect("public id missing check"); + } + + #[test] + fn test_public_id_with_no_identifier() { + // GIVEN + let rdap = Domain::builder() + .ldh_name("example.com") + .public_id(PublicId::illegal().id_type("thing").build()) + .build() + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + checks + .sub(crate::check::RdapStructure::PublidIds) + .expect("Public Ids not found") + .items + .iter() + .find(|c| c.check == Check::PublicIdIdentifierIsAbsent) + .expect("public id missing check"); + } + + #[test] + fn test_notice_with_no_description() { + // GIVEN + let notice = NoticeOrRemark { + title: None, + description: None, + links: None, + nr_type: None, + }; + let rdap = Domain::builder() + .ldh_name("example.com") + .notice(Notice(notice)) + .build() + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + dbg!(&checks); + checks + .sub(crate::check::RdapStructure::Notices) + .expect("Notices not found") + .sub(crate::check::RdapStructure::NoticeOrRemark) + .expect("Notice/Remark not found") + .items + .iter() + .find(|c| c.check == Check::NoticeOrRemarkDescriptionIsAbsent) + .expect("description missing check"); + } + + #[test] + fn test_nameserver_with_no_links() { + // GIVEN + let rdap = Nameserver::builder() + .ldh_name("example.com") + .build() + .expect("building nameserver") + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + assert!(checks.sub(crate::check::RdapStructure::Links).is_none()); + } + + #[test] + fn test_nameserver_with_no_self_links() { + // GIVEN + let rdap = Nameserver::builder() + .ldh_name("example.com") + .link( + Link::builder() + .href("https://foo") + .value("https://foo") + .rel("no_self") + .media_type("foo") + .build(), + ) + .build() + .expect("building nameserver") + .to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + assert!(!checks + .sub(crate::check::RdapStructure::Links) + .expect("Links not found") + .items + .iter() + .any(|c| c.check == Check::LinkObjectClassHasNoSelf)); + } + + #[rstest] + #[case(vec![])] + #[case(vec!["".to_string()])] + #[case(vec![" ".to_string()])] + #[case(vec![" ".to_string(), "foo".to_string()])] + #[test] + fn test_nameserver_with_empty_status(#[case] status: Vec) { + // GIVEN + + let mut ns = Nameserver::builder() + .ldh_name("ns1.example.com") + .build() + .unwrap(); + ns.object_common.status = Some(VectorStringish::from(status)); + let rdap = ns.to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + assert!(checks + .sub(crate::check::RdapStructure::Status) + .expect("status not found") + .items + .iter() + .any(|c| c.check == Check::StatusIsEmpty)); + } + + #[rstest] + #[case("")] + #[case(" ")] + #[test] + fn test_nameserver_with_empty_handle(#[case] handle: &str) { + // GIVEN + let mut ns = Nameserver::builder() + .ldh_name("ns1.example.com") + .build() + .unwrap(); + ns.object_common.handle = Some(handle.to_string()); + let rdap = ns.to_response(); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + assert!(checks + .sub(crate::check::RdapStructure::Handle) + .expect("handle not found") + .items + .iter() + .any(|c| c.check == Check::HandleIsEmpty)); + } + + #[test] + fn test_rdap_conformance_not_in_root() { + // GIVEN + let json = r#" + { + "rdapConformance": ["rdap_level_0"], + "objectClassName" : "domain", + "handle" : "XXXX", + "ldhName" : "xn--fo-5ja.example", + "entities" : + [ + { + "rdapConformance": ["rdap_level_0"], + "objectClassName" : "entity", + "handle" : "XXXX" + } + ] + } + "#; + let rdap = serde_json::from_str::(json).expect("parsing JSON"); + + // WHEN + let checks = rdap.get_checks(CheckParams::for_rdap(&rdap)); + + // THEN + checks + .sub(crate::check::RdapStructure::Entity) + .expect("entity not found") + .sub(crate::check::RdapStructure::RdapConformance) + .expect("rdap conformance not found") + .items + .iter() + .find(|c| c.check == Check::RdapConformanceInvalidParent) + .expect("check missing"); + } + + fn find_any_check(checks: &Checks, check_type: Check) -> bool { + if checks.items.iter().any(|c| c.check == check_type) { + return true; + } + if checks + .sub_checks + .iter() + .any(|c| find_any_check(c, check_type)) + { + return true; + } + false + } +} diff --git a/icann-rdap-common/src/contact/from_vcard.rs b/icann-rdap-common/src/contact/from_vcard.rs new file mode 100644 index 0000000..f557d46 --- /dev/null +++ b/icann-rdap-common/src/contact/from_vcard.rs @@ -0,0 +1,846 @@ +//! Convert jCard/vCard to Contact. +use serde_json::Value; + +use super::{Contact, Email, Lang, NameParts, Phone, PostalAddress}; + +impl Contact { + /// Creates a Contact from an array of [`Value`]s. + /// + /// ```rust + /// use icann_rdap_common::contact::Contact; + /// use serde::Deserialize; + /// use serde_json::Value; + /// + /// let json = r#" + /// [ + /// "vcard", + /// [ + /// ["version", {}, "text", "4.0"], + /// ["fn", {}, "text", "Joe User"], + /// ["kind", {}, "text", "individual"], + /// ["org", { + /// "type":"work" + /// }, "text", "Example"], + /// ["title", {}, "text", "Research Scientist"], + /// ["role", {}, "text", "Project Lead"], + /// ["adr", + /// { "type":"work" }, + /// "text", + /// [ + /// "", + /// "Suite 1234", + /// "4321 Rue Somewhere", + /// "Quebec", + /// "QC", + /// "G1V 2M2", + /// "Canada" + /// ] + /// ], + /// ["tel", + /// { "type":["work", "voice"], "pref":"1" }, + /// "uri", "tel:+1-555-555-1234;ext=102" + /// ], + /// ["email", + /// { "type":"work" }, + /// "text", "joe.user@example.com" + /// ] + /// ] + /// ]"#; + /// + /// let data: Vec = serde_json::from_str(json).unwrap(); + /// let contact = Contact::from_vcard(&data); + /// ``` + pub fn from_vcard(vcard_array: &[Value]) -> Option { + // value should be "vcard" followed by array + let value = vcard_array.first()?; + let vcard_literal = value.as_str()?; + if !vcard_literal.eq_ignore_ascii_case("vcard") { + return None; + }; + let vcard = vcard_array.get(1)?; + let vcard = vcard.as_array()?; + + let contact = Contact::builder() + .and_full_name(vcard.find_property("fn").get_text()) + .and_kind(vcard.find_property("kind").get_text()) + .titles(vcard.find_properties("title").get_texts().unwrap_or(vec![])) + .roles(vcard.find_properties("role").get_texts().unwrap_or(vec![])) + .nick_names( + vcard + .find_properties("nickname") + .get_texts() + .unwrap_or(vec![]), + ) + .organization_names(vcard.find_properties("org").get_texts().unwrap_or(vec![])) + .langs(vcard.find_properties("lang").get_langs().unwrap_or(vec![])) + .emails( + vcard + .find_properties("email") + .get_emails() + .unwrap_or(vec![]), + ) + .phones(vcard.find_properties("tel").get_phones().unwrap_or(vec![])) + .postal_addresses( + vcard + .find_properties("adr") + .get_postal_addresses() + .unwrap_or(vec![]), + ) + .and_name_parts(vcard.find_property("n").get_name_parts()) + .contact_uris( + vcard + .find_properties("contact-uri") + .get_texts() + .unwrap_or(vec![]), + ) + .urls(vcard.find_properties("url").get_texts().unwrap_or(vec![])) + .build(); + + contact.is_non_empty().then_some(contact) + } +} + +trait FindProperty<'a> { + fn find_property(self, name: &'a str) -> Option<&'a Vec>; +} + +impl<'a> FindProperty<'a> for &'a [Value] { + fn find_property(self, name: &'a str) -> Option<&'a Vec> { + self.iter() + .filter_map(|prop_array| prop_array.as_array()) + .find(|prop_array| { + if let Some(prop_name) = prop_array.first() { + if let Some(prop_name) = prop_name.as_str() { + prop_name.eq_ignore_ascii_case(name) + } else { + false + } + } else { + false + } + }) + } +} + +trait FindProperties<'a> { + fn find_properties(self, name: &'a str) -> Vec<&'a Vec>; +} + +impl<'a> FindProperties<'a> for &'a [Value] { + fn find_properties(self, name: &'a str) -> Vec<&'a Vec> { + self.iter() + .filter_map(|prop_array| prop_array.as_array()) + .filter(|prop_array| { + if let Some(prop_name) = prop_array.first() { + if let Some(prop_name) = prop_name.as_str() { + prop_name.eq_ignore_ascii_case(name) + } else { + false + } + } else { + false + } + }) + .collect() + } +} + +trait GetText<'a> { + fn get_text(self) -> Option; +} + +impl<'a> GetText<'a> for Option<&'a Vec> { + fn get_text(self) -> Option { + let values = self?; + let fourth = values.get(3)?; + fourth.as_str().map(|s| s.to_owned()) + } +} + +impl<'a> GetText<'a> for &'a Vec { + fn get_text(self) -> Option { + let fourth = self.get(3)?; + fourth.as_str().map(|s| s.to_owned()) + } +} + +trait GetTexts<'a> { + fn get_texts(self) -> Option>; +} + +impl<'a> GetTexts<'a> for &'a [&'a Vec] { + fn get_texts(self) -> Option> { + let texts = self + .iter() + .filter_map(|prop| (*prop).get_text()) + .collect::>(); + (!texts.is_empty()).then_some(texts) + } +} + +trait GetPreference<'a> { + fn get_preference(self) -> Option; +} + +impl<'a> GetPreference<'a> for &'a Vec { + fn get_preference(self) -> Option { + let second = self.get(1)?; + let second = second.as_object()?; + let preference = second.get("pref")?; + preference.as_str().and_then(|s| s.parse().ok()) + } +} + +trait GetLabel<'a> { + fn get_label(self) -> Option; +} + +impl<'a> GetLabel<'a> for &'a Vec { + fn get_label(self) -> Option { + let second = self.get(1)?; + let second = second.as_object()?; + let label = second.get("label")?; + label.as_str().map(|s| s.to_owned()) + } +} + +const CONTEXTS: [&str; 6] = ["home", "work", "office", "private", "mobile", "cell"]; + +trait GetContexts<'a> { + fn get_contexts(self) -> Option>; +} + +impl<'a> GetContexts<'a> for &'a Vec { + fn get_contexts(self) -> Option> { + let second = self.get(1)?; + let second = second.as_object()?; + let contexts = second.get("type")?; + if let Some(context) = contexts.as_str() { + let context = context.to_lowercase(); + if CONTEXTS.contains(&context.as_str()) { + return Some(vec![context]); + } else { + return None; + } + }; + let contexts = contexts.as_array()?; + let contexts = contexts + .iter() + .filter_map(|v| v.as_str()) + .map(|s| s.to_lowercase()) + .filter(|s| CONTEXTS.contains(&s.as_str())) + .collect::>(); + (!contexts.is_empty()).then_some(contexts) + } +} + +trait GetFeatures<'a> { + fn get_features(self) -> Option>; +} + +impl<'a> GetFeatures<'a> for &'a Vec { + fn get_features(self) -> Option> { + let second = self.get(1)?; + let second = second.as_object()?; + let features = second.get("type")?; + if let Some(feature) = features.as_str() { + let feature = feature.to_lowercase(); + if !CONTEXTS.contains(&feature.as_str()) { + return Some(vec![feature]); + } else { + return None; + } + }; + let features = features.as_array()?; + let features = features + .iter() + .filter_map(|v| v.as_str()) + .map(|s| s.to_lowercase()) + .filter(|s| !CONTEXTS.contains(&s.as_str())) + .collect::>(); + (!features.is_empty()).then_some(features) + } +} + +trait GetLangs<'a> { + fn get_langs(self) -> Option>; +} + +impl<'a> GetLangs<'a> for &'a [&'a Vec] { + fn get_langs(self) -> Option> { + let langs = self + .iter() + .filter_map(|prop| { + let tag = (*prop).get_text()?; + let lang = Lang::builder() + .tag(tag) + .and_preference((*prop).get_preference()) + .build(); + Some(lang) + }) + .collect::>(); + (!langs.is_empty()).then_some(langs) + } +} + +trait GetEmails<'a> { + fn get_emails(self) -> Option>; +} + +impl<'a> GetEmails<'a> for &'a [&'a Vec] { + fn get_emails(self) -> Option> { + let emails = self + .iter() + .filter_map(|prop| { + let addr = (*prop).get_text()?; + let email = Email::builder() + .email(addr) + .contexts((*prop).get_contexts().unwrap_or_default()) + .and_preference((*prop).get_preference()) + .build(); + Some(email) + }) + .collect::>(); + (!emails.is_empty()).then_some(emails) + } +} + +trait GetPhones<'a> { + fn get_phones(self) -> Option>; +} + +impl<'a> GetPhones<'a> for &'a [&'a Vec] { + fn get_phones(self) -> Option> { + let phones = self + .iter() + .filter_map(|prop| { + let number = (*prop).get_text()?; + let phone = Phone::builder() + .phone(number) + .features((*prop).get_features().unwrap_or_default()) + .contexts((*prop).get_contexts().unwrap_or_default()) + .and_preference((*prop).get_preference()) + .build(); + Some(phone) + }) + .collect::>(); + (!phones.is_empty()).then_some(phones) + } +} + +trait GetPostalAddresses<'a> { + fn get_postal_addresses(self) -> Option>; +} + +impl<'a> GetPostalAddresses<'a> for &'a [&'a Vec] { + fn get_postal_addresses(self) -> Option> { + let addrs = self + .iter() + .map(|prop| { + let mut postal_code: Option = None; + let mut country_code: Option = None; + let mut country_name: Option = None; + let mut region_code: Option = None; + let mut region_name: Option = None; + let mut locality: Option = None; + let mut street_parts: Vec = vec![]; + if let Some(fourth) = prop.get(3) { + if let Some(addr) = fourth.as_array() { + // the jcard address fields are in a different index of the array. + // + // [ + // "adr", + // {}, + // "text", + // [ + // "Mail Stop 3", // post office box (not recommended for use) + // "Suite 3000", // apartment or suite (not recommended for use) + // "123 Maple Ave", // street address + // "Quebec", // locality or city name + // "QC", // region (can be either a code or full name) + // "G1V 2M2", // postal code + // "Canada" // full country name + // ] + // ], + if let Some(pobox) = addr.first() { + if let Some(s) = pobox.as_str() { + if !s.is_empty() { + street_parts.push(s.to_string()) + } + } + } + if let Some(appt) = addr.get(1) { + if let Some(s) = appt.as_str() { + if !s.is_empty() { + street_parts.push(s.to_string()) + } + } + } + if let Some(street) = addr.get(2) { + if let Some(s) = street.as_str() { + if !s.is_empty() { + street_parts.push(s.to_string()) + } + } else if let Some(arry_s) = street.as_array() { + arry_s + .iter() + .filter_map(|v| v.as_str()) + .filter(|s| !s.is_empty()) + .for_each(|s| street_parts.push(s.to_string())) + } + } + if let Some(city) = addr.get(3) { + if let Some(s) = city.as_str() { + if !s.is_empty() { + locality = Some(s.to_string()); + } + } + } + if let Some(region) = addr.get(4) { + if let Some(s) = region.as_str() { + if !s.is_empty() { + if s.len() == 2 && s.to_uppercase() == s { + region_code = Some(s.to_string()) + } else { + region_name = Some(s.to_string()) + } + } + } + } + if let Some(pc) = addr.get(5) { + if let Some(s) = pc.as_str() { + if !s.is_empty() { + postal_code = Some(s.to_string()); + } + } + } + if let Some(country) = addr.get(6) { + if let Some(s) = country.as_str() { + if !s.is_empty() { + if s.len() == 2 && s.to_uppercase() == s { + country_code = Some(s.to_string()) + } else { + country_name = Some(s.to_string()) + } + } + } + } + } + }; + let street_parts = (!street_parts.is_empty()).then_some(street_parts); + PostalAddress::builder() + .and_full_address((*prop).get_label()) + .contexts((*prop).get_contexts().unwrap_or_default()) + .and_preference((*prop).get_preference()) + .and_country_code(country_code) + .and_country_name(country_name) + .and_postal_code(postal_code) + .and_region_name(region_name) + .and_region_code(region_code) + .and_locality(locality) + .street_parts(street_parts.unwrap_or_default()) + .build() + }) + .collect::>(); + (!addrs.is_empty()).then_some(addrs) + } +} + +trait GetNameParts<'a> { + fn get_name_parts(self) -> Option; +} + +impl<'a> GetNameParts<'a> for Option<&'a Vec> { + fn get_name_parts(self) -> Option { + let values = self?; + let fourth = values.get(3)?; + let parts = fourth.as_array()?; + let mut iter = parts.iter().filter(|p| p.is_string() || p.is_array()); + let mut prefixes: Option> = None; + let mut surnames: Option> = None; + let mut given_names: Option> = None; + let mut middle_names: Option> = None; + let mut suffixes: Option> = None; + if let Some(e) = iter.next() { + surnames = get_string_or_vec(e); + }; + if let Some(e) = iter.next() { + given_names = get_string_or_vec(e); + }; + if let Some(e) = iter.next() { + middle_names = get_string_or_vec(e); + }; + if let Some(e) = iter.next() { + prefixes = get_string_or_vec(e); + }; + if let Some(e) = iter.next() { + suffixes = get_string_or_vec(e); + }; + let name_parts = NameParts::builder() + .surnames(surnames.unwrap_or_default()) + .prefixes(prefixes.unwrap_or_default()) + .given_names(given_names.unwrap_or_default()) + .middle_names(middle_names.unwrap_or_default()) + .suffixes(suffixes.unwrap_or_default()) + .build(); + if name_parts.surnames.is_none() + && name_parts.given_names.is_none() + && name_parts.middle_names.is_none() + && name_parts.suffixes.is_none() + && name_parts.prefixes.is_none() + { + None + } else { + Some(name_parts) + } + } +} + +fn get_string_or_vec(value: &Value) -> Option> { + if let Some(e) = value.as_str() { + if e.is_empty() { + return None; + } else { + return Some(vec![e.to_string()]); + }; + }; + if let Some(e) = value.as_array() { + let strings = e + .iter() + .filter_map(|e| e.as_str()) + .filter(|s| !s.is_empty()) + .map(|s| s.to_string()) + .collect::>(); + return (!strings.is_empty()).then_some(strings); + }; + None +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use serde_json::Value; + + use crate::contact::{Contact, NameParts}; + + #[test] + fn GIVEN_vcard_WHEN_from_vcard_THEN_properties_are_correct() { + // GIVEN + let vcard = r#" + [ + "vcard", + [ + ["version", {}, "text", "4.0"], + ["fn", {}, "text", "Joe User"], + ["n", {}, "text", + ["User", "Joe", "", "", ["ing. jr", "M.Sc."]] + ], + ["kind", {}, "text", "individual"], + ["lang", { + "pref":"1" + }, "language-tag", "fr"], + ["lang", { + "pref":"2" + }, "language-tag", "en"], + ["org", { + "type":"work" + }, "text", "Example"], + ["title", {}, "text", "Research Scientist"], + ["role", {}, "text", "Project Lead"], + ["adr", + { "type":"work" }, + "text", + [ + "", + "Suite 1234", + "4321 Rue Somewhere", + "Quebec", + "QC", + "G1V 2M2", + "Canada" + ] + ], + ["adr", + { + "type":"home", + "label":"123 Maple Ave\nSuite 90001\nVancouver\nBC\n1239\n" + }, + "text", + [ + "", "", "", "", "", "", "" + ] + ], + ["tel", + { + "type":["work", "voice"], + "pref":"1" + }, + "uri", + "tel:+1-555-555-1234;ext=102" + ], + ["tel", + { "type":["work", "cell", "voice", "video", "text"] }, + "uri", + "tel:+1-555-555-4321" + ], + ["email", + { "type":"work" }, + "text", + "joe.user@example.com" + ], + ["geo", { + "type":"work" + }, "uri", "geo:46.772673,-71.282945"], + ["key", + { "type":"work" }, + "uri", + "https://www.example.com/joe.user/joe.asc" + ], + ["tz", {}, + "utc-offset", "-05:00"], + ["contact-uri", {}, + "uri", + "https://example.com/contact-form" + ], + ["url", {}, + "uri", + "https://example.com/some-url" + ] + ] + ] + "#; + + // WHEN + let actual = serde_json::from_str::>(vcard); + + // THEN + let actual = actual.expect("parsing vcard"); + let actual = Contact::from_vcard(&actual).expect("vcard not found"); + + // full name + assert_eq!(actual.full_name.expect("full_name not found"), "Joe User"); + + // kind + assert_eq!(actual.kind.expect("kind not found"), "individual"); + + // titles + assert_eq!( + actual + .titles + .expect("no titles") + .first() + .expect("titles empty"), + "Research Scientist" + ); + + // roles + assert_eq!( + actual + .roles + .expect("no roles") + .first() + .expect("roles empty"), + "Project Lead" + ); + + // organization names + assert_eq!( + actual + .organization_names + .expect("no organization_names") + .first() + .expect("organization_names empty"), + "Example" + ); + + // nick names + assert!(actual.nick_names.is_none()); + + // langs + let Some(langs) = actual.langs else { + panic!("langs not found") + }; + assert_eq!(langs.len(), 2); + assert_eq!(langs.first().expect("first lang").tag, "fr"); + assert_eq!(langs.first().expect("first lang").preference, Some(1)); + assert_eq!(langs.get(1).expect("second lang").tag, "en"); + assert_eq!(langs.get(1).expect("second lang").preference, Some(2)); + + // emails + let Some(emails) = actual.emails else { + panic!("emails not found") + }; + let Some(email) = emails.first() else { + panic!("no email found") + }; + assert_eq!(email.email, "joe.user@example.com"); + assert!(email + .contexts + .as_ref() + .expect("contexts not found") + .contains(&"work".to_string())); + + // phones + let Some(phones) = actual.phones else { + panic!("no phones found") + }; + let Some(phone) = phones.first() else { + panic!("no first phone") + }; + assert_eq!(phone.phone, "tel:+1-555-555-1234;ext=102"); + assert!(phone + .contexts + .as_ref() + .expect("no contexts") + .contains(&"work".to_string())); + assert!(phone + .features + .as_ref() + .expect("no features") + .contains(&"voice".to_string())); + let Some(phone) = phones.last() else { + panic!("no last phone") + }; + assert_eq!(phone.phone, "tel:+1-555-555-4321"); + assert!(phone + .contexts + .as_ref() + .expect("no contexts") + .contains(&"cell".to_string())); + assert!(phone + .features + .as_ref() + .expect("no features") + .contains(&"video".to_string())); + + // postal addresses + let Some(addresses) = actual.postal_addresses else { + panic!("no postal addresses") + }; + let Some(addr) = addresses.first() else { + panic!("first address not found") + }; + assert!(addr + .contexts + .as_ref() + .expect("no contexts") + .contains(&"work".to_string())); + let Some(street_parts) = &addr.street_parts else { + panic!("no street parts") + }; + assert_eq!(street_parts.first().expect("street part 0"), "Suite 1234"); + assert_eq!( + street_parts.get(1).expect("street part 1"), + "4321 Rue Somewhere" + ); + assert_eq!(addr.country_name.as_ref().expect("country name"), "Canada"); + assert!(addr.country_code.is_none()); + assert_eq!(addr.region_code.as_ref().expect("region code"), "QC"); + assert!(addr.region_name.is_none()); + assert_eq!(addr.postal_code.as_ref().expect("postal code"), "G1V 2M2"); + let Some(addr) = addresses.last() else { + panic!("last address not found") + }; + assert!(addr + .contexts + .as_ref() + .expect("no contexts") + .contains(&"home".to_string())); + assert_eq!( + addr.full_address.as_ref().expect("full address not foudn"), + "123 Maple Ave\nSuite 90001\nVancouver\nBC\n1239\n" + ); + + // name parts + let Some(name_parts) = actual.name_parts else { + panic!("no name parts") + }; + let expected = NameParts::builder() + .surnames(vec!["User".to_string()]) + .given_names(vec!["Joe".to_string()]) + .suffixes(vec!["ing. jr".to_string(), "M.Sc.".to_string()]) + .build(); + assert_eq!(name_parts, expected); + + // contact-uris + assert_eq!( + actual + .contact_uris + .expect("no contact-uris") + .first() + .expect("contact-uris empty"), + "https://example.com/contact-form" + ); + + // urls + assert_eq!( + actual + .urls + .expect("no urls") + .first() + .expect("urls are empty"), + "https://example.com/some-url" + ); + } + + #[test] + fn GIVEN_vcard_with_addr_street_array_WHEN_from_vcard_THEN_properties_are_correct() { + // GIVEN + let vcard = r#" + [ + "vcard", + [ + ["version", {}, "text", "4.0"], + ["fn", {}, "text", "Joe User"], + ["adr", + { "type":"work" }, + "text", + [ + "", + "Suite 1234", + ["4321 Rue Blue", "1, Gawwn"], + "Quebec", + "QC", + "G1V 2M2", + "Canada" + ] + ] + ] + ] + "#; + + // WHEN + let actual = serde_json::from_str::>(vcard); + + // THEN + let actual = actual.expect("parsing vcard"); + let actual = Contact::from_vcard(&actual).expect("vcard not found"); + + // full name + assert_eq!(actual.full_name.expect("full_name not found"), "Joe User"); + + // postal addresses + let Some(addresses) = actual.postal_addresses else { + panic!("no postal addresses") + }; + let Some(addr) = addresses.first() else { + panic!("first address not found") + }; + assert!(addr + .contexts + .as_ref() + .expect("no contexts") + .contains(&"work".to_string())); + let Some(street_parts) = &addr.street_parts else { + panic!("no street parts") + }; + assert_eq!(street_parts.first().expect("street part 0"), "Suite 1234"); + assert_eq!(street_parts.get(1).expect("street part 1"), "4321 Rue Blue"); + assert_eq!(street_parts.get(2).expect("street part 2"), "1, Gawwn"); + assert_eq!(addr.country_name.as_ref().expect("country name"), "Canada"); + assert!(addr.country_code.is_none()); + assert_eq!(addr.region_code.as_ref().expect("region code"), "QC"); + assert!(addr.region_name.is_none()); + assert_eq!(addr.postal_code.as_ref().expect("postal code"), "G1V 2M2"); + } +} diff --git a/icann-rdap-common/src/contact/mod.rs b/icann-rdap-common/src/contact/mod.rs new file mode 100644 index 0000000..ff5d962 --- /dev/null +++ b/icann-rdap-common/src/contact/mod.rs @@ -0,0 +1,486 @@ +//! Easy representation of contact information found in an Entity. +//! +//! This module converts contact information to and from vCard/jCard, which is hard to +//! work with directly. It is also intended as a way of bridging the between vCard/jCard +//! and any new contact model. +//! +//! This struct can be built using the builder. +//! +//! ```rust +//! use icann_rdap_common::contact::Contact; +//! +//! let contact = Contact::builder() +//! .kind("individual") +//! .full_name("Bob Smurd") +//! .build(); +//! ``` +//! +//! Once built, a Contact struct can be converted to an array of [serde_json::Value]'s, +//! which can be used with serde to serialize to JSON. +//! +//! ```rust +//! use icann_rdap_common::contact::Contact; +//! use serde::Serialize; +//! use serde_json::Value; +//! +//! let contact = Contact::builder() +//! .kind("individual") +//! .full_name("Bob Smurd") +//! .build(); +//! +//! let v = contact.to_vcard(); +//! let json = serde_json::to_string(&v); +//! ``` +//! +//! To deserialize, use the `from_vcard` function. +//! +//! ```rust +//! use icann_rdap_common::contact::Contact; +//! use serde::Deserialize; +//! use serde_json::Value; +//! +//! let json = r#" +//! [ +//! "vcard", +//! [ +//! ["version", {}, "text", "4.0"], +//! ["fn", {}, "text", "Joe User"], +//! ["kind", {}, "text", "individual"], +//! ["org", { +//! "type":"work" +//! }, "text", "Example"], +//! ["title", {}, "text", "Research Scientist"], +//! ["role", {}, "text", "Project Lead"], +//! ["adr", +//! { "type":"work" }, +//! "text", +//! [ +//! "", +//! "Suite 1234", +//! "4321 Rue Somewhere", +//! "Quebec", +//! "QC", +//! "G1V 2M2", +//! "Canada" +//! ] +//! ], +//! ["tel", +//! { "type":["work", "voice"], "pref":"1" }, +//! "uri", "tel:+1-555-555-1234;ext=102" +//! ], +//! ["email", +//! { "type":"work" }, +//! "text", "joe.user@example.com" +//! ] +//! ] +//! ]"#; +//! +//! let data: Vec = serde_json::from_str(json).unwrap(); +//! let contact = Contact::from_vcard(&data); +//! ``` + +mod from_vcard; +mod to_vcard; + +use std::fmt::Display; + +use buildstructor::Builder; + +use crate::prelude::to_opt_vec; + +/// Represents a contact. This more closely represents an EPP Contact with some +/// things taken from JSContact. +/// +/// Using the builder to create the Contact: +/// ```rust +/// use icann_rdap_common::contact::Contact; +/// +/// let contact = Contact::builder() +/// .kind("individual") +/// .full_name("Bob Smurd") +/// .build(); +/// ``` +/// +/// +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Contact { + /// Preferred languages. + pub langs: Option>, + + /// The kind such as individual, company, etc... + pub kind: Option, + + /// Full name of the contact. + pub full_name: Option, + + /// Structured parts of the name. + pub name_parts: Option, + + /// Nick names. + pub nick_names: Option>, + + /// Titles. + pub titles: Option>, + + /// Organizational Roles + pub roles: Option>, + + /// Organization names. + pub organization_names: Option>, + + /// Postal addresses. + pub postal_addresses: Option>, + + /// Email addresses. + pub emails: Option>, + + /// Phone numbers. + pub phones: Option>, + + /// Contact URIs. + pub contact_uris: Option>, + + /// URLs + pub urls: Option>, +} + +#[buildstructor::buildstructor] +impl Contact { + #[builder(visibility = "pub")] + #[allow(clippy::too_many_arguments)] + fn new( + langs: Vec, + kind: Option, + full_name: Option, + name_parts: Option, + nick_names: Vec, + titles: Vec, + roles: Vec, + organization_names: Vec, + postal_addresses: Vec, + emails: Vec, + phones: Vec, + contact_uris: Vec, + urls: Vec, + ) -> Self { + Self { + langs: to_opt_vec(langs), + kind, + full_name, + name_parts, + nick_names: to_opt_vec(nick_names), + titles: to_opt_vec(titles), + roles: to_opt_vec(roles), + organization_names: to_opt_vec(organization_names), + postal_addresses: to_opt_vec(postal_addresses), + emails: to_opt_vec(emails), + phones: to_opt_vec(phones), + contact_uris: to_opt_vec(contact_uris), + urls: to_opt_vec(urls), + } + } + + /// Returns false if there is data in the Contact. + pub fn is_non_empty(&self) -> bool { + self.langs.is_some() + || self.kind.is_some() + || self.full_name.is_some() + || self.name_parts.is_some() + || self.nick_names.is_some() + || self.titles.is_some() + || self.roles.is_some() + || self.organization_names.is_some() + || self.postal_addresses.is_some() + || self.emails.is_some() + || self.phones.is_some() + || self.contact_uris.is_some() + || self.urls.is_some() + } + + /// Set the set of emails. + pub fn set_emails(mut self, emails: &[impl ToString]) -> Self { + let emails: Vec = emails + .iter() + .map(|e| Email::builder().email(e.to_string()).build()) + .collect(); + self.emails = (!emails.is_empty()).then_some(emails); + self + } + + /// Add a voice phone to the set of phones. + pub fn add_voice_phones(mut self, phones: &[impl ToString]) -> Self { + let mut phones: Vec = phones + .iter() + .map(|p| { + Phone::builder() + .contexts(vec!["voice".to_string()]) + .phone(p.to_string()) + .build() + }) + .collect(); + if let Some(mut self_phones) = self.phones.clone() { + phones.append(&mut self_phones); + } else { + self.phones = (!phones.is_empty()).then_some(phones); + } + self + } + + /// Add a facsimile phone to the set of phones. + pub fn add_fax_phones(mut self, phones: &[impl ToString]) -> Self { + let mut phones: Vec = phones + .iter() + .map(|p| { + Phone::builder() + .contexts(vec!["fax".to_string()]) + .phone(p.to_string()) + .build() + }) + .collect(); + if let Some(mut self_phones) = self.phones.clone() { + phones.append(&mut self_phones); + } else { + self.phones = (!phones.is_empty()).then_some(phones); + } + self + } + + /// Set the set of postal addresses to only be the passed in postal address. + pub fn set_postal_address(mut self, postal_address: PostalAddress) -> Self { + self.postal_addresses = Some(vec![postal_address]); + self + } +} + +/// The language preference of the contact. +#[derive(Debug, Builder, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Lang { + /// The ordinal of the preference for this language. + pub preference: Option, + + /// RFC 5646 language tag. + pub tag: String, +} + +impl Display for Lang { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(pref) = self.preference { + write!(f, "{} (pref: {})", self.tag, pref) + } else { + f.write_str(&self.tag) + } + } +} + +/// Name parts of a name. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct NameParts { + /// Name prefixes. + pub prefixes: Option>, + + /// Surnames or last names. + pub surnames: Option>, + + /// Middle names. + pub middle_names: Option>, + + /// Given or first names. + pub given_names: Option>, + + /// Name suffixes. + pub suffixes: Option>, +} + +#[buildstructor::buildstructor] +impl NameParts { + #[builder(visibility = "pub")] + #[allow(clippy::too_many_arguments)] + fn new( + prefixes: Vec, + surnames: Vec, + middle_names: Vec, + given_names: Vec, + suffixes: Vec, + ) -> Self { + Self { + prefixes: to_opt_vec(prefixes), + surnames: to_opt_vec(surnames), + middle_names: to_opt_vec(middle_names), + given_names: to_opt_vec(given_names), + suffixes: to_opt_vec(suffixes), + } + } +} + +/// A postal address. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct PostalAddress { + /// Preference of this address in relation to others. + pub preference: Option, + + /// Work, home, etc.... Known as "type" in JCard. + pub contexts: Option>, + + /// An unstructured address. An unstructured postal address is + /// usually the complete postal address. That is, this string + /// would contain the street address, country, region, postal code, etc... + /// + /// Depending on how the postal address is given, it can either + /// be structured or unstructured. If it is given as unstructured, + /// then this value is populated. + /// + /// It is possible that a single postal address is given as both, + /// in which case this value is populated along with the other + /// values of the postal address. + pub full_address: Option, + + /// Invidual street lines. + pub street_parts: Option>, + + /// City name, county name, etc... + pub locality: Option, + + /// Name of region (i.e. state, province, etc...). + pub region_name: Option, + + /// Code for region. + pub region_code: Option, + + /// Name of the country. + pub country_name: Option, + + /// Code of the country. + pub country_code: Option, + + /// Postal code. + pub postal_code: Option, +} + +#[buildstructor::buildstructor] +impl PostalAddress { + #[builder(visibility = "pub")] + #[allow(clippy::too_many_arguments)] + fn new( + preference: Option, + contexts: Vec, + full_address: Option, + street_parts: Vec, + locality: Option, + region_name: Option, + region_code: Option, + country_name: Option, + country_code: Option, + postal_code: Option, + ) -> Self { + Self { + preference, + contexts: to_opt_vec(contexts), + full_address, + street_parts: to_opt_vec(street_parts), + locality, + region_name, + region_code, + country_name, + country_code, + postal_code, + } + } +} + +/// Represents an email address. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Email { + /// Preference of this email in relation to others. + pub preference: Option, + + /// Work, home, etc.... Known as "type" in JCard. + pub contexts: Option>, + + /// The email address. + pub email: String, +} + +#[buildstructor::buildstructor] +impl Email { + #[builder(visibility = "pub")] + fn new(preference: Option, contexts: Vec, email: String) -> Self { + Self { + preference, + contexts: to_opt_vec(contexts), + email, + } + } +} + +impl Display for Email { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut qualifiers = vec![]; + if let Some(pref) = self.preference { + qualifiers.push(format!("(pref: {pref})")); + } + if let Some(contexts) = &self.contexts { + qualifiers.push(format!("({})", contexts.join(","))); + } + let qualifiers = qualifiers.join(" "); + if qualifiers.is_empty() { + f.write_str(&self.email) + } else { + write!(f, "{} {}", &self.email, qualifiers) + } + } +} + +/// Represents phone number. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Phone { + /// Preference of this phone in relation to others. + pub preference: Option, + + /// Work, home, etc.... Known as "type" in JCard. + pub contexts: Option>, + + /// The phone number. + pub phone: String, + + /// Features (voice, fax, etc...) + pub features: Option>, +} + +#[buildstructor::buildstructor] +impl Phone { + #[builder(visibility = "pub")] + fn new( + preference: Option, + contexts: Vec, + phone: String, + features: Vec, + ) -> Self { + Self { + preference, + contexts: to_opt_vec(contexts), + phone, + features: to_opt_vec(features), + } + } +} + +impl Display for Phone { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut qualifiers = vec![]; + if let Some(pref) = self.preference { + qualifiers.push(format!("(pref: {pref})")); + } + if let Some(contexts) = &self.contexts { + qualifiers.push(format!("({})", contexts.join(","))); + } + if let Some(features) = &self.features { + qualifiers.push(format!("({})", features.join(","))); + } + let qualifiers = qualifiers.join(" "); + if qualifiers.is_empty() { + f.write_str(&self.phone) + } else { + write!(f, "{} {}", &self.phone, qualifiers) + } + } +} diff --git a/icann-rdap-common/src/contact/to_vcard.rs b/icann-rdap-common/src/contact/to_vcard.rs new file mode 100644 index 0000000..92b69c4 --- /dev/null +++ b/icann-rdap-common/src/contact/to_vcard.rs @@ -0,0 +1,303 @@ +//! Convert a Contact to jCard/vCard. +use std::str::FromStr; + +use serde_json::{json, Map, Value}; + +use super::Contact; + +impl Contact { + /// Output the Contact data as vCard in JSON values ([`Vec`]). + /// + /// ```rust + /// use icann_rdap_common::contact::Contact; + /// use serde::Serialize; + /// use serde_json::Value; + /// + /// let contact = Contact::builder() + /// .kind("individual") + /// .full_name("Bob Smurd") + /// .build(); + /// + /// let v = contact.to_vcard(); + /// let json = serde_json::to_string(&v); + /// ``` + pub fn to_vcard(&self) -> Vec { + // start the vcard with the version. + let mut vcard: Vec = vec![json!(["version", {}, "text", "4.0"])]; + + if let Some(full_name) = &self.full_name { + vcard.push(json!(["fn", {}, "text", full_name])); + } + + if let Some(name_parts) = &self.name_parts { + let surnames = vec_string_to_value(&name_parts.surnames); + let given_names = vec_string_to_value(&name_parts.given_names); + let middle_names = vec_string_to_value(&name_parts.middle_names); + let prefixes = vec_string_to_value(&name_parts.prefixes); + let suffixes = vec_string_to_value(&name_parts.suffixes); + vcard.push(json!([ + "n", + {}, + "text", + [surnames, given_names, middle_names, prefixes, suffixes] + ])); + } + + if let Some(kind) = &self.kind { + vcard.push(json!(["kind", {}, "text", kind])); + } + + if let Some(langs) = &self.langs { + for lang in langs { + let mut params: Map = Map::new(); + if let Some(pref) = lang.preference { + params.insert("pref".to_string(), Value::String(pref.to_string())); + } + vcard.push(json!([ + "lang", + Value::from(params), + "language-tag", + lang.tag + ])) + } + } + + if let Some(org_names) = &self.organization_names { + for org_name in org_names { + vcard.push(json!(["org", {}, "text", org_name])); + } + } + + if let Some(titles) = &self.titles { + for title in titles { + vcard.push(json!(["title", {}, "text", title])); + } + } + + if let Some(roles) = &self.roles { + for role in roles { + vcard.push(json!(["role", {}, "text", role])); + } + } + + if let Some(nick_names) = &self.nick_names { + for nick_name in nick_names { + vcard.push(json!(["nickname", {}, "text", nick_name])); + } + } + + if let Some(emails) = &self.emails { + for email in emails { + let mut params: Map = Map::new(); + if let Some(pref) = email.preference { + params.insert("pref".to_string(), Value::String(pref.to_string())); + } + if let Some(contexts) = email.contexts.as_ref() { + params.insert("type".to_string(), vec_string_to_param(contexts)); + } + vcard.push(json!(["email", Value::from(params), "text", email.email])) + } + } + + if let Some(phones) = &self.phones { + for phone in phones { + let mut params: Map = Map::new(); + if let Some(pref) = phone.preference { + params.insert("pref".to_string(), Value::String(pref.to_string())); + } + let mut types: Vec = vec![]; + if let Some(contexts) = &phone.contexts { + types.append(&mut contexts.clone()); + } + if let Some(features) = &phone.features { + types.append(&mut features.clone()); + } + params.insert("type".to_string(), vec_string_to_param(&types)); + vcard.push(json!(["tel", Value::from(params), "text", phone.phone])) + } + } + + if let Some(addrs) = &self.postal_addresses { + for addr in addrs { + let mut params: Map = Map::new(); + if let Some(pref) = addr.preference { + params.insert("pref".to_string(), Value::String(pref.to_string())); + } + if let Some(contexts) = addr.contexts.as_ref() { + params.insert("type".to_string(), vec_string_to_param(contexts)); + } + if let Some(full_address) = &addr.full_address { + params.insert( + "label".to_string(), + Value::from_str(full_address).expect("serializing full address"), + ); + } + let mut lines: Vec = vec![]; + if let Some(street_parts) = &addr.street_parts { + lines.push(street_parts.first().cloned().unwrap_or("".to_string())); + lines.push(street_parts.get(1).cloned().unwrap_or("".to_string())); + lines.push(street_parts.get(2).cloned().unwrap_or("".to_string())); + } else { + lines.push("".to_string()); + lines.push("".to_string()); + lines.push("".to_string()); + } + if let Some(locality) = &addr.locality { + lines.push(locality.to_owned()); + } else { + lines.push("".to_string()); + } + if let Some(region_name) = &addr.region_name { + lines.push(region_name.to_owned()); + } else if let Some(region_code) = &addr.region_code { + lines.push(region_code.to_owned()); + } else { + lines.push("".to_string()); + } + if let Some(postal_code) = &addr.postal_code { + lines.push(postal_code.to_owned()); + } else { + lines.push("".to_string()); + } + if let Some(country_name) = &addr.country_name { + lines.push(country_name.to_owned()); + } else if let Some(country_code) = &addr.country_code { + lines.push(country_code.to_owned()); + } else { + lines.push("".to_string()); + } + vcard.push(json!(["adr", Value::from(params), "text", lines])) + } + } + + if let Some(contact_uris) = &self.contact_uris { + for uri in contact_uris { + vcard.push(json!(["contact-uri", {}, "uri", uri])); + } + } + + if let Some(urls) = &self.urls { + for url in urls { + vcard.push(json!(["url", {}, "uri", url])); + } + } + + // return the vcard array + vec![Value::String("vcard".to_string()), Value::from(vcard)] + } +} + +fn vec_string_to_value(strings: &Option>) -> Value { + let Some(strings) = strings else { + return Value::String("".to_string()); + }; + + if strings.is_empty() { + return Value::String("".to_string()); + }; + + if strings.len() == 1 { + let Some(one) = strings.first() else { + panic!("couldn't get first element on length of 1") + }; + return Value::String(one.to_owned()); + }; + + // else + Value::from(strings.clone()) +} + +fn vec_string_to_param(strings: &[String]) -> Value { + if strings.is_empty() { + return Value::String("".to_string()); + }; + + if strings.len() == 1 { + let Some(one) = strings.first() else { + panic!("couldn't get first element on length of 1") + }; + return Value::String(one.to_owned()); + }; + + // else + Value::from(strings) +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use crate::contact::{Contact, Email, Lang, NameParts, Phone, PostalAddress}; + + #[test] + fn GIVEN_contact_WHEN_to_vcard_THEN_from_vcard_is_same() { + // GIVEN + let contact = Contact::builder() + .full_name("Joe User") + .name_parts( + NameParts::builder() + .surnames(vec!["User".to_string()]) + .given_names(vec!["Joe".to_string()]) + .suffixes(vec!["ing. jr".to_string(), "M.Sc.".to_string()]) + .build(), + ) + .kind("individual") + .langs(vec![ + Lang::builder().preference(1).tag("fr").build(), + Lang::builder().preference(2).tag("en").build(), + ]) + .organization_names(vec!["Example".to_string()]) + .titles(vec!["Research Scientist".to_string()]) + .roles(vec!["Project Lead".to_string()]) + .contact_uris(vec!["https://example.com/contact-form".to_string()]) + .postal_addresses(vec![PostalAddress::builder() + .country_name("Canada") + .postal_code("G1V 2M2") + .region_code("QC") + .locality("Quebec") + .street_parts(vec![ + "Suite 1234".to_string(), + "4321 Rue Somewhere".to_string(), + ]) + .build()]) + .phones(vec![ + Phone::builder() + .preference(1) + .contexts(vec!["work".to_string()]) + .features(vec!["voice".to_string()]) + .phone("tel:+1-555-555-1234;ext=102") + .build(), + Phone::builder() + .contexts(vec!["work".to_string(), "cell".to_string()]) + .features(vec![ + "voice".to_string(), + "video".to_string(), + "text".to_string(), + ]) + .phone("tel:+1-555-555-4321") + .build(), + ]) + .emails(vec![Email::builder() + .contexts(vec!["work".to_string()]) + .email("joe.user@example.com") + .build()]) + .urls(vec!["https://example.com/some-url".to_string()]) + .build(); + + // WHEN + let actual = Contact::from_vcard(&contact.to_vcard()).expect("from vcard"); + + // THEN + assert_eq!(contact.full_name, actual.full_name); + assert_eq!(contact.name_parts, actual.name_parts); + assert_eq!(contact.kind, actual.kind); + assert_eq!(contact.langs, actual.langs); + assert_eq!(contact.organization_names, actual.organization_names); + assert_eq!(contact.titles, actual.titles); + assert_eq!(contact.roles, actual.roles); + assert_eq!(contact.postal_addresses, actual.postal_addresses); + assert_eq!(contact.phones, actual.phones); + assert_eq!(contact.emails, actual.emails); + assert_eq!(contact.contact_uris, actual.contact_uris); + assert_eq!(contact.urls, actual.urls); + } +} diff --git a/icann-rdap-common/src/dns_types.rs b/icann-rdap-common/src/dns_types.rs new file mode 100644 index 0000000..0ffa154 --- /dev/null +++ b/icann-rdap-common/src/dns_types.rs @@ -0,0 +1,320 @@ +//! DNS and DNSSEC types. + +use std::str::{Chars, FromStr}; + +use {idna::domain_to_ascii, thiserror::Error}; + +use crate::check::StringCheck; + +/// Errors when determining DNS information. +#[derive(Debug, Error)] +pub enum DnsTypeError { + #[error("Invalid DNS Algorithm")] + InvalidAlgorithm, + #[error("Invalid DNS Digest")] + InvalidDigest, +} + +/// Information about DNSSEC Algorithm. +pub struct DnsAlgorithm { + pub number: u8, + pub mnemonic: &'static str, + pub zone_signing: bool, + pub transaction_signing: bool, +} + +/// DNS Algorithm Variants. +pub enum DnsAlgorithmType { + DeleteDs(DnsAlgorithm), + RsaMd5(DnsAlgorithm), + DiffieHellman(DnsAlgorithm), + Dsa(DnsAlgorithm), + RsaSha1(DnsAlgorithm), + DsaNsec3Sha1(DnsAlgorithm), + RsaSha1Nsec3Sha1(DnsAlgorithm), + RsaSha256(DnsAlgorithm), + RsaSha512(DnsAlgorithm), + EccGost(DnsAlgorithm), + EcdsaP256Sha256(DnsAlgorithm), + EcdsaP384Sha384(DnsAlgorithm), + Ed25519(DnsAlgorithm), + Ed448(DnsAlgorithm), + PrivateDns(DnsAlgorithm), + PrivateOid(DnsAlgorithm), +} + +impl DnsAlgorithmType { + /// Convert an algorithm number to a [DnsAlgorithmType]. + pub fn from_number(number: u8) -> Result { + Ok(match number { + 0 => Self::DeleteDs(DnsAlgorithm { + number: 0, + mnemonic: "DELETE", + zone_signing: false, + transaction_signing: false, + }), + 1 => Self::RsaMd5(DnsAlgorithm { + number: 1, + mnemonic: "RSAMD5", + zone_signing: false, + transaction_signing: true, + }), + 2 => Self::DiffieHellman(DnsAlgorithm { + number: 2, + mnemonic: "DH", + zone_signing: false, + transaction_signing: true, + }), + 3 => Self::Dsa(DnsAlgorithm { + number: 3, + mnemonic: "DSA", + zone_signing: true, + transaction_signing: true, + }), + 5 => Self::RsaSha1(DnsAlgorithm { + number: 5, + mnemonic: "RSASHA1", + zone_signing: true, + transaction_signing: true, + }), + 6 => Self::DsaNsec3Sha1(DnsAlgorithm { + number: 6, + mnemonic: "DSA-NSEC3-SHA1", + zone_signing: true, + transaction_signing: true, + }), + 7 => Self::RsaSha1Nsec3Sha1(DnsAlgorithm { + number: 7, + mnemonic: "RSA-NSEC3-SHA1", + zone_signing: true, + transaction_signing: true, + }), + 8 => Self::RsaSha256(DnsAlgorithm { + number: 8, + mnemonic: "RSASHA256", + zone_signing: true, + transaction_signing: false, + }), + 10 => Self::RsaSha512(DnsAlgorithm { + number: 10, + mnemonic: "RSASHA512", + zone_signing: true, + transaction_signing: false, + }), + 12 => Self::EccGost(DnsAlgorithm { + number: 12, + mnemonic: "ECC-GOST", + zone_signing: true, + transaction_signing: false, + }), + 13 => Self::EcdsaP256Sha256(DnsAlgorithm { + number: 13, + mnemonic: "ECDSAP256SHA256", + zone_signing: true, + transaction_signing: false, + }), + 14 => Self::EcdsaP384Sha384(DnsAlgorithm { + number: 14, + mnemonic: "ECDSAP384SHA384", + zone_signing: true, + transaction_signing: false, + }), + 15 => Self::Ed25519(DnsAlgorithm { + number: 15, + mnemonic: "ED25519", + zone_signing: true, + transaction_signing: false, + }), + 16 => Self::Ed448(DnsAlgorithm { + number: 16, + mnemonic: "ED448", + zone_signing: true, + transaction_signing: false, + }), + 253 => Self::PrivateDns(DnsAlgorithm { + number: 253, + mnemonic: "PRIVATEDNS", + zone_signing: true, + transaction_signing: true, + }), + 254 => Self::PrivateOid(DnsAlgorithm { + number: 254, + mnemonic: "PRIVATEOID", + zone_signing: true, + transaction_signing: true, + }), + _ => return Err(DnsTypeError::InvalidAlgorithm), + }) + } + + fn algo(self) -> DnsAlgorithm { + match self { + Self::DeleteDs(a) + | Self::RsaMd5(a) + | Self::DiffieHellman(a) + | Self::Dsa(a) + | Self::RsaSha1(a) + | Self::DsaNsec3Sha1(a) + | Self::RsaSha1Nsec3Sha1(a) + | Self::RsaSha256(a) + | Self::RsaSha512(a) + | Self::EccGost(a) + | Self::EcdsaP256Sha256(a) + | Self::EcdsaP384Sha384(a) + | Self::Ed25519(a) + | Self::Ed448(a) + | Self::PrivateDns(a) + | Self::PrivateOid(a) => a, + } + } + + /// Get the mnemonic for the algorithm number. + pub fn mnemonic(number: u8) -> Result<&'static str, DnsTypeError> { + let alg = Self::from_number(number)?; + Ok(alg.algo().mnemonic) + } + + /// True if the DNS Algorithm can sign zones. + pub fn zone_signing(number: u8) -> Result { + let alg = Self::from_number(number)?; + Ok(alg.algo().zone_signing) + } +} + +/// DNS Digest. +pub struct DnsDigest { + pub number: u8, + pub mnemonic: &'static str, + pub mandatory: bool, +} + +/* + +0 Reserved - [RFC3658] +1 SHA-1 MANDATORY [RFC3658] +2 SHA-256 MANDATORY [RFC4509] +3 GOST R 34.11-94 OPTIONAL [RFC5933] +4 SHA-384 OPTIONAL +*/ + +/// DNS Digest Variants. +pub enum DnsDigestType { + Sha1(DnsDigest), + Sha256(DnsDigest), + Gost(DnsDigest), + Sha384(DnsDigest), +} + +impl DnsDigestType { + /// Get the [DnsDigestType] from the protocol number. + pub fn from_number(number: u8) -> Result { + Ok(match number { + 1 => Self::Sha1(DnsDigest { + number: 1, + mnemonic: "SHA1", + mandatory: true, + }), + 2 => Self::Sha256(DnsDigest { + number: 2, + mnemonic: "SHA256", + mandatory: true, + }), + 3 => Self::Gost(DnsDigest { + number: 3, + mnemonic: "GOST", + mandatory: false, + }), + 4 => Self::Sha384(DnsDigest { + number: 4, + mnemonic: "SHA384", + mandatory: false, + }), + _ => return Err(DnsTypeError::InvalidDigest), + }) + } + + /// Get the mnemonic from the protocol number. + pub fn mnemonic(number: u8) -> Result<&'static str, DnsTypeError> { + let digest = Self::from_number(number)?; + Ok(match digest { + Self::Sha1(d) | Self::Sha256(d) | Self::Gost(d) | Self::Sha384(d) => d.mnemonic, + }) + } +} + +/// Error specific to processing of domain names. +#[derive(Debug, Error)] +pub enum DomainNameError { + #[error("Invalid Domain Name")] + InvalidDomainName, + #[error(transparent)] + IdnaError(#[from] idna::Errors), +} + +/// Represents a Domain name. +#[derive(Debug)] +pub struct DomainName { + domain_name: String, + ascii: String, +} + +impl DomainName { + /// Iterate over the characters of the domain name. + pub fn chars(&self) -> Chars<'_> { + self.domain_name.chars() + } + + /// Is this domain name a TLD. + pub fn is_tld(&self) -> bool { + self.domain_name.is_tld() + } + + /// Gets the ASCII version of the domain, which is different if this is an IDN. + pub fn to_ascii(&self) -> &str { + &self.ascii + } + + /// Is this domain name an IDN. + pub fn is_idn(&self) -> bool { + !self.ascii.eq(&self.domain_name) + } + + /// Is this the DNS root. + pub fn is_root(&self) -> bool { + self.domain_name.eq(".") + } + + /// Get this domain name with a leading dot. + pub fn with_leading_dot(&self) -> String { + if !self.is_root() { + format!(".{}", self.domain_name) + } else { + self.domain_name.to_string() + } + } + + /// Trim leading dot. + pub fn trim_leading_dot(&self) -> &str { + if !self.is_root() { + self.domain_name.trim_start_matches('.') + } else { + &self.domain_name + } + } +} + +impl FromStr for DomainName { + type Err = DomainNameError; + + /// Create a new DomainName from a string. + fn from_str(s: &str) -> Result { + if !s.is_unicode_domain_name() { + return Err(DomainNameError::InvalidDomainName); + } + let ascii = domain_to_ascii(s)?; + Ok(Self { + domain_name: s.to_string(), + ascii, + }) + } +} diff --git a/icann-rdap-common/src/httpdata.rs b/icann-rdap-common/src/httpdata.rs new file mode 100644 index 0000000..f1073e3 --- /dev/null +++ b/icann-rdap-common/src/httpdata.rs @@ -0,0 +1,327 @@ +//! Code for handling HTTP caching. + +use { + chrono::{DateTime, Duration, Utc}, + serde::{Deserialize, Serialize}, +}; + +/// Represents the data from HTTP responses. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct HttpData { + pub content_length: Option, + pub content_type: Option, + pub scheme: Option, + pub host: String, + pub expires: Option, + pub cache_control: Option, + pub received: DateTime, + pub status_code: u16, + pub location: Option, + pub access_control_allow_origin: Option, + pub access_control_allow_credentials: Option, + pub strict_transport_security: Option, + pub retry_after: Option, + pub request_uri: Option, +} + +#[buildstructor::buildstructor] +impl HttpData { + #[builder(visibility = "pub")] + #[allow(clippy::too_many_arguments)] + fn new( + content_length: Option, + content_type: Option, + scheme: Option, + host: String, + expires: Option, + cache_control: Option, + status_code: u16, + location: Option, + access_control_allow_origin: Option, + access_control_allow_credentials: Option, + strict_transport_security: Option, + retry_after: Option, + received: DateTime, + request_uri: Option, + ) -> Self { + Self { + content_length, + content_type, + scheme, + host, + expires, + cache_control, + received, + status_code, + location, + access_control_allow_origin, + access_control_allow_credentials, + strict_transport_security, + retry_after, + request_uri, + } + } + + #[builder(entry = "now", visibility = "pub")] + #[allow(clippy::too_many_arguments)] + fn new_now( + content_length: Option, + content_type: Option, + scheme: String, + host: String, + expires: Option, + cache_control: Option, + status_code: Option, + location: Option, + access_control_allow_origin: Option, + access_control_allow_credentials: Option, + strict_transport_security: Option, + retry_after: Option, + request_uri: Option, + ) -> Self { + Self { + content_length, + content_type, + scheme: Some(scheme), + host, + expires, + cache_control, + received: Utc::now(), + status_code: status_code.unwrap_or(200), + location, + access_control_allow_origin, + access_control_allow_credentials, + strict_transport_security, + retry_after, + request_uri, + } + } + + #[builder(entry = "example", visibility = "pub")] + #[allow(clippy::too_many_arguments)] + fn new_example( + content_length: Option, + content_type: Option, + expires: Option, + cache_control: Option, + status_code: Option, + location: Option, + access_control_allow_origin: Option, + access_control_allow_credentials: Option, + strict_transport_security: Option, + retry_after: Option, + request_uri: Option, + ) -> Self { + Self { + content_length, + content_type, + scheme: Some("http".to_string()), + host: "example.com".to_string(), + expires, + cache_control, + received: Utc::now(), + status_code: status_code.unwrap_or(200), + location, + access_control_allow_origin, + access_control_allow_credentials, + strict_transport_security, + retry_after, + request_uri, + } + } + + pub fn is_expired(&self, max_age: i64) -> bool { + let now = Utc::now(); + if now >= self.received + Duration::seconds(max_age) { + return true; + } + if let Some(cache_control) = &self.cache_control { + let cc_max_age = cache_control + .split(',') + .map(|s| s.trim()) + .find(|s| s.starts_with("max-age=")); + if let Some(cc_max_age) = cc_max_age { + let cc_max_age = cc_max_age.trim_start_matches("max-age=").parse::(); + if let Ok(cc_max_age) = cc_max_age { + return now >= self.received + Duration::seconds(cc_max_age); + } + } + } + if let Some(expires) = &self.expires { + let expire_time = DateTime::parse_from_rfc2822(expires); + return if let Ok(expire_time) = expire_time { + now >= expire_time + } else { + false + }; + } + false + } + + pub fn should_cache(&self) -> bool { + if let Some(cache_control) = &self.cache_control { + return !cache_control + .split(',') + .map(|s| s.trim()) + .any(|s| s.eq("no-store") || s.eq("no-cache")); + } + true + } + + pub fn from_lines(lines: &[String]) -> Result<(Self, &[String]), serde_json::Error> { + let count = lines.iter().take_while(|s| !s.starts_with("---")).count(); + let cache_data = lines + .iter() + .take(count) + .cloned() + .collect::>() + .join(""); + let cache_data = serde_json::from_str(&cache_data)?; + Ok((cache_data, &lines[count + 1..])) + } + + pub fn to_lines(&self, data: &str) -> Result { + let mut lines = serde_json::to_string(self)?; + lines.push_str("\n---\n"); + lines.push_str(data); + Ok(lines) + } + + pub fn content_length(&self) -> Option { + self.content_length + } + + pub fn content_type(&self) -> Option<&str> { + self.content_type.as_deref() + } + + pub fn scheme(&self) -> Option<&str> { + self.scheme.as_deref() + } + + pub fn host(&self) -> &str { + &self.host + } + + pub fn expires(&self) -> Option<&str> { + self.expires.as_deref() + } + + pub fn cache_control(&self) -> Option<&str> { + self.cache_control.as_deref() + } + + pub fn received(&self) -> &DateTime { + &self.received + } + + pub fn status_code(&self) -> u16 { + self.status_code + } + + pub fn location(&self) -> Option<&str> { + self.location.as_deref() + } + + pub fn access_control_allow_origin(&self) -> Option<&str> { + self.access_control_allow_origin.as_deref() + } + + pub fn access_control_allow_credentials(&self) -> Option<&str> { + self.access_control_allow_credentials.as_deref() + } + + pub fn strict_transport_security(&self) -> Option<&str> { + self.strict_transport_security.as_deref() + } + + pub fn retry_after(&self) -> Option<&str> { + self.retry_after.as_deref() + } + + pub fn request_uri(&self) -> Option<&str> { + self.request_uri.as_deref() + } +} + +#[cfg(test)] +mod tests { + use { + super::HttpData, + chrono::{Duration, Utc}, + rstest::rstest, + }; + + #[rstest] + #[case(HttpData::example().cache_control("max-age=0").build(), 100, true)] + #[case(HttpData::example().cache_control("max-age=100").build(), 0, true)] + #[case(HttpData::example().cache_control("max-age=100").build(), 50, false)] + #[case(HttpData::example().build(), 0, true)] + #[case(HttpData::example().build(), 100, false)] + #[case(HttpData::example().expires(Utc::now().to_rfc2822()).build(), 100, true)] + #[case(HttpData::example().expires((Utc::now() + Duration::seconds(50)).to_rfc2822()).build(), 100, false)] + #[case(HttpData::example().expires((Utc::now() + Duration::seconds(100)).to_rfc2822()).build(), 50, false)] + #[case(HttpData::example().cache_control("max-age=100").expires(Utc::now().to_rfc2822()).build(), 100, false)] + #[case(HttpData::example().cache_control("max-age=0").expires((Utc::now() + Duration::seconds(50)).to_rfc2822()).build(), 100, true)] + fn test_cache_data_and_max_age_is_expired( + #[case] cache_data: HttpData, + #[case] max_age: i64, + #[case] expected: bool, + ) { + // GIVEN in parameters + + // WHEN + let actual = cache_data.is_expired(max_age); + + // THEN + assert_eq!(actual, expected); + } + + #[rstest] + #[case(HttpData::example().cache_control("no-cache").build(), false)] + #[case(HttpData::example().cache_control("no-store").build(), false)] + #[case(HttpData::example().cache_control("max-age=40").build(), true)] + fn test_cache_control(#[case] cache_data: HttpData, #[case] expected: bool) { + // GIVEN in parameters + + // WHEN + let actual = cache_data.should_cache(); + + // THEN + assert_eq!(actual, expected); + } + + #[test] + fn test_data_and_data_cache_to_lines() { + // GIVEN + let data = "foo"; + let cache_data = HttpData::example().content_length(14).build(); + + // WHEN + let actual = cache_data.to_lines(data).unwrap(); + + // THEN + let expected = format!("{}\n---\nfoo", serde_json::to_string(&cache_data).unwrap()); + assert_eq!(actual, expected); + } + + #[test] + fn test_from_lines() { + // GIVEN + let data = "foo"; + let cache_data = HttpData::example().content_length(14).build(); + let lines = cache_data + .to_lines(data) + .unwrap() + .split('\n') + .map(|s| s.to_string()) + .collect::>(); + + // WHEN + let actual = HttpData::from_lines(&lines).expect("parsing lines"); + + // THEN + assert_eq!(cache_data, actual.0); + assert_eq!(vec![data], actual.1); + } +} diff --git a/icann-rdap-common/src/iana.rs b/icann-rdap-common/src/iana.rs new file mode 100644 index 0000000..64e19cc --- /dev/null +++ b/icann-rdap-common/src/iana.rs @@ -0,0 +1,746 @@ +//! The IANA RDAP Bootstrap Registries. + +use { + ipnet::{Ipv4Net, Ipv6Net}, + prefix_trie::PrefixMap, + serde::{Deserialize, Serialize}, + thiserror::Error, +}; + +/// IANA registry variants for RDAP. +#[derive(Debug, Serialize, Deserialize, Clone)] +pub enum IanaRegistryType { + RdapBootstrapDns, + RdapBootstrapAsn, + RdapBootstrapIpv4, + RdapBootstrapIpv6, + RdapObjectTags, +} + +impl IanaRegistryType { + /// Get the URL for an IANA RDAP registry. + pub fn url(&self) -> &str { + match self { + Self::RdapBootstrapDns => "https://data.iana.org/rdap/dns.json", + Self::RdapBootstrapAsn => "https://data.iana.org/rdap/asn.json", + Self::RdapBootstrapIpv4 => "https://data.iana.org/rdap/ipv4.json", + Self::RdapBootstrapIpv6 => "https://data.iana.org/rdap/ipv6.json", + Self::RdapObjectTags => "https://data.iana.org/rdap/object-tags.json", + } + } + + /// Get the filename in the URL for the IANA RDAP registry. + pub fn file_name(&self) -> &str { + let url = self.url(); + url.rsplit('/') + .next() + .expect("unexpected errror: cannot get filename from url") + } +} + +/// Classes of IANA registries. +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(untagged)] +pub enum IanaRegistry { + RdapBootstrapRegistry(RdapBootstrapRegistry), + // might add IANA registrar IDs later +} + +/// Represents an IANA RDAP bootstrap registry. +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct RdapBootstrapRegistry { + pub version: String, + pub publication: String, + pub description: Option, + pub services: Vec>>, +} + +pub trait BootstrapRegistry { + fn get_dns_bootstrap_urls(&self, ldh: &str) -> Result, BootstrapRegistryError>; + fn get_asn_bootstrap_urls(&self, asn: &str) -> Result, BootstrapRegistryError>; + fn get_ipv4_bootstrap_urls(&self, ipv4: &str) -> Result, BootstrapRegistryError>; + fn get_ipv6_bootstrap_urls(&self, ipv6: &str) -> Result, BootstrapRegistryError>; + fn get_tag_bootstrap_urls(&self, tag: &str) -> Result, BootstrapRegistryError>; +} + +/// Errors from processing IANA RDAP bootstrap registries. +#[derive(Debug, Error)] +pub enum BootstrapRegistryError { + #[error("Empty Service")] + EmptyService, + #[error("Empty URL Set")] + EmptyUrlSet, + #[error("Invalid Bootstrap Input")] + InvalidBootstrapInput, + #[error("No Bootstrap URLs Found")] + NoBootstrapUrls, + #[error("Invalid Bootstrap Service")] + InvalidBootstrapService, +} + +impl BootstrapRegistry for IanaRegistry { + /// Get the URLs from the IANA domain bootstrap registry. + fn get_dns_bootstrap_urls(&self, ldh: &str) -> Result, BootstrapRegistryError> { + let mut longest_match: Option<(usize, Vec)> = None; + let Self::RdapBootstrapRegistry(bootstrap) = self; + for service in &bootstrap.services { + let tlds = service + .first() + .ok_or(BootstrapRegistryError::EmptyService)?; + for tld in tlds { + // if the ldh domain ends with the tld or the tld is the empty string which means the root + if ldh.ends_with(tld) || tld.is_empty() { + let urls = service.last().ok_or(BootstrapRegistryError::EmptyUrlSet)?; + let longest = longest_match.get_or_insert_with(|| (tld.len(), urls.to_owned())); + if longest.0 < tld.len() { + *longest = (tld.len(), urls.to_owned()); + } + } + } + } + let longest = longest_match.ok_or(BootstrapRegistryError::NoBootstrapUrls)?; + Ok(longest.1) + } + + /// Get the URLS from the IANA autnum bootstrap registry. + fn get_asn_bootstrap_urls(&self, asn: &str) -> Result, BootstrapRegistryError> { + let autnum = asn + .trim_start_matches(|c| -> bool { matches!(c, 'a' | 'A' | 's' | 'S') }) + .parse::() + .map_err(|_| BootstrapRegistryError::InvalidBootstrapInput)?; + let Self::RdapBootstrapRegistry(bootstrap) = self; + for service in &bootstrap.services { + let as_ranges = service + .first() + .ok_or(BootstrapRegistryError::EmptyService)?; + for range in as_ranges { + let as_split = range.split('-').collect::>(); + let start_as = as_split + .first() + .ok_or(BootstrapRegistryError::InvalidBootstrapService)? + .parse::() + .map_err(|_| BootstrapRegistryError::InvalidBootstrapInput)?; + let end_as = as_split + .last() + .ok_or(BootstrapRegistryError::InvalidBootstrapService)? + .parse::() + .map_err(|_| BootstrapRegistryError::InvalidBootstrapService)?; + if start_as <= autnum && end_as >= autnum { + let urls = service.last().ok_or(BootstrapRegistryError::EmptyUrlSet)?; + return Ok(urls.to_owned()); + } + } + } + Err(BootstrapRegistryError::NoBootstrapUrls) + } + + /// Get the URLs from the IANA IPv4 bootstrap registry. + fn get_ipv4_bootstrap_urls(&self, ipv4: &str) -> Result, BootstrapRegistryError> { + let mut pm: PrefixMap> = PrefixMap::new(); + let Self::RdapBootstrapRegistry(bootstrap) = self; + for service in &bootstrap.services { + let urls = service.last().ok_or(BootstrapRegistryError::EmptyService)?; + for cidr in service + .first() + .ok_or(BootstrapRegistryError::InvalidBootstrapService)? + { + pm.insert( + cidr.parse() + .map_err(|_| BootstrapRegistryError::InvalidBootstrapService)?, + urls.clone(), + ); + } + } + let net = pm + .get_lpm( + &ipv4 + .parse::() + .map_err(|_| BootstrapRegistryError::InvalidBootstrapInput)?, + ) + .ok_or(BootstrapRegistryError::NoBootstrapUrls)?; + Ok(net.1.to_owned()) + } + + /// Get the URLs from the IANA IPv6 bootstrap registry. + fn get_ipv6_bootstrap_urls(&self, ipv6: &str) -> Result, BootstrapRegistryError> { + let mut pm: PrefixMap> = PrefixMap::new(); + let Self::RdapBootstrapRegistry(bootstrap) = self; + for service in &bootstrap.services { + let urls = service.last().ok_or(BootstrapRegistryError::EmptyService)?; + for cidr in service + .first() + .ok_or(BootstrapRegistryError::InvalidBootstrapService)? + { + pm.insert( + cidr.parse() + .map_err(|_| BootstrapRegistryError::InvalidBootstrapService)?, + urls.clone(), + ); + } + } + let net = pm + .get_lpm( + &ipv6 + .parse::() + .map_err(|_| BootstrapRegistryError::InvalidBootstrapInput)?, + ) + .ok_or(BootstrapRegistryError::NoBootstrapUrls)?; + Ok(net.1.to_owned()) + } + + /// Get the URLs from the IANA object tag bootstrap registry. + fn get_tag_bootstrap_urls(&self, tag: &str) -> Result, BootstrapRegistryError> { + let Self::RdapBootstrapRegistry(bootstrap) = self; + for service in &bootstrap.services { + let object_tag = service + .get(1) + .ok_or(BootstrapRegistryError::InvalidBootstrapService)? + .first() + .ok_or(BootstrapRegistryError::EmptyService)?; + if object_tag.to_ascii_uppercase() == tag.to_ascii_uppercase() { + let urls = service.last().ok_or(BootstrapRegistryError::EmptyUrlSet)?; + return Ok(urls.to_owned()); + } + } + Err(BootstrapRegistryError::NoBootstrapUrls) + } +} + +/// Prefer HTTPS urls. +pub fn get_preferred_url(urls: Vec) -> Result { + if urls.is_empty() { + Err(BootstrapRegistryError::EmptyUrlSet) + } else { + let url = urls + .iter() + .find(|s| s.starts_with("https://")) + .unwrap_or_else(|| urls.first().unwrap()); + Ok(url.to_owned()) + } +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use rstest::rstest; + + use crate::iana::{get_preferred_url, BootstrapRegistry}; + + use super::{IanaRegistry, IanaRegistryType}; + + #[rstest] + #[case(IanaRegistryType::RdapBootstrapDns, "dns.json")] + #[case(IanaRegistryType::RdapBootstrapAsn, "asn.json")] + #[case(IanaRegistryType::RdapBootstrapIpv4, "ipv4.json")] + #[case(IanaRegistryType::RdapBootstrapIpv6, "ipv6.json")] + #[case(IanaRegistryType::RdapObjectTags, "object-tags.json")] + fn GIVEN_registry_WHEN_get_file_name_THEN_correct_result( + #[case] registry: IanaRegistryType, + #[case] expected: &str, + ) { + // GIVEN in parameters + + // WHEN + let actual = registry.file_name(); + + // THEN + assert_eq!(actual, expected); + } + + #[test] + fn GIVEN_domain_bootstrap_WHEN_deserialize_THEN_success() { + // GIVEN + let bootstrap = r#" + { + "version": "1.0", + "publication": "2024-01-07T10:11:12Z", + "description": "Some text", + "services": [ + [ + ["net", "com"], + [ + "https://registry.example.com/myrdap/" + ] + ], + [ + ["org", "mytld"], + [ + "https://example.org/" + ] + ], + [ + ["xn--zckzah"], + [ + "https://example.net/rdap/xn--zckzah/", + "http://example.net/rdap/xn--zckzah/" + ] + ] + ] + } + "#; + + // WHEN + let actual = serde_json::from_str::(bootstrap); + + // THEN + actual.unwrap(); + } + + #[test] + fn GIVEN_one_url_WHEN_preferred_urls_THEN_that_is_the_one() { + // GIVEN + let urls = vec!["http://foo.example".to_string()]; + + // WHEN + let actual = get_preferred_url(urls).expect("cannot get preferred url"); + + // THEN + assert_eq!(actual, "http://foo.example"); + } + + #[test] + fn GIVEN_one_http_and_https_url_WHEN_preferred_urls_THEN_return_https() { + // GIVEN + let urls = vec![ + "http://foo.example".to_string(), + "https://foo.example".to_string(), + ]; + + // WHEN + let actual = get_preferred_url(urls).expect("cannot get preferred url"); + + // THEN + assert_eq!(actual, "https://foo.example"); + } + + #[test] + fn GIVEN_domain_bootstrap_with_matching_WHEN_find_THEN_url_matches() { + // GIVEN + let bootstrap = r#" + { + "version": "1.0", + "publication": "2024-01-07T10:11:12Z", + "description": "Some text", + "services": [ + [ + ["net", "com"], + [ + "https://registry.example.com/myrdap/" + ] + ], + [ + ["org", "mytld"], + [ + "https://example.org/" + ] + ] + ] + } + "#; + let iana = + serde_json::from_str::(bootstrap).expect("cannot parse domain bootstrap"); + + // WHEN + let actual = iana.get_dns_bootstrap_urls("foo.org"); + + // THEN + assert_eq!( + actual.expect("no vec").first().expect("vec is empty"), + "https://example.org/" + ); + } + + #[test] + fn GIVEN_domain_bootstrap_with_two_matching_WHEN_find_THEN_return_longest_match() { + // GIVEN + let bootstrap = r#" + { + "version": "1.0", + "publication": "2024-01-07T10:11:12Z", + "description": "Some text", + "services": [ + [ + ["co.uk"], + [ + "https://registry.co.uk/" + ] + ], + [ + ["uk"], + [ + "https://registry.uk/" + ] + ] + ] + } + "#; + let iana = + serde_json::from_str::(bootstrap).expect("cannot parse domain bootstrap"); + + // WHEN + let actual = iana.get_dns_bootstrap_urls("foo.co.uk"); + + // THEN + assert_eq!( + actual.expect("no vec").first().expect("vec is empty"), + "https://registry.co.uk/" + ); + } + + #[test] + fn GIVEN_domain_bootstrap_with_root_WHEN_find_THEN_url_matches() { + // GIVEN + let bootstrap = r#" + { + "version": "1.0", + "publication": "2024-01-07T10:11:12Z", + "description": "Some text", + "services": [ + [ + ["net", "com"], + [ + "https://registry.example.com/myrdap/" + ] + ], + [ + [""], + [ + "https://example.org/" + ] + ] + ] + } + "#; + let iana = + serde_json::from_str::(bootstrap).expect("cannot parse domain bootstrap"); + + // WHEN + let actual = iana.get_dns_bootstrap_urls("foo.org"); + + // THEN + assert_eq!( + actual.expect("no vec").first().expect("vec is empty"), + "https://example.org/" + ); + } + + #[test] + fn GIVEN_autnum_bootstrap_with_match_WHEN_find_with_string_THEN_return_match() { + // GIVEN + let bootstrap = r#" + { + "version": "1.0", + "publication": "2024-01-07T10:11:12Z", + "description": "RDAP Bootstrap file for example registries.", + "services": [ + [ + ["64496-64496"], + [ + "https://rir3.example.com/myrdap/" + ] + ], + [ + ["64497-64510", "65536-65551"], + [ + "https://example.org/" + ] + ], + [ + ["64512-65534"], + [ + "http://example.net/rdaprir2/", + "https://example.net/rdaprir2/" + ] + ] + ] + } + "#; + let iana = + serde_json::from_str::(bootstrap).expect("cannot parse autnum bootstrap"); + + // WHEN + let actual = iana.get_asn_bootstrap_urls("as64498"); + + // THEN + assert_eq!( + actual.expect("no vec").first().expect("vec is empty"), + "https://example.org/" + ); + } + + #[rstest] + #[case(64497u32, "https://example.org/")] + #[case(64498u32, "https://example.org/")] + #[case(64510u32, "https://example.org/")] + #[case(65536u32, "https://example.org/")] + #[case(65537u32, "https://example.org/")] + #[case(64513u32, "http://example.net/rdaprir2/")] + fn GIVEN_autnum_bootstrap_with_match_WHEN_find_with_number_THEN_return_match( + #[case] asn: u32, + #[case] bootstrap_url: &str, + ) { + // GIVEN + let bootstrap = r#" + { + "version": "1.0", + "publication": "2024-01-07T10:11:12Z", + "description": "RDAP Bootstrap file for example registries.", + "services": [ + [ + ["64496-64496"], + [ + "https://rir3.example.com/myrdap/" + ] + ], + [ + ["64497-64510", "65536-65551"], + [ + "https://example.org/" + ] + ], + [ + ["64512-65534"], + [ + "http://example.net/rdaprir2/", + "https://example.net/rdaprir2/" + ] + ] + ] + } + "#; + let iana = + serde_json::from_str::(bootstrap).expect("cannot parse autnum bootstrap"); + + // WHEN + let actual = iana.get_asn_bootstrap_urls(&asn.to_string()); + + // THEN + assert_eq!( + actual.expect("no vec").first().expect("vec is empty"), + bootstrap_url + ); + } + + #[test] + fn GIVEN_ipv4_bootstrap_with_match_WHEN_find_with_ip_address_THEN_return_match() { + // GIVEN + let bootstrap = r#" + { + "version": "1.0", + "publication": "2024-01-07T10:11:12Z", + "description": "RDAP Bootstrap file for example registries.", + "services": [ + [ + ["198.51.100.0/24", "192.0.0.0/8"], + [ + "https://rir1.example.com/myrdap/" + ] + ], + [ + ["203.0.113.0/24", "192.0.2.0/24"], + [ + "https://example.org/" + ] + ], + [ + ["203.0.113.0/28"], + [ + "https://example.net/rdaprir2/", + "http://example.net/rdaprir2/" + ] + ] + ] + } + "#; + let iana = + serde_json::from_str::(bootstrap).expect("cannot parse ipv4 bootstrap"); + + // WHEN + let actual = iana.get_ipv4_bootstrap_urls("198.51.100.1/32"); + + // THEN + assert_eq!( + actual.expect("no vec").first().expect("vec is empty"), + "https://rir1.example.com/myrdap/" + ); + } + + #[test] + fn GIVEN_ipv4_bootstrap_with_match_WHEN_find_with_cidr_THEN_return_match() { + // GIVEN + let bootstrap = r#" + { + "version": "1.0", + "publication": "2024-01-07T10:11:12Z", + "description": "RDAP Bootstrap file for example registries.", + "services": [ + [ + ["198.51.100.0/24", "192.0.0.0/8"], + [ + "https://rir1.example.com/myrdap/" + ] + ], + [ + ["203.0.113.0/24", "192.0.2.0/24"], + [ + "https://example.org/" + ] + ], + [ + ["203.0.113.0/28"], + [ + "https://example.net/rdaprir2/", + "http://example.net/rdaprir2/" + ] + ] + ] + } + "#; + let iana = + serde_json::from_str::(bootstrap).expect("cannot parse ipv4 bootstrap"); + + // WHEN + let actual = iana.get_ipv4_bootstrap_urls("203.0.113.0/24"); + + // THEN + assert_eq!( + actual.expect("no vec").first().expect("vec is empty"), + "https://example.org/" + ); + } + + #[test] + fn GIVEN_ipv6_bootstrap_with_match_WHEN_find_with_ip_address_THEN_return_match() { + // GIVEN + let bootstrap = r#" + { + "version": "1.0", + "publication": "2024-01-07T10:11:12Z", + "description": "RDAP Bootstrap file for example registries.", + "services": [ + [ + ["2001:db8::/34"], + [ + "https://rir2.example.com/myrdap/" + ] + ], + [ + ["2001:db8:4000::/36", "2001:db8:ffff::/48"], + [ + "https://example.org/" + ] + ], + [ + ["2001:db8:1000::/36"], + [ + "https://example.net/rdaprir2/", + "http://example.net/rdaprir2/" + ] + ] + ] + } + "#; + let iana = + serde_json::from_str::(bootstrap).expect("cannot parse ipv6 bootstrap"); + + // WHEN + let actual = iana.get_ipv6_bootstrap_urls("2001:db8::1/128"); + + // THEN + assert_eq!( + actual.expect("no vec").first().expect("vec is empty"), + "https://rir2.example.com/myrdap/" + ); + } + + #[test] + fn GIVEN_ipv6_bootstrap_with_match_WHEN_find_with_ip_cidr_THEN_return_match() { + // GIVEN + let bootstrap = r#" + { + "version": "1.0", + "publication": "2024-01-07T10:11:12Z", + "description": "RDAP Bootstrap file for example registries.", + "services": [ + [ + ["2001:db8::/34"], + [ + "https://rir2.example.com/myrdap/" + ] + ], + [ + ["2001:db8:4000::/36", "2001:db8:ffff::/48"], + [ + "https://example.org/" + ] + ], + [ + ["2001:db8:1000::/36"], + [ + "https://example.net/rdaprir2/", + "http://example.net/rdaprir2/" + ] + ] + ] + } + "#; + let iana = + serde_json::from_str::(bootstrap).expect("cannot parse ipv6 bootstrap"); + + // WHEN + let actual = iana.get_ipv6_bootstrap_urls("2001:db8:4000::/36"); + + // THEN + assert_eq!( + actual.expect("no vec").first().expect("vec is empty"), + "https://example.org/" + ); + } + + #[test] + fn GIVEN_tag_bootstrap_with_match_WHEN_find_with_tag_THEN_return_match() { + // GIVEN + let bootstrap = r#" + { + "version": "1.0", + "publication": "YYYY-MM-DDTHH:MM:SSZ", + "description": "RDAP bootstrap file for service provider object tags", + "services": [ + [ + ["contact@example.com"], + ["YYYY"], + [ + "https://example.com/rdap/" + ] + ], + [ + ["contact@example.org"], + ["ZZ54"], + [ + "http://rdap.example.org/" + ] + ], + [ + ["contact@example.net"], + ["1754"], + [ + "https://example.net/rdap/", + "http://example.net/rdap/" + ] + ] + ] + } + "#; + let iana = + serde_json::from_str::(bootstrap).expect("cannot parse tag bootstrap"); + + // WHEN + let actual = iana.get_tag_bootstrap_urls("YYYY"); + + // THEN + assert_eq!( + actual.expect("no vec").first().expect("vec is empty"), + "https://example.com/rdap/" + ); + } +} diff --git a/icann-rdap-common/src/lib.rs b/icann-rdap-common/src/lib.rs new file mode 100644 index 0000000..148c765 --- /dev/null +++ b/icann-rdap-common/src/lib.rs @@ -0,0 +1,28 @@ +#![allow(rustdoc::bare_urls)] +#![doc = include_str!("../README.md")] +pub mod check; +pub mod contact; +pub mod dns_types; +pub mod httpdata; +pub mod iana; +pub mod media_types; +pub mod response; + +/// Basics RDAP structures. +pub mod prelude { + #[doc(inline)] + pub use crate::contact::*; + #[doc(inline)] + pub use crate::response::*; +} + +#[cfg(debug_assertions)] +use const_format::formatcp; + +/// Version of this software. +#[cfg(not(any(target_arch = "wasm32", debug_assertions)))] +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); + +/// Version of this software. +#[cfg(debug_assertions)] +pub const VERSION: &str = formatcp!("{}_DEV_BUILD", env!("CARGO_PKG_VERSION")); diff --git a/icann-rdap-common/src/media_types.rs b/icann-rdap-common/src/media_types.rs new file mode 100644 index 0000000..11abd69 --- /dev/null +++ b/icann-rdap-common/src/media_types.rs @@ -0,0 +1,7 @@ +//! RDAP media types (formerly known as mime types). + +/// The "application/json" media type value. +pub const JSON_MEDIA_TYPE: &str = "application/json"; + +/// The "application/rdap+json" media type value. +pub const RDAP_MEDIA_TYPE: &str = "application/rdap+json"; diff --git a/icann-rdap-common/src/response/autnum.rs b/icann-rdap-common/src/response/autnum.rs new file mode 100644 index 0000000..7ea0ea3 --- /dev/null +++ b/icann-rdap-common/src/response/autnum.rs @@ -0,0 +1,341 @@ +//! RDAP Autonomous System Number. +use { + crate::prelude::{Common, Extension, ObjectCommon}, + serde::{Deserialize, Serialize}, +}; + +use super::{ + to_opt_vec, types::Link, CommonFields, Entity, Event, GetSelfLink, Notice, Numberish, + ObjectCommonFields, Port43, Remark, SelfLink, ToChild, ToResponse, +}; + +/// Represents an RDAP [autnum](https://rdap.rcode3.com/protocol/object_classes.html#autnum) object response. +/// +/// Using the builder to construct this structure is recommended +/// as it will fill-in many of the mandatory fields. +/// The following is an example. +/// +/// ```rust +/// use icann_rdap_common::prelude::*; +/// +/// let autnum = Autnum::builder() +/// .autnum_range(700..710) // the range of autnums +/// .handle("AS700-1") +/// .status("active") +/// .build(); +/// let c = serde_json::to_string_pretty(&autnum).unwrap(); +/// eprintln!("{c}"); +/// ``` +/// This will produce the following. +/// +/// ```norust +/// { +/// "rdapConformance": [ +/// "rdap_level_0" +/// ], +/// "objectClassName": "autnum", +/// "handle": "AS700-1", +/// "status": [ +/// "active" +/// ], +/// "startAutnum": 700, +/// "endAutnum": 710 +/// } +/// ``` +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Autnum { + #[serde(flatten)] + pub common: Common, + + #[serde(flatten)] + pub object_common: ObjectCommon, + + #[serde(rename = "startAutnum")] + #[serde(skip_serializing_if = "Option::is_none")] + pub start_autnum: Option>, + + #[serde(rename = "endAutnum")] + #[serde(skip_serializing_if = "Option::is_none")] + pub end_autnum: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + + #[serde(rename = "type")] + #[serde(skip_serializing_if = "Option::is_none")] + pub autnum_type: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub country: Option, +} + +#[buildstructor::buildstructor] +impl Autnum { + /// Builds a basic autnum object. + /// + /// ```rust + /// use icann_rdap_common::prelude::*; + /// + /// let autnum = Autnum::builder() + /// .autnum_range(700..710) + /// .handle("AS700-1") + /// .status("active") + /// .build(); + /// ``` + #[builder(visibility = "pub")] + #[allow(clippy::too_many_arguments)] + fn new( + autnum_range: std::ops::Range, + handle: Option, + remarks: Vec, + links: Vec, + events: Vec, + statuses: Vec, + port_43: Option, + entities: Vec, + notices: Vec, + country: Option, + autnum_type: Option, + name: Option, + extensions: Vec, + redacted: Option>, + ) -> Self { + Self { + common: Common::level0() + .extensions(extensions) + .and_notices(to_opt_vec(notices)) + .build(), + object_common: ObjectCommon::autnum() + .and_handle(handle) + .and_remarks(to_opt_vec(remarks)) + .and_links(to_opt_vec(links)) + .and_events(to_opt_vec(events)) + .status(statuses) + .and_port_43(port_43) + .and_entities(to_opt_vec(entities)) + .and_redacted(redacted) + .build(), + start_autnum: Some(Numberish::::from(autnum_range.start)), + end_autnum: Some(Numberish::::from(autnum_range.end)), + name, + autnum_type, + country, + } + } + + /// Returns the starting ASN of the range. + pub fn start_autnum(&self) -> Option { + self.start_autnum.as_ref().and_then(|n| n.as_u32()) + } + + /// Returns the ending ASN of the range. + pub fn end_autnum(&self) -> Option { + self.end_autnum.as_ref().and_then(|n| n.as_u32()) + } + + /// Returns the name of the ASN. + pub fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + /// Returns the type of the ASN. + pub fn autnum_type(&self) -> Option<&str> { + self.autnum_type.as_deref() + } + + /// Returns the country of the ASN. + pub fn country(&self) -> Option<&str> { + self.country.as_deref() + } +} + +impl ToResponse for Autnum { + fn to_response(self) -> super::RdapResponse { + super::RdapResponse::Autnum(Box::new(self)) + } +} + +impl GetSelfLink for Autnum { + fn get_self_link(&self) -> Option<&Link> { + self.object_common.get_self_link() + } +} + +impl SelfLink for Autnum { + fn set_self_link(mut self, link: Link) -> Self { + self.object_common = self.object_common.set_self_link(link); + self + } +} + +impl ToChild for Autnum { + fn to_child(mut self) -> Self { + self.common = Common { + rdap_conformance: None, + notices: None, + }; + self + } +} + +impl CommonFields for Autnum { + fn common(&self) -> &Common { + &self.common + } +} + +impl ObjectCommonFields for Autnum { + fn object_common(&self) -> &ObjectCommon { + &self.object_common + } +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use super::Autnum; + + #[test] + fn GIVEN_autnum_WHEN_deserialize_THEN_success() { + // GIVEN + let expected = r#" + { + "objectClassName" : "autnum", + "handle" : "XXXX-RIR", + "startAutnum" : 65536, + "endAutnum" : 65541, + "name": "AS-RTR-1", + "type" : "DIRECT ALLOCATION", + "status" : [ "active" ], + "country": "AU", + "remarks" : + [ + { + "description" : + [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links" : + [ + { + "value" : "https://example.net/autnum/65537", + "rel" : "self", + "href" : "https://example.net/autnum/65537", + "type" : "application/rdap+json" + } + ], + "events" : + + [ + { + "eventAction" : "registration", + "eventDate" : "1990-12-31T23:59:59Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "1991-12-31T23:59:59Z" + } + ], + "entities" : + [ + { + "objectClassName" : "entity", + "handle" : "XXXX", + "vcardArray":[ + "vcard", + [ + ["version", {}, "text", "4.0"], + ["fn", {}, "text", "Joe User"], + ["kind", {}, "text", "individual"], + ["lang", { + "pref":"1" + }, "language-tag", "fr"], + ["lang", { + "pref":"2" + }, "language-tag", "en"], + ["org", { + "type":"work" + }, "text", "Example"], + ["title", {}, "text", "Research Scientist"], + ["role", {}, "text", "Project Lead"], + ["adr", + { "type":"work" }, + "text", + [ + "", + "Suite 1234", + "4321 Rue Somewhere", + "Quebec", + "QC", + "G1V 2M2", + "Canada" + ] + ], + ["tel", + { "type":["work", "voice"], "pref":"1" }, + "uri", "tel:+1-555-555-1234;ext=102" + ], + ["email", + { "type":"work" }, + "text", "joe.user@example.com" + ] + ] + ], + "roles" : [ "registrant" ], + "remarks" : + [ + { + "description" : + [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links" : + [ + { + "value" : "https://example.net/entity/XXXX", + "rel" : "self", + "href" : "https://example.net/entity/XXXX", + "type" : "application/rdap+json" + } + ], + "events" : + [ + { + "eventAction" : "registration", + "eventDate" : "1990-12-31T23:59:59Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "1991-12-31T23:59:59Z" + } + ] + } + ] + } + "#; + + // WHEN + let actual = serde_json::from_str::(expected); + + // THEN + let actual = actual.unwrap(); + assert_eq!(actual.object_common.object_class_name, "autnum"); + assert!(actual.object_common.handle.is_some()); + assert!(actual.start_autnum.is_some()); + assert!(actual.end_autnum.is_some()); + assert!(actual.name.is_some()); + assert!(actual.autnum_type.is_some()); + assert!(actual.object_common.status.is_some()); + assert!(actual.country.is_some()); + assert!(actual.object_common.remarks.is_some()); + assert!(actual.object_common.links.is_some()); + assert!(actual.object_common.events.is_some()); + assert!(actual.object_common.entities.is_some()); + } +} diff --git a/icann-rdap-common/src/response/common.rs b/icann-rdap-common/src/response/common.rs new file mode 100644 index 0000000..7daed61 --- /dev/null +++ b/icann-rdap-common/src/response/common.rs @@ -0,0 +1,51 @@ +use serde::{Deserialize, Serialize}; + +use super::{Extension, ExtensionId, Notice, Notices, RdapConformance}; + +/// Holds those types that are common in all responses. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Common { + #[serde(rename = "rdapConformance")] + #[serde(skip_serializing_if = "Option::is_none")] + pub rdap_conformance: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub notices: Option, +} + +#[buildstructor::buildstructor] +impl Common { + #[builder(entry = "level0", visibility = "pub(crate)")] + fn new_level0(mut extensions: Vec, notices: Option>) -> Self { + let mut standard_extensions = vec![ExtensionId::RdapLevel0.to_extension()]; + extensions.append(&mut standard_extensions); + Self { + rdap_conformance: Some(extensions), + notices, + } + } +} + +/// Empty Extensions. +static EMPTY_EXTENSIONS: Vec = vec![]; +/// Empty Notices. +static EMPTY_NOTICES: Vec = vec![]; + +/// Convience methods for fields in [Common]. +pub trait CommonFields { + /// Getter for [Common]. + fn common(&self) -> &Common; + + /// Getter for Vec of RDAP extensions. + fn extensions(&self) -> &Vec { + self.common() + .rdap_conformance + .as_ref() + .unwrap_or(&EMPTY_EXTENSIONS) + } + + /// Getter for Vec of Notices. + fn notices(&self) -> &Vec { + self.common().notices.as_ref().unwrap_or(&EMPTY_NOTICES) + } +} diff --git a/icann-rdap-common/src/response/domain.rs b/icann-rdap-common/src/response/domain.rs new file mode 100644 index 0000000..614b9d8 --- /dev/null +++ b/icann-rdap-common/src/response/domain.rs @@ -0,0 +1,986 @@ +//! RDAP Domain Object Class +use { + crate::prelude::{Common, Extension, ObjectCommon}, + buildstructor::Builder, + serde::{Deserialize, Serialize}, +}; + +use super::{ + lenient::{Boolish, Numberish}, + nameserver::Nameserver, + network::Network, + to_opt_vec, to_opt_vectorstringish, + types::{Events, Link, Links, PublicIds}, + CommonFields, Entity, Event, GetSelfLink, Notice, ObjectCommonFields, Port43, PublicId, Remark, + SelfLink, ToChild, ToResponse, VectorStringish, EMPTY_VEC_STRING, +}; + +/// Represents an RDAP variant name. +#[derive(Serialize, Deserialize, Builder, Clone, Debug, PartialEq, Eq)] +pub struct VariantName { + #[serde(rename = "ldhName")] + #[serde(skip_serializing_if = "Option::is_none")] + pub ldh_name: Option, + + #[serde(rename = "unicodeName")] + #[serde(skip_serializing_if = "Option::is_none")] + pub unicode_name: Option, +} + +impl VariantName { + /// Convenience method. + pub fn ldh_name(&self) -> Option<&str> { + self.ldh_name.as_deref() + } + + /// Convenience method. + pub fn unicode_name(&self) -> Option<&str> { + self.unicode_name.as_deref() + } +} + +/// Represents an RDAP IDN variant. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Variant { + #[serde(rename = "relation")] + #[serde(skip_serializing_if = "Option::is_none")] + pub relations: Option, + + #[serde(rename = "idnTable")] + #[serde(skip_serializing_if = "Option::is_none")] + pub idn_table: Option, + + #[serde(rename = "variantNames")] + #[serde(skip_serializing_if = "Option::is_none")] + pub variant_names: Option>, +} + +static EMPTY_VARIANT_NAMES: Vec = vec![]; + +#[buildstructor::buildstructor] +impl Variant { + #[builder(visibility = "pub")] + fn new( + relations: Vec, + idn_table: Option, + variant_names: Vec, + ) -> Self { + Self { + relations: to_opt_vectorstringish(relations), + idn_table, + variant_names: to_opt_vec(variant_names), + } + } + + /// Convenience method to get relations. + pub fn relations(&self) -> &Vec { + self.relations + .as_ref() + .map(|v| v.vec()) + .unwrap_or(&EMPTY_VEC_STRING) + } + + /// Convenience method to get variant names. + pub fn variant_names(&self) -> &Vec { + self.variant_names.as_ref().unwrap_or(&EMPTY_VARIANT_NAMES) + } + + /// Convenience method. + pub fn idn_table(&self) -> Option<&str> { + self.idn_table.as_deref() + } +} + +static EMPTY_LINKS: Vec = vec![]; +static EMPTY_EVENTS: Vec = vec![]; + +/// Represents `dsData`. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct DsDatum { + #[serde(rename = "keyTag")] + #[serde(skip_serializing_if = "Option::is_none")] + pub key_tag: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub algorithm: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub digest: Option, + + #[serde(rename = "digestType")] + #[serde(skip_serializing_if = "Option::is_none")] + pub digest_type: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub links: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub events: Option, +} + +#[buildstructor::buildstructor] +impl DsDatum { + /// Builder for `dsData` + #[builder(visibility = "pub")] + fn new( + key_tag: Option, + algorithm: Option, + digest: Option, + digest_type: Option, + links: Vec, + events: Vec, + ) -> Self { + Self { + key_tag: key_tag.map(Numberish::::from), + algorithm: algorithm.map(Numberish::::from), + digest, + digest_type: digest_type.map(Numberish::::from), + links: to_opt_vec(links), + events: to_opt_vec(events), + } + } + + /// Convenience method to get links. + pub fn links(&self) -> &Vec { + self.links.as_ref().unwrap_or(&EMPTY_LINKS) + } + + /// Convenience method to get events. + pub fn events(&self) -> &Vec { + self.events.as_ref().unwrap_or(&EMPTY_EVENTS) + } + + /// Returns a u32 if it was given, otherwise None. + pub fn key_tag(&self) -> Option { + self.key_tag.as_ref().and_then(|n| n.as_u32()) + } + + /// Returns a u8 if it was given, otherwise None. + pub fn digest_type(&self) -> Option { + self.digest_type.as_ref().and_then(|n| n.as_u8()) + } + + /// Convenience method. + pub fn digest(&self) -> Option<&str> { + self.digest.as_deref() + } +} + +/// Represents `keyData`. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct KeyDatum { + #[serde(skip_serializing_if = "Option::is_none")] + pub flags: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub protocol: Option>, + + #[serde(rename = "publicKey")] + #[serde(skip_serializing_if = "Option::is_none")] + pub public_key: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub algorithm: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub links: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub events: Option, +} + +#[buildstructor::buildstructor] +impl KeyDatum { + /// Builder for `keyData` + #[builder(visibility = "pub")] + fn new( + flags: Option, + protocol: Option, + public_key: Option, + algorithm: Option, + links: Vec, + events: Vec, + ) -> Self { + Self { + flags: flags.map(Numberish::::from), + protocol: protocol.map(Numberish::::from), + public_key, + algorithm: algorithm.map(Numberish::::from), + links: to_opt_vec(links), + events: to_opt_vec(events), + } + } + + /// Convenience method to get links. + pub fn links(&self) -> &Vec { + self.links.as_ref().unwrap_or(&EMPTY_LINKS) + } + + /// Convenience method to get events. + pub fn events(&self) -> &Vec { + self.events.as_ref().unwrap_or(&EMPTY_EVENTS) + } + + /// Returns a u16 if it was given, otherwise None. + pub fn flags(&self) -> Option { + self.flags.as_ref().and_then(|n| n.as_u16()) + } + + /// Returns a u8 if it was given, otherwise None. + pub fn protocol(&self) -> Option { + self.protocol.as_ref().and_then(|n| n.as_u8()) + } + + /// Returns a u8 if it was given, otherwise None. + pub fn algorithm(&self) -> Option { + self.algorithm.as_ref().and_then(|n| n.as_u8()) + } + + /// Convenience method. + pub fn public_key(&self) -> Option<&str> { + self.public_key.as_deref() + } +} + +static EMPTY_DS_DATA: Vec = vec![]; +static EMPTY_KEY_DATA: Vec = vec![]; + +/// Represents the DNSSEC information of a domain. +/// +/// The following shows how to use the builders to +/// create a domain with secure DNS informaiton. +/// +/// ```rust +/// use icann_rdap_common::prelude::*; +/// +/// // Builds DNS security `keyData`. +/// let key_datum = KeyDatum::builder() +/// .flags(257) +/// .protocol(3) +/// .algorithm(8) +/// .public_key("AwEAAa6eDzronzjEDbT...Jg1M5N rBSPkuXpdFE=") +/// .build(); +/// +/// // Builds DNS security `dsData`. +/// let ds_datum = DsDatum::builder() +/// .algorithm(13) +/// .key_tag(20149) +/// .digest_type(2) +/// .digest("cf066bceadb799a27b62e3e82dc2e4da314c1807db98f13d82f0043b1418cf4e") +/// .build(); +/// +/// // Builds DNS security. +/// let secure_dns = SecureDns::builder() +/// .ds_data(ds_datum) +/// .key_data(key_datum) +/// .zone_signed(true) +/// .delegation_signed(false) +/// .max_sig_life(604800) +/// .build(); +/// +/// // Builds `domain` with DNS security. +/// let domain = Domain::builder() +/// .ldh_name("example.com") +/// .handle("EXAMPLE-DOMAIN") +/// .status("active") +/// .secure_dns(secure_dns) +/// .build(); +/// ``` +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct SecureDns { + #[serde(rename = "zoneSigned")] + #[serde(skip_serializing_if = "Option::is_none")] + pub zone_signed: Option, + + #[serde(rename = "delegationSigned")] + #[serde(skip_serializing_if = "Option::is_none")] + pub delegation_signed: Option, + + #[serde(rename = "maxSigLife")] + #[serde(skip_serializing_if = "Option::is_none")] + pub max_sig_life: Option>, + + #[serde(rename = "dsData")] + #[serde(skip_serializing_if = "Option::is_none")] + pub ds_data: Option>, + + #[serde(rename = "keyData")] + #[serde(skip_serializing_if = "Option::is_none")] + pub key_data: Option>, +} + +#[buildstructor::buildstructor] +impl SecureDns { + /// Builder for `secureDNS`. + #[builder(visibility = "pub")] + fn new( + zone_signed: Option, + delegation_signed: Option, + max_sig_life: Option, + ds_datas: Vec, + key_datas: Vec, + ) -> Self { + Self { + zone_signed: zone_signed.map(Boolish::from), + delegation_signed: delegation_signed.map(Boolish::from), + max_sig_life: max_sig_life.map(Numberish::::from), + ds_data: to_opt_vec(ds_datas), + key_data: to_opt_vec(key_datas), + } + } + + /// Convenience method to get ds data. + pub fn ds_data(&self) -> &Vec { + self.ds_data.as_ref().unwrap_or(&EMPTY_DS_DATA) + } + + /// Convenience method to get key data. + pub fn key_data(&self) -> &Vec { + self.key_data.as_ref().unwrap_or(&EMPTY_KEY_DATA) + } + + /// Returns true if a truish value was given, otherwise false. + pub fn zone_signed(&self) -> bool { + self.zone_signed.as_ref().map_or(false, |b| b.into_bool()) + } + + /// Returns true if a truish value was given, otherwise false. + pub fn delegation_signed(&self) -> bool { + self.delegation_signed + .as_ref() + .map_or(false, |b| b.into_bool()) + } + + /// Returns max_sig_life as a u64 if it was given, otherwise None. + pub fn max_sig_life(&self) -> Option { + self.max_sig_life.as_ref().and_then(|n| n.as_u64()) + } +} + +static EMPTY_PUBLIC_IDS: Vec = vec![]; +static EMPTY_NAMESERVERS: Vec = vec![]; + +/// Represents an RDAP [domain](https://rdap.rcode3.com/protocol/object_classes.html#domain) response. +/// +/// Using the builder is recommended to construct this structure as it +/// will fill-in many of the mandatory fields. +/// The following is an example. +/// +/// ```rust +/// use icann_rdap_common::prelude::*; +/// +/// let domain = Domain::builder() +/// .ldh_name("foo.example.com") +/// .handle("foo_example_com-1") +/// .status("active") +/// .build(); +/// let c = serde_json::to_string_pretty(&domain).unwrap(); +/// eprintln!("{c}"); +/// ``` +/// +/// This will produce the following. +/// +/// ```norust +/// { +/// "rdapConformance": [ +/// "rdap_level_0" +/// ], +/// "objectClassName": "domain", +/// "handle": "foo_example_com-1", +/// "status": [ +/// "active" +/// ], +/// "ldhName": "foo.example.com" +/// } +/// ``` +/// +/// Domains have many sub-structures that are also constructed +/// using builders, which may then be passed into a Domain +/// builder. +/// +/// ```rust +/// use icann_rdap_common::prelude::*; +/// +/// let nameservers = vec![ +/// Nameserver::builder() +/// .ldh_name("ns1.example.com") +/// .address("127.0.0.1") +/// .build() +/// .unwrap(), +/// Nameserver::builder() +/// .ldh_name("ns2.example.com") +/// .build() +/// .unwrap(), +/// ]; +/// +/// let ds_datum = DsDatum::builder() +/// .algorithm(13) +/// .key_tag(20149) +/// .digest_type(2) +/// .digest("cf066bceadb799a27b62e3e82dc2e4da314c1807db98f13d82f0043b1418cf4e") +/// .build(); +/// +/// let secure_dns = SecureDns::builder() +/// .ds_data(ds_datum) +/// .zone_signed(true) +/// .delegation_signed(false) +/// .build(); +/// +/// let domain = Domain::builder() +/// .ldh_name("foo.example.com") +/// .handle("foo_example_com-3") +/// .status("active") +/// .nameservers(nameservers) +/// .secure_dns(secure_dns) +/// .build(); +/// ``` +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Domain { + #[serde(flatten)] + pub common: Common, + + #[serde(flatten)] + pub object_common: ObjectCommon, + + #[serde(rename = "ldhName")] + #[serde(skip_serializing_if = "Option::is_none")] + pub ldh_name: Option, + + #[serde(rename = "unicodeName")] + #[serde(skip_serializing_if = "Option::is_none")] + pub unicode_name: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub variants: Option>, + + #[serde(rename = "secureDNS")] + #[serde(skip_serializing_if = "Option::is_none")] + pub secure_dns: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub nameservers: Option>, + + #[serde(rename = "publicIds")] + #[serde(skip_serializing_if = "Option::is_none")] + pub public_ids: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub network: Option, +} + +#[buildstructor::buildstructor] +impl Domain { + /// Builds a basic domain object. + /// + /// ```rust + /// use icann_rdap_common::prelude::*; + /// + /// let domain = Domain::builder() + /// .ldh_name("foo.example.com") + /// .handle("foo_example_com-1") + /// .status("active") + /// .build(); + /// ``` + #[builder(visibility = "pub")] + #[allow(clippy::too_many_arguments)] + fn new>( + ldh_name: T, + unicode_name: Option, + nameservers: Vec, + handle: Option, + remarks: Vec, + links: Vec, + events: Vec, + statuses: Vec, + port_43: Option, + entities: Vec, + notices: Vec, + public_ids: Vec, + secure_dns: Option, + variants: Vec, + network: Option, + extensions: Vec, + redacted: Option>, + ) -> Self { + Self { + common: Common::level0() + .extensions(extensions) + .and_notices(to_opt_vec(notices)) + .build(), + object_common: ObjectCommon::domain() + .and_handle(handle) + .and_remarks(to_opt_vec(remarks)) + .and_links(to_opt_vec(links)) + .and_events(to_opt_vec(events)) + .status(statuses) + .and_port_43(port_43) + .and_entities(to_opt_vec(entities)) + .and_redacted(redacted) + .build(), + ldh_name: Some(ldh_name.into()), + unicode_name, + variants: to_opt_vec(variants), + secure_dns, + nameservers: to_opt_vec(nameservers), + public_ids: to_opt_vec(public_ids), + network, + } + } + + /// Builds an IDN object. + /// + /// ```rust + /// use icann_rdap_common::prelude::*; + /// + /// let domain = Domain::idn() + /// .unicode_name("foo.example.com") + /// .handle("foo_example_com-1") + /// .status("active") + /// .build(); + /// ``` + #[builder(entry = "idn", visibility = "pub")] + #[allow(clippy::too_many_arguments)] + fn new_idn>( + ldh_name: Option, + unicode_name: T, + nameservers: Vec, + handle: Option, + remarks: Vec, + links: Vec, + events: Vec, + statuses: Vec, + port_43: Option, + entities: Vec, + notices: Vec, + public_ids: Vec, + secure_dns: Option, + variants: Vec, + network: Option, + extensions: Vec, + ) -> Self { + Self { + common: Common::level0() + .extensions(extensions) + .and_notices(to_opt_vec(notices)) + .build(), + object_common: ObjectCommon::domain() + .and_handle(handle) + .and_remarks(to_opt_vec(remarks)) + .and_links(to_opt_vec(links)) + .and_events(to_opt_vec(events)) + .status(statuses) + .and_port_43(port_43) + .and_entities(to_opt_vec(entities)) + .build(), + ldh_name, + unicode_name: Some(unicode_name.into()), + variants: to_opt_vec(variants), + secure_dns, + nameservers: to_opt_vec(nameservers), + public_ids: to_opt_vec(public_ids), + network, + } + } + + /// Convenience method to get the public IDs. + pub fn public_ids(&self) -> &Vec { + self.public_ids.as_ref().unwrap_or(&EMPTY_PUBLIC_IDS) + } + + /// Convenience method to get the nameservers. + pub fn nameservers(&self) -> &Vec { + self.nameservers.as_ref().unwrap_or(&EMPTY_NAMESERVERS) + } + + /// Convenience method. + pub fn ldh_name(&self) -> Option<&str> { + self.ldh_name.as_deref() + } + + /// Convenience method. + pub fn unicode_name(&self) -> Option<&str> { + self.unicode_name.as_deref() + } +} + +impl ToResponse for Domain { + fn to_response(self) -> super::RdapResponse { + super::RdapResponse::Domain(Box::new(self)) + } +} + +impl GetSelfLink for Domain { + fn get_self_link(&self) -> Option<&Link> { + self.object_common.get_self_link() + } +} + +impl SelfLink for Domain { + fn set_self_link(mut self, link: Link) -> Self { + self.object_common = self.object_common.set_self_link(link); + self + } +} + +impl ToChild for Domain { + fn to_child(mut self) -> Self { + self.common = Common { + rdap_conformance: None, + notices: None, + }; + self + } +} + +impl CommonFields for Domain { + fn common(&self) -> &Common { + &self.common + } +} + +impl ObjectCommonFields for Domain { + fn object_common(&self) -> &ObjectCommon { + &self.object_common + } +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use crate::response::{types::Link, SelfLink}; + + use super::Domain; + + #[test] + fn GIVEN_domain_WHEN_deserialize_THEN_success() { + // GIVEN + let expected = r#" + { + "objectClassName" : "domain", + "handle" : "XXXX", + "ldhName" : "xn--fo-5ja.example", + "unicodeName" : "fóo.example", + "variants" : + [ + { + "relation" : [ "registered", "conjoined" ], + "variantNames" : + [ + { + "ldhName" : "xn--fo-cka.example", + "unicodeName" : "fõo.example" + }, + { + "ldhName" : "xn--fo-fka.example", + "unicodeName" : "föo.example" + } + ] + }, + { + "relation" : [ "unregistered", "registration restricted" ], + "idnTable": ".EXAMPLE Swedish", + "variantNames" : + [ + { + "ldhName": "xn--fo-8ja.example", + "unicodeName" : "fôo.example" + } + ] + + } + ], + "status" : [ "locked", "transfer prohibited" ], + "publicIds":[ + { + "type":"ENS_Auth ID", + "identifier":"1234567890" + } + ], + "nameservers" : + [ + { + "objectClassName" : "nameserver", + "handle" : "XXXX", + "ldhName" : "ns1.example.com", + "status" : [ "active" ], + "ipAddresses" : + { + "v6": [ "2001:db8::123", "2001:db8::124" ], + "v4": [ "192.0.2.1", "192.0.2.2" ] + }, + "remarks" : + [ + { + "description" : + [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links" : + [ + { + "value" : "https://example.net/nameserver/ns1.example.com", + "rel" : "self", + "href" : "https://example.net/nameserver/ns1.example.com", + "type" : "application/rdap+json" + } + ], + "events" : + [ + { + "eventAction" : "registration", + "eventDate" : "1990-12-31T23:59:59Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "1991-12-31T23:59:59Z" + } + ] + }, + { + "objectClassName" : "nameserver", + "handle" : "XXXX", + "ldhName" : "ns2.example.com", + "status" : [ "active" ], + "ipAddresses" : + { + "v6" : [ "2001:db8::125", "2001:db8::126" ], + "v4" : [ "192.0.2.3", "192.0.2.4" ] + }, + "remarks" : + [ + { + "description" : + [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links" : + [ + { + "value" : "https://example.net/nameserver/ns2.example.com", + "rel" : "self", + "href" : "https://example.net/nameserver/ns2.example.com", + "type" : "application/rdap+json" + } + ], + "events" : + [ + { + "eventAction" : "registration", + "eventDate" : "1990-12-31T23:59:59Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "1991-12-31T23:59:59Z" + } + ] + } + ], + "secureDNS": + { + + "zoneSigned": true, + "delegationSigned": true, + "maxSigLife": 604800, + "keyData": + [ + { + "flags": 257, + "protocol": 3, + "algorithm": 8, + "publicKey": "AwEAAa6eDzronzjEDbT...Jg1M5N rBSPkuXpdFE=", + "events": + [ + { + "eventAction": "last changed", + "eventDate": "2012-07-23T05:15:47Z" + } + ] + } + ] + }, + "remarks" : + [ + { + "description" : + [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links" : + [ + { + "value": "https://example.net/domain/xn--fo-5ja.example", + "rel" : "self", + "href" : "https://example.net/domain/xn--fo-5ja.example", + "type" : "application/rdap+json" + } + ], + "port43" : "whois.example.net", + "events" : + [ + { + "eventAction" : "registration", + "eventDate" : "1990-12-31T23:59:59Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "1991-12-31T23:59:59Z", + "eventActor" : "joe@example.com" + }, + { + "eventAction" : "transfer", + "eventDate" : "1991-12-31T23:59:59Z", + "eventActor" : "joe@example.com" + }, + { + "eventAction" : "expiration", + "eventDate" : "2016-12-31T23:59:59Z", + "eventActor" : "joe@example.com" + } + ], + "entities" : + [ + { + "objectClassName" : "entity", + "handle" : "XXXX", + "vcardArray":[ + "vcard", + [ + ["version", {}, "text", "4.0"], + ["fn", {}, "text", "Joe User"], + ["kind", {}, "text", "individual"], + ["lang", { + "pref":"1" + }, "language-tag", "fr"], + ["lang", { + "pref":"2" + }, "language-tag", "en"], + ["org", { + "type":"work" + }, "text", "Example"], + ["title", {}, "text", "Research Scientist"], + ["role", {}, "text", "Project Lead"], + ["adr", + { "type":"work" }, + "text", + [ + "", + "Suite 1234", + "4321 Rue Somewhere", + "Quebec", + "QC", + "G1V 2M2", + "Canada" + ] + + ], + ["tel", + { "type":["work", "voice"], "pref":"1" }, + "uri", "tel:+1-555-555-1234;ext=102" + ], + ["email", + { "type":"work" }, + "text", "joe.user@example.com" + ] + ] + ], + "status" : [ "validated", "locked" ], + "roles" : [ "registrant" ], + "remarks" : + [ + { + "description" : + [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links" : + [ + { + "value" : "https://example.net/entity/XXXX", + "rel" : "self", + "href" : "https://example.net/entity/XXXX", + "type" : "application/rdap+json" + } + ], + "events" : + [ + { + "eventAction" : "registration", + "eventDate" : "1990-12-31T23:59:59Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "1991-12-31T23:59:59Z" + } + ] + } + ] + } + "#; + + // WHEN + let actual = serde_json::from_str::(expected); + + // THEN + let actual = actual.unwrap(); + assert_eq!(actual.object_common.object_class_name, "domain"); + assert!(actual.object_common.handle.is_some()); + assert!(actual.ldh_name.is_some()); + assert!(actual.unicode_name.is_some()); + assert!(actual.variants.is_some()); + assert!(actual.public_ids.is_some()); + assert!(actual.object_common.remarks.is_some()); + assert!(actual.object_common.links.is_some()); + assert!(actual.object_common.events.is_some()); + assert!(actual.object_common.port_43.is_some()); + assert!(actual.object_common.entities.is_some()); + assert!(actual.secure_dns.is_some()); + } + + #[test] + fn GIVEN_no_self_links_WHEN_set_self_link_THEN_link_is_only_one() { + // GIVEN + let mut domain = Domain::builder() + .ldh_name("foo.example") + .link( + Link::builder() + .href("http://bar.example") + .value("http://bar.example") + .rel("unknown") + .build(), + ) + .build(); + + // WHEN + domain = domain.set_self_link( + Link::builder() + .href("http://foo.example") + .value("http://foo.example") + .rel("unknown") + .build(), + ); + + // THEN + assert_eq!( + domain + .object_common + .links + .expect("links are empty") + .iter() + .filter(|link| link.is_relation("self")) + .count(), + 1 + ); + } +} diff --git a/icann-rdap-common/src/response/entity.rs b/icann-rdap-common/src/response/entity.rs new file mode 100644 index 0000000..7e5dacd --- /dev/null +++ b/icann-rdap-common/src/response/entity.rs @@ -0,0 +1,420 @@ +//! Entity object class. +use { + crate::{ + contact::Contact, + prelude::{Common, Extension, ObjectCommon}, + }, + serde::{Deserialize, Serialize}, + serde_json::Value, + strum_macros::{Display, EnumString}, +}; + +use super::{ + autnum::Autnum, + network::Network, + to_opt_vec, to_opt_vectorstringish, + types::{Events, Link, PublicIds}, + CommonFields, Event, GetSelfLink, Notice, ObjectCommonFields, Port43, PublicId, Remark, + SelfLink, ToChild, ToResponse, VectorStringish, EMPTY_VEC_STRING, +}; + +/// Represents an RDAP [entity](https://rdap.rcode3.com/protocol/object_classes.html#entity) response. +/// +/// Use of the builder is recommended when constructing this structure as it +/// will fill-in the mandatory fields. +/// The following is an example. +/// +/// ```rust +/// use icann_rdap_common::prelude::*; +/// +/// let contact = Contact::builder() +/// .kind("individual") +/// .full_name("Bob Smurd") +/// .build(); +/// +/// let entity = Entity::builder() +/// .handle("foo_example_com-1") +/// .status("active") +/// .role("registrant") +/// .contact(contact) +/// .build(); +/// let c = serde_json::to_string_pretty(&entity).unwrap(); +/// eprintln!("{c}"); +/// ``` +/// +/// This will produce the following. +/// +/// ```norust +/// { +/// "rdapConformance": [ +/// "rdap_level_0" +/// ], +/// "objectClassName": "entity", +/// "handle": "foo_example_com-1", +/// "status": [ +/// "active" +/// ], +/// "vcardArray": [ +/// "vcard", +/// [ +/// [ +/// "version", +/// {}, +/// "text", +/// "4.0" +/// ], +/// [ +/// "fn", +/// {}, +/// "text", +/// "Bob Smurd" +/// ], +/// [ +/// "kind", +/// {}, +/// "text", +/// "individual" +/// ] +/// ] +/// ], +/// "roles": [ +/// "registrant" +/// ] +/// } +/// ``` +/// +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Entity { + #[serde(flatten)] + pub common: Common, + + #[serde(flatten)] + pub object_common: ObjectCommon, + + #[serde(rename = "vcardArray")] + #[serde(skip_serializing_if = "Option::is_none")] + pub vcard_array: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub roles: Option, + + #[serde(rename = "publicIds")] + #[serde(skip_serializing_if = "Option::is_none")] + pub public_ids: Option, + + #[serde(rename = "asEventActor")] + #[serde(skip_serializing_if = "Option::is_none")] + pub as_event_actor: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub autnums: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub networks: Option>, +} + +static EMPTY_PUBLIC_IDS: Vec = vec![]; +static EMPTY_AS_EVENT_ACTORS: Vec = vec![]; +static EMPTY_AUTNUMS: Vec = vec![]; +static EMPTY_NETWORKS: Vec = vec![]; + +#[buildstructor::buildstructor] +impl Entity { + /// Builds a basic autnum object. + /// + /// ```rust + /// use icann_rdap_common::prelude::*; + /// + /// let contact = Contact::builder() + /// .kind("individual") + /// .full_name("Bob Smurd") + /// .build(); + /// + /// let entity = Entity::builder() + /// .handle("foo_example_com-1") + /// .status("active") + /// .role("registrant") + /// .contact(contact) + /// .build(); + /// ``` + #[builder(visibility = "pub")] + #[allow(clippy::too_many_arguments)] + fn new>( + handle: T, + remarks: Vec, + links: Vec, + events: Vec, + statuses: Vec, + port_43: Option, + entities: Vec, + as_event_actors: Vec, + contact: Option, + roles: Vec, + public_ids: Vec, + notices: Vec, + networks: Vec, + autnums: Vec, + extensions: Vec, + redacted: Option>, + ) -> Self { + Self { + common: Common::level0() + .extensions(extensions) + .and_notices(to_opt_vec(notices)) + .build(), + object_common: ObjectCommon::entity() + .handle(handle.into()) + .and_remarks(to_opt_vec(remarks)) + .and_links(to_opt_vec(links)) + .and_events(to_opt_vec(events)) + .status(statuses) + .and_port_43(port_43) + .and_entities(to_opt_vec(entities)) + .and_redacted(redacted) + .build(), + vcard_array: contact.map(|c| c.to_vcard()), + roles: to_opt_vectorstringish(roles), + public_ids: to_opt_vec(public_ids), + as_event_actor: to_opt_vec(as_event_actors), + autnums: to_opt_vec(autnums), + networks: to_opt_vec(networks), + } + } + + /// Convenience method to get a [Contact] from the impentrable vCard. + pub fn contact(&self) -> Option { + let vcard = self.vcard_array.as_ref()?; + Contact::from_vcard(vcard) + } + + /// Convenience method to get the roles. + pub fn roles(&self) -> &Vec { + self.roles + .as_ref() + .map(|v| v.vec()) + .unwrap_or(&EMPTY_VEC_STRING) + } + + /// Convenience method to get the public IDs. + pub fn public_ids(&self) -> &Vec { + self.public_ids.as_ref().unwrap_or(&EMPTY_PUBLIC_IDS) + } + + /// Convenience method to get the events this entity acted on. + pub fn as_event_actors(&self) -> &Vec { + self.as_event_actor + .as_ref() + .unwrap_or(&EMPTY_AS_EVENT_ACTORS) + } + + /// Convenience method to get the autnums. + pub fn autnums(&self) -> &Vec { + self.autnums.as_ref().unwrap_or(&EMPTY_AUTNUMS) + } + + /// Convenience method to get the networks. + pub fn networks(&self) -> &Vec { + self.networks.as_ref().unwrap_or(&EMPTY_NETWORKS) + } +} + +impl ToResponse for Entity { + fn to_response(self) -> super::RdapResponse { + super::RdapResponse::Entity(Box::new(self)) + } +} + +impl GetSelfLink for Entity { + fn get_self_link(&self) -> Option<&Link> { + self.object_common.get_self_link() + } +} + +impl SelfLink for Entity { + fn set_self_link(mut self, link: Link) -> Self { + self.object_common = self.object_common.set_self_link(link); + self + } +} + +impl ToChild for Entity { + fn to_child(mut self) -> Self { + self.common = Common { + rdap_conformance: None, + notices: None, + }; + self + } +} + +impl CommonFields for Entity { + fn common(&self) -> &Common { + &self.common + } +} + +impl ObjectCommonFields for Entity { + fn object_common(&self) -> &ObjectCommon { + &self.object_common + } +} + +/// IANA registered roles for entities. +#[derive(PartialEq, Eq, Debug, EnumString, Display)] +#[strum(serialize_all = "lowercase")] +pub enum EntityRole { + Registrant, + Technical, + Administrative, + Abuse, + Billing, + Registrar, + Reseller, + Sponsor, + Proxy, + Notifications, + Noc, +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use super::Entity; + + #[test] + fn GIVEN_entity_WHEN_deserialize_THEN_success() { + // GIVEN + let expected = r#" + { + "objectClassName" : "entity", + "handle":"XXXX", + "vcardArray":[ + "vcard", + [ + ["version", {}, "text", "4.0"], + ["fn", {}, "text", "Joe User"], + ["n", {}, "text", + ["User", "Joe", "", "", ["ing. jr", "M.Sc."]] + ], + ["kind", {}, "text", "individual"], + ["lang", { + "pref":"1" + }, "language-tag", "fr"], + ["lang", { + "pref":"2" + }, "language-tag", "en"], + ["org", { + "type":"work" + }, "text", "Example"], + ["title", {}, "text", "Research Scientist"], + ["role", {}, "text", "Project Lead"], + ["adr", + { "type":"work" }, + "text", + [ + "", + "Suite 1234", + "4321 Rue Somewhere", + "Quebec", + "QC", + "G1V 2M2", + "Canada" + ] + ], + ["adr", + { + "type":"home", + "label":"123 Maple Ave\nSuite 90001\nVancouver\nBC\n1239\n" + }, + "text", + [ + "", "", "", "", "", "", "" + ] + ], + ["tel", + { + "type":["work", "voice"], + "pref":"1" + }, + "uri", + "tel:+1-555-555-1234;ext=102" + ], + ["tel", + { "type":["work", "cell", "voice", "video", "text"] }, + "uri", + "tel:+1-555-555-4321" + ], + ["email", + { "type":"work" }, + "text", + "joe.user@example.com" + ], + ["geo", { + "type":"work" + }, "uri", "geo:46.772673,-71.282945"], + ["key", + { "type":"work" }, + "uri", + "https://www.example.com/joe.user/joe.asc" + ], + ["tz", {}, + "utc-offset", "-05:00"], + ["url", { "type":"home" }, + "uri", "https://example.org"] + ] + ], + "roles":[ "registrar" ], + "publicIds":[ + { + "type":"IANA Registrar ID", + "identifier":"1" + } + ], + "remarks":[ + { + "description":[ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links":[ + { + "value":"https://example.com/entity/XXXX", + "rel":"self", + "href":"https://example.com/entity/XXXX", + "type" : "application/rdap+json" + } + ], + "events":[ + { + "eventAction":"registration", + "eventDate":"1990-12-31T23:59:59Z" + } + ], + "asEventActor":[ + + { + "eventAction":"last changed", + "eventDate":"1991-12-31T23:59:59Z" + } + ] + } + "#; + + // WHEN + let actual = serde_json::from_str::(expected); + + // THEN + let actual = actual.unwrap(); + assert_eq!(actual.object_common.object_class_name, "entity"); + assert!(actual.object_common.handle.is_some()); + assert!(actual.vcard_array.is_some()); + assert!(actual.roles.is_some()); + assert!(actual.public_ids.is_some()); + assert!(actual.object_common.remarks.is_some()); + assert!(actual.object_common.links.is_some()); + assert!(actual.object_common.events.is_some()); + assert!(actual.as_event_actor.is_some()); + } +} diff --git a/icann-rdap-common/src/response/error.rs b/icann-rdap-common/src/response/error.rs new file mode 100644 index 0000000..6b6c3bc --- /dev/null +++ b/icann-rdap-common/src/response/error.rs @@ -0,0 +1,119 @@ +//! RFC 9083 Error +use { + crate::prelude::Extension, + serde::{Deserialize, Serialize}, +}; + +use crate::media_types::RDAP_MEDIA_TYPE; + +use super::{ + types::{Link, Notice, NoticeOrRemark}, + Common, CommonFields, ToResponse, +}; + +/// Represents an error response from an RDAP server. +/// +/// This structure represents the JSON returned by an RDAP server +/// describing an error. +/// See [RFC 9083, Section 6](https://datatracker.ietf.org/doc/html/rfc9083#name-error-response-body). +/// +/// Do not confuse this with [crate::response::RdapResponseError]. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Rfc9083Error { + #[serde(flatten)] + pub common: Common, + + #[serde(rename = "errorCode")] + pub error_code: u16, + + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option>, +} + +#[buildstructor::buildstructor] +impl Rfc9083Error { + /// Creates a new RFC 9083 Error for a specific HTTP error code. + #[builder(visibility = "pub")] + fn new(error_code: u16, notices: Vec, extensions: Vec) -> Self { + let notices = (!notices.is_empty()).then_some(notices); + Self { + common: Common::level0() + .extensions(extensions) + .and_notices(notices) + .build(), + error_code, + title: None, + description: None, + } + } + + /// Creates an RFC 9083 error for an HTTP redirect. + #[builder(entry = "redirect", visibility = "pub")] + fn new_redirect(url: String, extensions: Vec) -> Self { + let links = vec![Link::builder() + .href(&url) + .value(&url) + .media_type(RDAP_MEDIA_TYPE) + .rel("related") + .build()]; + let notices = vec![Notice(NoticeOrRemark::builder().links(links).build())]; + Self { + common: Common::level0() + .extensions(extensions) + .notices(notices) + .build(), + error_code: 307, + title: None, + description: None, + } + } + + pub fn is_redirect(&self) -> bool { + self.error_code > 299 && self.error_code < 400 + } +} + +impl CommonFields for Rfc9083Error { + fn common(&self) -> &Common { + &self.common + } +} + +impl ToResponse for Rfc9083Error { + fn to_response(self) -> super::RdapResponse { + super::RdapResponse::ErrorResponse(Box::new(self)) + } +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use super::Rfc9083Error; + + #[test] + fn GIVEN_error_code_301_WHEN_is_redirect_THEN_true() { + // GIVEN + let e = Rfc9083Error::redirect().url("https://foo.example").build(); + + // WHEN + let actual = e.is_redirect(); + + // THEN + assert!(actual); + } + + #[test] + fn GIVEN_error_code_404_WHEN_is_redirect_THEN_false() { + // GIVEN + let e = Rfc9083Error::builder().error_code(404).build(); + + // WHEN + let actual = e.is_redirect(); + + // THEN + assert!(!actual); + } +} diff --git a/icann-rdap-common/src/response/help.rs b/icann-rdap-common/src/response/help.rs new file mode 100644 index 0000000..8c9fb78 --- /dev/null +++ b/icann-rdap-common/src/response/help.rs @@ -0,0 +1,40 @@ +//! Server Help Response. +use { + crate::prelude::{Extension, Notice}, + serde::{Deserialize, Serialize}, +}; + +use super::{to_opt_vec, Common, CommonFields, ToResponse}; + +/// Represents an RDAP help response. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Help { + #[serde(flatten)] + pub common: Common, +} + +#[buildstructor::buildstructor] +impl Help { + /// Builds a basic help response. + #[builder(visibility = "pub")] + fn new(notices: Vec, extensions: Vec) -> Self { + Self { + common: Common::level0() + .extensions(extensions) + .and_notices(to_opt_vec(notices)) + .build(), + } + } +} + +impl CommonFields for Help { + fn common(&self) -> &Common { + &self.common + } +} + +impl ToResponse for Help { + fn to_response(self) -> super::RdapResponse { + super::RdapResponse::Help(Box::new(self)) + } +} diff --git a/icann-rdap-common/src/response/lenient.rs b/icann-rdap-common/src/response/lenient.rs new file mode 100644 index 0000000..dbf1909 --- /dev/null +++ b/icann-rdap-common/src/response/lenient.rs @@ -0,0 +1,626 @@ +//! Types for more lenient processing of invalid RDAP + +use std::{fmt::Display, marker::PhantomData, str::FromStr}; + +use { + serde::{de::Visitor, Deserialize, Deserializer, Serialize}, + serde_json::Number, +}; + +use crate::check::StringListCheck; + +/// A type that is suppose to be a vector of strings. +/// +/// Provides a choice between a string or a vector of strings for deserialization. +/// +/// This type is provided to be lenient with misbehaving RDAP servers that +/// serve a string when they are suppose to be serving an array of +/// strings. +/// +/// Use one of the From methods for construction. +/// ```rust +/// use icann_rdap_common::prelude::*; +/// +/// let v = VectorStringish::from(vec!["one".to_string(), "two".to_string()]); +/// +/// // or +/// +/// let v = VectorStringish::from("one".to_string()); +/// ```` +#[derive(Serialize, Clone, Debug, PartialEq, Eq)] +#[serde(transparent)] +pub struct VectorStringish { + vec: Vec, + #[serde(skip)] + is_string: bool, +} + +impl<'de> Deserialize<'de> for VectorStringish { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(VectorStringishVisitor) + } +} + +struct VectorStringishVisitor; + +impl<'de> Visitor<'de> for VectorStringishVisitor { + type Value = VectorStringish; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("expected an array of strings") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + Ok(VectorStringish { + vec: vec![v.to_owned()], + is_string: true, + }) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut v = vec![]; + loop { + let n = seq.next_element()?; + if let Some(s) = n { + v.push(s); + } else { + break; + } + } + Ok(VectorStringish { + vec: v, + is_string: false, + }) + } +} + +impl From for VectorStringish { + fn from(value: String) -> Self { + VectorStringish { + vec: vec![value], + is_string: false, + } + } +} + +impl From<&str> for VectorStringish { + fn from(value: &str) -> Self { + VectorStringish { + vec: vec![value.to_owned()], + is_string: false, + } + } +} + +impl From> for VectorStringish { + fn from(value: Vec) -> Self { + VectorStringish { + vec: value, + is_string: false, + } + } +} + +impl From for Vec { + fn from(value: VectorStringish) -> Self { + value.vec + } +} + +impl From<&VectorStringish> for Vec { + fn from(value: &VectorStringish) -> Self { + value.vec.to_owned() + } +} + +impl VectorStringish { + /// Consumes and converts it to a `Vec`. + pub fn into_vec(self) -> Vec { + self.vec + } + + /// Gets a reference to the underlying `Vec`. + pub fn vec(&self) -> &Vec { + &self.vec + } + + /// Returns true if the deserialization was as a string. + pub fn is_string(&self) -> bool { + self.is_string + } +} + +impl StringListCheck for VectorStringish { + fn is_empty_or_any_empty_or_whitespace(&self) -> bool { + self.vec().is_empty_or_any_empty_or_whitespace() + } + + fn is_ldh_string_list(&self) -> bool { + self.vec().is_ldh_string_list() + } +} + +/// Returns `Some(VectorStringish)` if the vector is not empty, otherwise `None`. +pub fn to_opt_vectorstringish(vec: Vec) -> Option { + (!vec.is_empty()).then_some(VectorStringish::from(vec)) +} + +pub(crate) static EMPTY_VEC_STRING: Vec = vec![]; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(untagged)] +enum BoolishInner { + /// Valid RDAP. + Bool(bool), + + /// Invalide RDAP. + String(String), +} + +/// A type that is suppose to be a boolean. +/// +/// Provides a choice between a boolean or a string representation of a boolean for deserialization. +/// +/// This type is provided to be lenient with misbehaving RDAP servers that +/// serve a string representation of a boolean when they are suppose to be serving a boolean +/// +/// Use one of the From methods for construction. +/// ```rust +/// use icann_rdap_common::prelude::*; +/// +/// let v = Boolish::from(true); +/// ```` +/// +/// When converting from a string (as would happen with deserialization), +/// the values "true", "t", "yes", and "y" (case-insensitive with whitespace trimmed) +/// will be true, all other values will be false. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(transparent)] +pub struct Boolish { + inner: BoolishInner, +} + +impl From for Boolish { + fn from(value: bool) -> Self { + Boolish { + inner: BoolishInner::Bool(value), + } + } +} + +impl Display for Boolish { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.into_bool()) + } +} + +impl Boolish { + /// Converts to a bool. + pub fn into_bool(&self) -> bool { + match &self.inner { + BoolishInner::Bool(value) => *value, + BoolishInner::String(value) => Boolish::is_true(value), + } + } + + /// Returns true if the deserialization was as a string. + pub fn is_string(&self) -> bool { + match &self.inner { + BoolishInner::Bool(_) => false, + BoolishInner::String(_) => true, + } + } + + fn is_true(value: &str) -> bool { + let s = value.trim().to_lowercase(); + s == "true" || s == "t" || s == "yes" || s == "y" + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(untagged)] +enum NumberishInner { + /// Valid RDAP. + Number(Number), + + /// Invalide RDAP. + String(String), +} + +/// A type that is suppose to be a number. +/// +/// Provides a choice between a number or a string representation of a number for deserialization. +/// +/// This type is provided to be lenient with misbehaving RDAP servers that +/// serve a string representation of a number when they are suppose to be serving a number. +/// +/// Use the From methods for construction. +/// ```rust +/// use icann_rdap_common::prelude::*; +/// +/// let v = Numberish::from(123); +/// ```` +/// +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(transparent)] +pub struct Numberish { + inner: NumberishInner, + phatom: PhantomData, +} + +impl From for Numberish +where + Number: From, +{ + fn from(value: T) -> Self { + Numberish { + inner: NumberishInner::Number(Number::from(value)), + phatom: PhantomData, + } + } +} + +impl Display for Numberish +where + Number: From, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + self.as_u64() + .map_or("RANGE_ERRROR".to_string(), |u| u.to_string()) + ) + } +} + +impl Numberish +where + Number: From, +{ + /// Returns true if the deserialization was as a string. + pub fn is_string(&self) -> bool { + match &self.inner { + NumberishInner::Number(_) => false, + NumberishInner::String(_) => true, + } + } + + /// If the `Number` is an integer, represent it as u64 if possible. Returns None otherwise. + pub fn as_u64(&self) -> Option { + match &self.inner { + NumberishInner::Number(n) => n.as_u64(), + NumberishInner::String(s) => Number::from_str(s).ok()?.as_u64(), + } + } + + /// If the `Number` is an integer, represent it as u32 if possible. Returns None otherwise. + pub fn as_u32(&self) -> Option { + match &self.inner { + NumberishInner::Number(n) => n.as_u64()?.try_into().ok(), + NumberishInner::String(s) => Number::from_str(s).ok()?.as_u64()?.try_into().ok(), + } + } + + /// If the `Number` is an integer, represent it as u16 if possible. Returns None otherwise. + pub fn as_u16(&self) -> Option { + match &self.inner { + NumberishInner::Number(n) => n.as_u64()?.try_into().ok(), + NumberishInner::String(s) => Number::from_str(s).ok()?.as_u64()?.try_into().ok(), + } + } + + /// If the `Number` is an integer, represent it as u8 if possible. Returns None otherwise. + pub fn as_u8(&self) -> Option { + match &self.inner { + NumberishInner::Number(n) => n.as_u64()?.try_into().ok(), + NumberishInner::String(s) => Number::from_str(s).ok()?.as_u64()?.try_into().ok(), + } + } +} + +#[cfg(test)] +mod tests { + use { + super::*, + serde_json::{from_str, to_string}, + }; + + // + // VectorStringish tests + // + + #[test] + fn test_vectorstringish_serialize_many() { + // GIVEN + let many = VectorStringish::from(vec!["one".to_string(), "two".to_string()]); + + // WHEN + let serialized = to_string(&many).unwrap(); + + // THEN + assert_eq!(serialized, r#"["one","two"]"#); + } + + #[test] + fn test_vectorstringish_serialize_one() { + // GIVEN + let one = VectorStringish::from("one".to_string()); + + // WHEN + let serialized = to_string(&one).unwrap(); + + // THEN + assert_eq!(serialized, r#"["one"]"#); + } + + #[test] + fn test_vectorstringish_deserialize_many() { + // GIVEN + let json_str = r#"["one","two"]"#; + + // WHEN + let deserialized: VectorStringish = from_str(json_str).unwrap(); + + // THEN + assert_eq!( + deserialized.vec(), + &vec!["one".to_string(), "two".to_string()] + ); + + // and THEN is not string + assert!(!deserialized.is_string()) + } + + #[test] + fn test_vectorstringish_deserialize_one() { + // GIVEN + let json_str = r#""one""#; + + // WHEN + let deserialized: VectorStringish = from_str(json_str).unwrap(); + + // THEN + assert_eq!(deserialized.vec(), &vec!["one".to_string()]); + + // and THEN is string + assert!(deserialized.is_string()) + } + + // + // Boolish tests + // + + #[test] + fn test_boolish_serialize_bool() { + // GIVEN + let b = Boolish::from(true); + + // WHEN + let serialized = to_string(&b).unwrap(); + + // THEN + assert_eq!(serialized, "true"); + } + + #[test] + fn test_boolish_deserialize_bool_true() { + // GIVEN + let json_str = "true"; + + // WHEN + let deserialized: Boolish = from_str(json_str).unwrap(); + + // THEN + assert!(deserialized.into_bool()); + assert!(!deserialized.is_string()); + } + + #[test] + fn test_boolish_deserialize_bool_false() { + // GIVEN + let json_str = "false"; + + // WHEN + let deserialized: Boolish = from_str(json_str).unwrap(); + + // THEN + assert!(!deserialized.into_bool()); + assert!(!deserialized.is_string()); + } + + #[test] + fn test_boolish_deserialize_string_true() { + // GIVEN + let json_str = r#""true""#; + + // WHEN + let deserialized: Boolish = from_str(json_str).unwrap(); + + // THEN + assert!(deserialized.into_bool()); + assert!(deserialized.is_string()); + } + + #[test] + fn test_boolish_deserialize_string_false() { + // GIVEN + let json_str = r#""false""#; + + // WHEN + let deserialized: Boolish = from_str(json_str).unwrap(); + + // THEN + assert!(!deserialized.into_bool()); + assert!(deserialized.is_string()); + } + + #[test] + fn test_boolish_is_true() { + // GIVEN various true values + let true_values = ["true", "t", "yes", "y", " True ", " T ", " Yes ", " Y "]; + + // THEN all are true + for value in true_values { + assert!(Boolish::is_true(value)); + } + } + + #[test] + fn test_boolish_is_false() { + // GIVEN various false values + let false_values = ["false", "f", "no", "n", "False", "blah", "1", "0", ""]; + + // THEN all are false + for value in false_values { + assert!(!Boolish::is_true(value)); + } + } + + #[test] + fn test_boolish_from_bool() { + assert!(Boolish::from(true).into_bool()); + assert!(!Boolish::from(false).into_bool()); + } + + // + // Numberish Tests + // + + #[test] + fn test_numberish_serialize_number() { + // GIVEN a Numberish from a number + let n = Numberish::::from(123); + + // WHEN serialized + let serialized = to_string(&n).unwrap(); + + // THEN it is the correct string + assert_eq!(serialized, "123"); + } + + #[test] + fn test_numberish_deserialize_number() { + // GIVEN a JSON string representing a number + let json_str = "123"; + + // WHEN deserialized + let deserialized: Numberish = from_str(json_str).unwrap(); + + // THEN the value is correct and it's not a string + assert_eq!(deserialized.as_u32(), Some(123)); + assert!(!deserialized.is_string()); + } + + #[test] + fn test_numberish_deserialize_string() { + // GIVEN a JSON string representing a number as a string + let json_str = r#""123""#; + + // WHEN deserialized + let deserialized: Numberish = from_str(json_str).unwrap(); + + // THEN the value is correct and it's a string + assert_eq!(deserialized.as_u32(), Some(123)); + assert!(deserialized.is_string()); + } + + #[test] + fn test_numberish_as_u64_number() { + // GIVEN a Numberish from a u64 + let n = Numberish::from(123u64); + + // WHEN as_u64 is called + let result = n.as_u64(); + + // THEN the result is Some(123) + assert_eq!(result, Some(123)); + } + + #[test] + fn test_numberish_as_u64_string_invalid() { + // GIVEN a Numberish from a string that does not represent a u64 + let n = Numberish { + inner: NumberishInner::String("abc".to_string()), + phatom: PhantomData::, + }; + + // WHEN as_u64 is called + let result = n.as_u64(); + + // THEN the result is None + assert_eq!(result, None); + } + + #[test] + fn test_numberish_as_smaller_types() { + // GIVEN a valid number + let n = Numberish::from(123u64); + + // THEN smaller type conversions work + assert_eq!(n.as_u32(), Some(123)); + assert_eq!(n.as_u16(), Some(123)); + assert_eq!(n.as_u8(), Some(123)); + + // GIVEN a number too large + let n = Numberish::from(u32::MAX as u64 + 1); + + // THEN smaller type conversions fail + assert_eq!(n.as_u32(), None); + assert_eq!(n.as_u16(), None); + assert_eq!(n.as_u8(), None); + + // GIVEN a valid number string + let n = Numberish { + inner: NumberishInner::String("123".to_string()), + phatom: PhantomData::, + }; + + // THEN smaller type conversions work + assert_eq!(n.as_u32(), Some(123)); + assert_eq!(n.as_u16(), Some(123)); + assert_eq!(n.as_u8(), Some(123)); + + // GIVEN a number string too large + let n = Numberish { + inner: NumberishInner::String((u32::MAX as u64 + 1).to_string()), + phatom: PhantomData::, + }; + + // THEN smaller type conversions fail + assert_eq!(n.as_u32(), None); + } + + #[test] + fn test_numberish_display_number() { + let n = Numberish::::from(123); + assert_eq!(format!("{}", n), "123"); + } + + #[test] + fn test_numberish_display_string_valid() { + let n = Numberish { + inner: NumberishInner::String("123".to_string()), + phatom: PhantomData::, + }; + assert_eq!(format!("{}", n), "123"); + } + + #[test] + fn test_numberish_display_string_invalid() { + let n = Numberish { + inner: NumberishInner::String("abc".to_string()), + phatom: PhantomData::, + }; + assert_eq!(format!("{}", n), "RANGE_ERRROR"); + } +} diff --git a/icann-rdap-common/src/response/mod.rs b/icann-rdap-common/src/response/mod.rs new file mode 100644 index 0000000..4fbb080 --- /dev/null +++ b/icann-rdap-common/src/response/mod.rs @@ -0,0 +1,652 @@ +//! RDAP structures for parsing and creating RDAP responses. +use std::any::TypeId; + +use { + cidr, + serde::{Deserialize, Serialize}, + serde_json::Value, + strum_macros::Display, + thiserror::Error, +}; + +use crate::media_types::RDAP_MEDIA_TYPE; + +#[doc(inline)] +pub use autnum::*; +#[doc(inline)] +pub use common::*; +#[doc(inline)] +pub use domain::*; +#[doc(inline)] +pub use entity::*; +#[doc(inline)] +pub use error::*; +#[doc(inline)] +pub use help::*; +#[doc(inline)] +pub use lenient::*; +#[doc(inline)] +pub use nameserver::*; +#[doc(inline)] +pub use network::*; +#[doc(inline)] +pub use obj_common::*; +#[doc(inline)] +pub use search::*; +#[doc(inline)] +pub use types::*; + +pub(crate) mod autnum; +pub(crate) mod common; +pub(crate) mod domain; +pub(crate) mod entity; +pub(crate) mod error; +pub(crate) mod help; +pub(crate) mod lenient; +pub(crate) mod nameserver; +pub(crate) mod network; +pub(crate) mod obj_common; +pub mod redacted; // RFC 9537 is not a mainstream extension. +pub(crate) mod search; +pub(crate) mod types; + +/// An error caused be processing an RDAP response. +/// +/// This is caused because the JSON constituting the +/// RDAP response has a problem that cannot be overcome. +/// +/// Do not confuse this with [Rfc9083Error]. +#[derive(Debug, Error)] +pub enum RdapResponseError { + /// The JSON type is incorrect. + #[error("Wrong JSON type: {0}")] + WrongJsonType(String), + + /// The type of RDAP response is unknown. + #[error("Unknown RDAP response.")] + UnknownRdapResponse, + + /// An error has occurred parsing the JSON. + #[error(transparent)] + SerdeJson(#[from] serde_json::Error), + + /// An error with parsing an IP address. + #[error(transparent)] + AddrParse(#[from] std::net::AddrParseError), + + /// An error caused with parsing a CIDR address. + #[error(transparent)] + CidrParse(#[from] cidr::errors::NetworkParseError), +} + +/// The various types of RDAP response. +/// +/// It can be parsed from JSON using serde: +/// +/// ```rust +/// use icann_rdap_common::response::RdapResponse; +/// +/// let json = r#" +/// { +/// "objectClassName": "ip network", +/// "links": [ +/// { +/// "value": "http://localhost:3000/rdap/ip/10.0.0.0/16", +/// "rel": "self", +/// "href": "http://localhost:3000/rdap/ip/10.0.0.0/16", +/// "type": "application/rdap+json" +/// } +/// ], +/// "events": [ +/// { +/// "eventAction": "registration", +/// "eventDate": "2023-06-16T22:56:49.594173356+00:00" +/// }, +/// { +/// "eventAction": "last changed", +/// "eventDate": "2023-06-16T22:56:49.594189140+00:00" +/// } +/// ], +/// "startAddress": "10.0.0.0", +/// "endAddress": "10.0.255.255", +/// "ipVersion": "v4" +/// } +/// "#; +/// +/// let rdap: RdapResponse = serde_json::from_str(json).unwrap(); +/// assert!(matches!(rdap, RdapResponse::Network(_))); +/// ``` +#[derive(Serialize, Deserialize, Clone, Display, PartialEq, Debug)] +#[serde(untagged, try_from = "Value")] +pub enum RdapResponse { + // Object Classes + Entity(Box), + Domain(Box), + Nameserver(Box), + Autnum(Box), + Network(Box), + + // Search Results + DomainSearchResults(Box), + EntitySearchResults(Box), + NameserverSearchResults(Box), + + // Error + ErrorResponse(Box), + + // Help + Help(Box), + // These are all boxed to keep the variant size alligned. + // While not completely necessary for all these variants today, + // this will prevent an API change in the future when new items + // are added to each variant when supporting future RDAP extensions. +} + +impl TryFrom for RdapResponse { + type Error = RdapResponseError; + + fn try_from(value: Value) -> Result { + let response = if let Some(object) = value.as_object() { + object + } else { + return Err(RdapResponseError::WrongJsonType( + "response is not an object".to_string(), + )); + }; + + // if it has an objectClassName + if let Some(class_name) = response.get("objectClassName") { + if let Some(name_str) = class_name.as_str() { + return match name_str { + "domain" => Ok(serde_json::from_value::(value)?.to_response()), + "entity" => Ok(serde_json::from_value::(value)?.to_response()), + "nameserver" => Ok(serde_json::from_value::(value)?.to_response()), + "autnum" => Ok(serde_json::from_value::(value)?.to_response()), + "ip network" => Ok(serde_json::from_value::(value)?.to_response()), + _ => Err(RdapResponseError::UnknownRdapResponse), + }; + } else { + return Err(RdapResponseError::WrongJsonType( + "'objectClassName' is not a string".to_string(), + )); + } + }; + + // else if it is a domain search result + if let Some(result) = response.get("domainSearchResults") { + if result.is_array() { + return Ok(serde_json::from_value::(value)?.to_response()); + } else { + return Err(RdapResponseError::WrongJsonType( + "'domainSearchResults' is not an array".to_string(), + )); + } + } + // else if it is a entity search result + if let Some(result) = response.get("entitySearchResults") { + if result.is_array() { + return Ok(serde_json::from_value::(value)?.to_response()); + } else { + return Err(RdapResponseError::WrongJsonType( + "'entitySearchResults' is not an array".to_string(), + )); + } + } + // else if it is a nameserver search result + if let Some(result) = response.get("nameserverSearchResults") { + if result.is_array() { + return Ok(serde_json::from_value::(value)?.to_response()); + } else { + return Err(RdapResponseError::WrongJsonType( + "'nameserverSearchResults' is not an array".to_string(), + )); + } + } + + // else if it has an errorCode + if let Some(result) = response.get("errorCode") { + if result.is_u64() { + return Ok(serde_json::from_value::(value)?.to_response()); + } else { + return Err(RdapResponseError::WrongJsonType( + "'errorCode' is not an unsigned integer".to_string(), + )); + } + } + + // else if it has a notices then it is help response at this point + if let Some(result) = response.get("notices") { + if result.is_array() { + return Ok(serde_json::from_value::(value)?.to_response()); + } else { + return Err(RdapResponseError::WrongJsonType( + "'notices' is not an array".to_string(), + )); + } + } + Err(RdapResponseError::UnknownRdapResponse) + } +} + +impl RdapResponse { + pub fn get_type(&self) -> TypeId { + match self { + Self::Entity(_) => TypeId::of::(), + Self::Domain(_) => TypeId::of::(), + Self::Nameserver(_) => TypeId::of::(), + Self::Autnum(_) => TypeId::of::(), + Self::Network(_) => TypeId::of::(), + Self::DomainSearchResults(_) => TypeId::of::(), + Self::EntitySearchResults(_) => TypeId::of::(), + Self::NameserverSearchResults(_) => TypeId::of::(), + Self::ErrorResponse(_) => TypeId::of::(), + Self::Help(_) => TypeId::of::(), + } + } + + pub fn get_links(&self) -> Option<&Links> { + match self { + Self::Entity(e) => e.object_common.links.as_ref(), + Self::Domain(d) => d.object_common.links.as_ref(), + Self::Nameserver(n) => n.object_common.links.as_ref(), + Self::Autnum(a) => a.object_common.links.as_ref(), + Self::Network(n) => n.object_common.links.as_ref(), + Self::DomainSearchResults(_) + | Self::EntitySearchResults(_) + | Self::NameserverSearchResults(_) + | Self::ErrorResponse(_) + | Self::Help(_) => None, + } + } + + pub fn get_conformance(&self) -> Option<&RdapConformance> { + match self { + Self::Entity(e) => e.common.rdap_conformance.as_ref(), + Self::Domain(d) => d.common.rdap_conformance.as_ref(), + Self::Nameserver(n) => n.common.rdap_conformance.as_ref(), + Self::Autnum(a) => a.common.rdap_conformance.as_ref(), + Self::Network(n) => n.common.rdap_conformance.as_ref(), + Self::DomainSearchResults(s) => s.common.rdap_conformance.as_ref(), + Self::EntitySearchResults(s) => s.common.rdap_conformance.as_ref(), + Self::NameserverSearchResults(s) => s.common.rdap_conformance.as_ref(), + Self::ErrorResponse(e) => e.common.rdap_conformance.as_ref(), + Self::Help(h) => h.common.rdap_conformance.as_ref(), + } + } + + pub fn has_extension_id(&self, extension_id: ExtensionId) -> bool { + self.get_conformance().map_or(false, |conformance| { + conformance.contains(&extension_id.to_extension()) + }) + } + + pub fn has_extension(&self, extension: &str) -> bool { + self.get_conformance().map_or(false, |conformance| { + conformance.contains(&Extension::from(extension)) + }) + } + + pub fn is_redirect(&self) -> bool { + match self { + Self::ErrorResponse(e) => e.is_redirect(), + _ => false, + } + } +} + +impl GetSelfLink for RdapResponse { + fn get_self_link(&self) -> Option<&Link> { + self.get_links() + .and_then(|links| links.iter().find(|link| link.is_relation("self"))) + } +} + +/// A trait for converting structs into an appropriate [RdapResponse] variant. +pub trait ToResponse { + /// Consumes the object and returns an [RdapResponse]. + fn to_response(self) -> RdapResponse; +} + +/// Trait for getting a link with a `rel` of "self". +pub trait GetSelfLink { + /// Get's the first self link. + /// See [crate::response::ObjectCommon::get_self_link()]. + fn get_self_link(&self) -> Option<&Link>; +} + +/// Train for setting a link with a `rel` of "self". +pub trait SelfLink: GetSelfLink { + /// See [crate::response::ObjectCommon::get_self_link()]. + fn set_self_link(self, link: Link) -> Self; +} + +/// Gets the `href` of a link with `rel` of "related" and `type` with the RDAP media type. +pub fn get_related_links(rdap_response: &RdapResponse) -> Vec<&str> { + let Some(links) = rdap_response.get_links() else { + return vec![]; + }; + + let mut urls: Vec<_> = links + .iter() + .filter_map(|l| match (&l.href, &l.rel, &l.media_type) { + (Some(href), Some(rel), Some(media_type)) + if rel.eq_ignore_ascii_case("related") + && media_type.eq_ignore_ascii_case(RDAP_MEDIA_TYPE) => + { + Some(href.as_str()) + } + _ => None, + }) + .collect(); + + // if none are found with correct media type, look for something that looks like an RDAP link + if urls.is_empty() { + urls = links + .iter() + .filter(|l| { + if let Some(href) = l.href() { + if let Some(rel) = l.rel() { + rel.eq_ignore_ascii_case("related") + && (href.contains("/domain/") + || href.contains("/ip/") + || href.contains("/autnum/") + || href.contains("/nameserver/") + || href.contains("/entity/")) + } else { + false + } + } else { + false + } + }) + .map(|l| l.href.as_ref().unwrap().as_str()) + .collect::>(); + } + urls +} + +/// Makes a root object class suitable for being embedded in another object class. +pub trait ToChild { + /// Removes notices and rdapConformance so this object can be a child + /// of another object. + fn to_child(self) -> Self; +} + +/// Returns `Some(Vec)` if the vector is not empty, otherwise `None`. +pub fn to_opt_vec(vec: Vec) -> Option> { + (!vec.is_empty()).then_some(vec) +} + +/// Returns `Vec` if `is_some()` else an empty vector. +pub fn opt_to_vec(opt: Option>) -> Vec { + opt.unwrap_or_default() +} + +#[cfg(test)] +mod tests { + use serde_json::Value; + + use crate::media_types::RDAP_MEDIA_TYPE; + + use super::{get_related_links, Domain, Link, RdapResponse, ToResponse}; + + #[test] + fn test_redaction_response_gets_object() { + // GIVEN + let expected: Value = + serde_json::from_str(include_str!("test_files/lookup_with_redaction.json")).unwrap(); + + // WHEN + let actual = RdapResponse::try_from(expected).unwrap(); + + // THEN + assert!(matches!(actual, RdapResponse::Domain(_))); + } + + #[test] + fn test_redaction_response_has_extension() { + // GIVEN + let expected: Value = + serde_json::from_str(include_str!("test_files/lookup_with_redaction.json")).unwrap(); + + // WHEN + let actual = RdapResponse::try_from(expected).unwrap(); + + // THEN + assert!(actual.has_extension_id(crate::response::types::ExtensionId::Redacted)); + } + + #[test] + fn test_redaction_response_domain_search() { + // GIVEN + let expected: Value = + serde_json::from_str(include_str!("test_files/domain_search_with_redaction.json")) + .unwrap(); + + // WHEN + let actual = RdapResponse::try_from(expected).unwrap(); + + // THEN + assert!(matches!(actual, RdapResponse::DomainSearchResults(_))); + } + + #[test] + fn test_resopnse_is_domain() { + // GIVEN + let expected: Value = + serde_json::from_str(include_str!("test_files/domain_afnic_fr.json")).unwrap(); + + // WHEN + let actual = RdapResponse::try_from(expected).unwrap(); + + // THEN + assert!(matches!(actual, RdapResponse::Domain(_))); + } + + #[test] + fn test_response_is_entity() { + // GIVEN + let expected: Value = + serde_json::from_str(include_str!("test_files/entity_arin_hostmaster.json")).unwrap(); + + // WHEN + let actual = RdapResponse::try_from(expected).unwrap(); + + // THEN + assert!(matches!(actual, RdapResponse::Entity(_))); + } + + #[test] + fn test_response_is_nameserver() { + // GIVEN + let expected: Value = + serde_json::from_str(include_str!("test_files/nameserver_ns1_nic_fr.json")).unwrap(); + + // WHEN + let actual = RdapResponse::try_from(expected).unwrap(); + + // THEN + assert!(matches!(actual, RdapResponse::Nameserver(_))); + } + + #[test] + fn test_response_is_autnum() { + // GIVEN + let expected: Value = + serde_json::from_str(include_str!("test_files/autnum_16509.json")).unwrap(); + + // WHEN + let actual = RdapResponse::try_from(expected).unwrap(); + + // THEN + assert!(matches!(actual, RdapResponse::Autnum(_))); + } + + #[test] + fn test_response_is_network() { + // GIVEN + let expected: Value = + serde_json::from_str(include_str!("test_files/network_192_198_0_0.json")).unwrap(); + + // WHEN + let actual = RdapResponse::try_from(expected).unwrap(); + + // THEN + assert!(matches!(actual, RdapResponse::Network(_))); + } + + #[test] + fn test_response_is_domain_search_results() { + // GIVEN + let expected: Value = + serde_json::from_str(include_str!("test_files/domains_ldhname_ns1_arin_net.json")) + .unwrap(); + + // WHEN + let actual = RdapResponse::try_from(expected).unwrap(); + + // THEN + assert!(matches!(actual, RdapResponse::DomainSearchResults(_))); + } + + #[test] + fn test_response_is_entity_search_results() { + // GIVEN + let expected: Value = + serde_json::from_str(include_str!("test_files/entities_fn_arin.json")).unwrap(); + + // WHEN + let actual = RdapResponse::try_from(expected).unwrap(); + + // THEN + assert!(matches!(actual, RdapResponse::EntitySearchResults(_))); + } + + #[test] + fn test_response_is_help() { + // GIVEN + let expected: Value = + serde_json::from_str(include_str!("test_files/help_nic_fr.json")).unwrap(); + + // WHEN + let actual = RdapResponse::try_from(expected).unwrap(); + + // THEN + assert!(matches!(actual, RdapResponse::Help(_))); + } + + #[test] + fn test_response_is_error() { + // GIVEN + let expected: Value = + serde_json::from_str(include_str!("test_files/error_ripe_net.json")).unwrap(); + + // WHEN + let actual = RdapResponse::try_from(expected).unwrap(); + + // THEN + assert!(matches!(actual, RdapResponse::ErrorResponse(_))); + } + + #[test] + fn test_string_is_entity_search_results() { + // GIVEN + let entity: Value = + serde_json::from_str(include_str!("test_files/entities_fn_arin.json")).unwrap(); + let value = RdapResponse::try_from(entity).unwrap(); + + // WHEN + let actual = value.to_string(); + + // THEN + assert_eq!(actual, "EntitySearchResults"); + } + + #[test] + fn test_get_related_for_non_rel_link() { + // GIVEN + let rdap = Domain::builder() + .ldh_name("example.com") + .link( + Link::builder() + .rel("not-related") + .href("http://example.com") + .value("http://example.com") + .build(), + ) + .build() + .to_response(); + + // WHEN + let links = get_related_links(&rdap); + + // THEN + assert!(links.is_empty()); + } + + #[test] + fn test_get_related_for_rel_with_rdap_type_link() { + // GIVEN + let link = Link::builder() + .rel("related") + .href("http://example.com") + .value("http://example.com") + .media_type(RDAP_MEDIA_TYPE) + .build(); + let rdap = Domain::builder() + .ldh_name("example.com") + .link(link.clone()) + .build() + .to_response(); + + // WHEN + let links = get_related_links(&rdap); + + // THEN + assert!(!links.is_empty()); + assert_eq!(links.first().expect("empty links"), &link.href().unwrap()); + } + + #[test] + fn test_get_related_for_rel_link() { + // GIVEN + let link = Link::builder() + .rel("related") + .href("http://example.com") + .value("http://example.com") + .build(); + let rdap = Domain::builder() + .ldh_name("example.com") + .link(link.clone()) + .build() + .to_response(); + + // WHEN + let links = get_related_links(&rdap); + + // THEN + assert!(links.is_empty()); + } + + #[test] + fn test_get_related_for_rel_link_that_look_like_rdap() { + // GIVEN + let link = Link::builder() + .rel("related") + .href("http://example.com/domain/foo") + .value("http://example.com") + .build(); + let rdap = Domain::builder() + .ldh_name("example.com") + .link(link.clone()) + .build() + .to_response(); + + // WHEN + let links = get_related_links(&rdap); + + // THEN + assert!(!links.is_empty()); + assert_eq!(links.first().expect("empty links"), &link.href().unwrap()); + } +} diff --git a/icann-rdap-common/src/response/nameserver.rs b/icann-rdap-common/src/response/nameserver.rs new file mode 100644 index 0000000..024d14c --- /dev/null +++ b/icann-rdap-common/src/response/nameserver.rs @@ -0,0 +1,341 @@ +//! RDAP Nameserver object class. +use { + crate::prelude::{Common, Extension, ObjectCommon}, + std::{net::IpAddr, str::FromStr}, +}; + +use serde::{Deserialize, Serialize}; + +use super::{ + to_opt_vec, to_opt_vectorstringish, types::Link, CommonFields, Entity, Event, GetSelfLink, + Notice, ObjectCommonFields, Port43, RdapResponseError, Remark, SelfLink, ToChild, ToResponse, + VectorStringish, EMPTY_VEC_STRING, +}; + +/// Represents an IP address set for nameservers. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] +pub struct IpAddresses { + #[serde(skip_serializing_if = "Option::is_none")] + pub v6: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub v4: Option, +} + +#[buildstructor::buildstructor] +impl IpAddresses { + /// Builds nameserver IP address. + #[builder(visibility = "pub")] + fn new(addresses: Vec) -> Result { + let mut v4: Vec = Vec::new(); + let mut v6: Vec = Vec::new(); + for addr in addresses { + let ip = IpAddr::from_str(&addr)?; + match ip { + IpAddr::V4(_) => v4.push(addr), + IpAddr::V6(_) => v6.push(addr), + } + } + Ok(Self { + v4: to_opt_vectorstringish(v4), + v6: to_opt_vectorstringish(v6), + }) + } + + #[allow(dead_code)] + #[builder(entry = "illegal", visibility = "pub(crate)")] + fn new_illegal(v6: Option>, v4: Option>) -> Self { + Self { + v4: v4.map(VectorStringish::from), + v6: v6.map(VectorStringish::from), + } + } + + /// Get the IPv6 addresses. + pub fn v6s(&self) -> &Vec { + self.v6 + .as_ref() + .map(|v| v.vec()) + .unwrap_or(&EMPTY_VEC_STRING) + } + + /// Get the IPv4 addresses. + pub fn v4s(&self) -> &Vec { + self.v4 + .as_ref() + .map(|v| v.vec()) + .unwrap_or(&EMPTY_VEC_STRING) + } +} + +/// Represents an RDAP [nameserver](https://rdap.rcode3.com/protocol/object_classes.html#nameserver) response. +/// +/// Using the builder is recommended to construct this structure as it +/// will fill-in many of the mandatory fields. +/// The following is an example. +/// +/// ```rust +/// use icann_rdap_common::prelude::*; +/// +/// let ns = Nameserver::builder() +/// .ldh_name("ns1.example.com") +/// .handle("ns1_example_com-1") +/// .status("active") +/// .address("10.0.0.1") +/// .address("10.0.0.2") +/// .entity(Entity::builder().handle("FOO").build()) +/// .build().unwrap(); +/// let c = serde_json::to_string_pretty(&ns).unwrap(); +/// eprintln!("{c}"); +/// ``` +/// +/// This will produce the following. +/// +/// ```norust +/// { +/// "rdapConformance": [ +/// "rdap_level_0" +/// ], +/// "objectClassName": "nameserver", +/// "handle": "ns1_example_com-1", +/// "status": [ +/// "active" +/// ], +/// "entities": [ +/// { +/// "rdapConformance": [ +/// "rdap_level_0" +/// ], +/// "objectClassName": "entity", +/// "handle": "FOO" +/// } +/// ], +/// "ldhName": "ns1.example.com", +/// "ipAddresses": { +/// "v4": [ +/// "10.0.0.1", +/// "10.0.0.2" +/// ] +/// } +/// } +/// ``` +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Nameserver { + #[serde(flatten)] + pub common: Common, + + #[serde(flatten)] + pub object_common: ObjectCommon, + + #[serde(rename = "ldhName")] + #[serde(skip_serializing_if = "Option::is_none")] + pub ldh_name: Option, + + #[serde(rename = "unicodeName")] + #[serde(skip_serializing_if = "Option::is_none")] + pub unicode_name: Option, + + #[serde(rename = "ipAddresses")] + #[serde(skip_serializing_if = "Option::is_none")] + pub ip_addresses: Option, +} + +#[buildstructor::buildstructor] +impl Nameserver { + /// Builds a basic nameserver object. + /// + /// ```rust + /// use icann_rdap_common::prelude::*; + /// + /// let ns = Nameserver::builder() + /// .ldh_name("ns1.example.com") + /// .handle("ns1_example_com-1") + /// .status("active") + /// .address("10.0.0.1") + /// .address("10.0.0.2") + /// .entity(Entity::builder().handle("FOO").build()) + /// .build().unwrap(); + /// ``` + #[builder(visibility = "pub")] + #[allow(clippy::too_many_arguments)] + fn new>( + ldh_name: T, + addresses: Vec, + handle: Option, + remarks: Vec, + links: Vec, + events: Vec, + statuses: Vec, + port_43: Option, + entities: Vec, + notices: Vec, + extensions: Vec, + redacted: Option>, + ) -> Result { + let ip_addresses = if !addresses.is_empty() { + Some(IpAddresses::builder().addresses(addresses).build()?) + } else { + None + }; + Ok(Self { + common: Common::level0() + .extensions(extensions) + .and_notices(to_opt_vec(notices)) + .build(), + object_common: ObjectCommon::nameserver() + .and_handle(handle) + .and_remarks(to_opt_vec(remarks)) + .and_links(to_opt_vec(links)) + .and_events(to_opt_vec(events)) + .status(statuses) + .and_port_43(port_43) + .and_entities(to_opt_vec(entities)) + .and_redacted(redacted) + .build(), + ldh_name: Some(ldh_name.into()), + unicode_name: None, + ip_addresses, + }) + } + + #[builder(entry = "illegal", visibility = "pub(crate)")] + #[allow(clippy::too_many_arguments)] + #[allow(dead_code)] + fn new_illegal(ldh_name: Option, ip_addresses: Option) -> Self { + Self { + common: Common::level0().build(), + object_common: ObjectCommon::nameserver().build(), + ldh_name, + unicode_name: None, + ip_addresses, + } + } + + /// Get the LDH name. + pub fn ldh_name(&self) -> Option<&str> { + self.ldh_name.as_deref() + } + + /// Get the Unicode name. + pub fn unicode_name(&self) -> Option<&str> { + self.unicode_name.as_deref() + } + + /// Get the IP addresses. + pub fn ip_addresses(&self) -> Option<&IpAddresses> { + self.ip_addresses.as_ref() + } +} + +impl ToResponse for Nameserver { + fn to_response(self) -> super::RdapResponse { + super::RdapResponse::Nameserver(Box::new(self)) + } +} + +impl GetSelfLink for Nameserver { + fn get_self_link(&self) -> Option<&Link> { + self.object_common.get_self_link() + } +} + +impl SelfLink for Nameserver { + fn set_self_link(mut self, link: Link) -> Self { + self.object_common = self.object_common.set_self_link(link); + self + } +} + +impl ToChild for Nameserver { + fn to_child(mut self) -> Self { + self.common = Common { + rdap_conformance: None, + notices: None, + }; + self + } +} + +impl CommonFields for Nameserver { + fn common(&self) -> &Common { + &self.common + } +} + +impl ObjectCommonFields for Nameserver { + fn object_common(&self) -> &ObjectCommon { + &self.object_common + } +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use super::Nameserver; + + #[test] + fn GIVEN_nameserver_WHEN_deserialize_THEN_success() { + // GIVEN + let expected = r#" + { + "objectClassName" : "nameserver", + "handle" : "XXXX", + "ldhName" : "ns1.xn--fo-5ja.example", + "unicodeName" : "ns.fóo.example", + "status" : [ "active" ], + "ipAddresses" : + { + "v4": [ "192.0.2.1", "192.0.2.2" ], + "v6": [ "2001:db8::123" ] + }, + "remarks" : + [ + { + "description" : + [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links" : + [ + { + "value" : "https://example.net/nameserver/ns1.xn--fo-5ja.example", + "rel" : "self", + "href" : "https://example.net/nameserver/ns1.xn--fo-5ja.example", + "type" : "application/rdap+json" + } + ], + "port43" : "whois.example.net", + "events" : + [ + { + "eventAction" : "registration", + "eventDate" : "1990-12-31T23:59:59Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "1991-12-31T23:59:59Z", + "eventActor" : "joe@example.com" + } + ] + } + "#; + + // WHEN + let actual = serde_json::from_str::(expected); + + // THEN + let actual = actual.unwrap(); + assert_eq!(actual.object_common.object_class_name, "nameserver"); + assert!(actual.object_common.handle.is_some()); + assert!(actual.ldh_name.is_some()); + assert!(actual.unicode_name.is_some()); + assert!(actual.ip_addresses.is_some()); + assert!(actual.object_common.remarks.is_some()); + assert!(actual.object_common.status.is_some()); + assert!(actual.object_common.links.is_some()); + assert!(actual.object_common.events.is_some()); + } +} diff --git a/icann-rdap-common/src/response/network.rs b/icann-rdap-common/src/response/network.rs new file mode 100644 index 0000000..d28dc58 --- /dev/null +++ b/icann-rdap-common/src/response/network.rs @@ -0,0 +1,548 @@ +//! RDAP IP Network. +use { + crate::prelude::{Common, Extension, ObjectCommon}, + std::str::FromStr, +}; + +use { + cidr::IpInet, + serde::{Deserialize, Serialize}, +}; + +use super::{ + to_opt_vec, + types::{ExtensionId, Link}, + CommonFields, Entity, Event, GetSelfLink, Notice, Numberish, ObjectCommonFields, Port43, + RdapResponseError, Remark, SelfLink, ToChild, ToResponse, +}; + +/// Cidr0 structure from the Cidr0 extension. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(untagged)] +pub enum Cidr0Cidr { + V4Cidr(V4Cidr), + V6Cidr(V6Cidr), +} + +impl std::fmt::Display for Cidr0Cidr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::V4Cidr(cidr) => cidr.fmt(f), + Self::V6Cidr(cidr) => cidr.fmt(f), + } + } +} + +/// Represents a CIDR0 V4 CIDR. +/// +/// This structure allow both the prefix +/// and length to be optional to handle misbehaving servers, however +/// both are required according to the CIDR0 RDAP extension. To create +/// a valid stucture, use the builder. +/// +/// However, it is recommended to use the builder on `Network` which will +/// create the appropriate CIDR0 structure. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct V4Cidr { + pub v4prefix: Option, + pub length: Option>, +} + +#[buildstructor::buildstructor] +impl V4Cidr { + /// Builds an Ipv4 CIDR0. + #[builder(visibility = "pub")] + fn new(v4prefix: String, length: u8) -> Self { + V4Cidr { + v4prefix: Some(v4prefix), + length: Some(Numberish::::from(length)), + } + } +} + +impl std::fmt::Display for V4Cidr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let length_s = if let Some(length) = &self.length { + length.to_string() + } else { + "not_given".to_string() + }; + write!( + f, + "{}/{}", + self.v4prefix.as_ref().unwrap_or(&"not_given".to_string()), + length_s + ) + } +} + +/// Represents a CIDR0 V6 CIDR. +/// +/// This structure allow both the prefix +/// and length to be optional to handle misbehaving servers, however +/// both are required according to the CIDR0 RDAP extension. To create +/// a valid stucture, use the builder. +/// +/// However, it is recommended to use the builder on `Network` which will +/// create the appropriate CIDR0 structure. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct V6Cidr { + pub v6prefix: Option, + pub length: Option>, +} + +#[buildstructor::buildstructor] +impl V6Cidr { + /// Builds an IPv6 CIDR0. + #[builder(visibility = "pub")] + fn new(v6prefix: String, length: u8) -> Self { + V6Cidr { + v6prefix: Some(v6prefix), + length: Some(Numberish::::from(length)), + } + } +} + +impl std::fmt::Display for V6Cidr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let length_s = if let Some(length) = &self.length { + length.to_string() + } else { + "not_given".to_string() + }; + write!( + f, + "{}/{}", + self.v6prefix.as_ref().unwrap_or(&"not_given".to_string()), + length_s + ) + } +} + +/// Represents an RDAP [IP network](https://rdap.rcode3.com/protocol/object_classes.html#ip-network) response. +/// +/// Use of the builder is recommended to create this structure. +/// The builder will create the appropriate CIDR0 structures and +/// is easier than specifying start and end IP addresses. +/// +/// ```rust +/// use icann_rdap_common::prelude::*; +/// +/// let net = Network::builder() +/// .cidr("10.0.0.0/24") +/// .handle("NET-10-0-0-0") +/// .status("active") +/// .build().unwrap(); +/// ``` +/// +/// This will create the following RDAP structure. +/// +/// ```norust +/// { +/// "rdapConformance": [ +/// "cidr0", +/// "rdap_level_0" +/// ], +/// "objectClassName": "ip network", +/// "handle": "NET-10-0-0-0", +/// "status": [ +/// "active" +/// ], +/// "startAddress": "10.0.0.0", +/// "endAddress": "10.0.0.255", +/// "ipVersion": "v4", +/// "cidr0_cidrs": [ +/// { +/// "v4prefix": "10.0.0.0", +/// "length": 24 +/// } +/// ] +/// } +/// ``` +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Network { + #[serde(flatten)] + pub common: Common, + + #[serde(flatten)] + pub object_common: ObjectCommon, + + #[serde(rename = "startAddress")] + #[serde(skip_serializing_if = "Option::is_none")] + pub start_address: Option, + + #[serde(rename = "endAddress")] + #[serde(skip_serializing_if = "Option::is_none")] + pub end_address: Option, + + #[serde(rename = "ipVersion")] + #[serde(skip_serializing_if = "Option::is_none")] + pub ip_version: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + + #[serde(rename = "type")] + #[serde(skip_serializing_if = "Option::is_none")] + pub network_type: Option, + + #[serde(rename = "parentHandle")] + #[serde(skip_serializing_if = "Option::is_none")] + pub parent_handle: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub country: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub cidr0_cidrs: Option>, +} + +static EMPTY_CIDR0CIDRS: Vec = vec![]; + +#[buildstructor::buildstructor] +impl Network { + /// Builds a basic IP network object. + /// + /// ```rust + /// use icann_rdap_common::prelude::*; + /// + /// let net = Network::builder() + /// .cidr("10.0.0.0/24") + /// .handle("NET-10-0-0-0") + /// .status("active") + /// .build().unwrap(); + /// ``` + #[builder(visibility = "pub")] + #[allow(clippy::too_many_arguments)] + fn new( + cidr: String, + handle: Option, + country: Option, + name: Option, + network_type: Option, + parent_handle: Option, + remarks: Vec, + links: Vec, + events: Vec, + statuses: Vec, + port_43: Option, + entities: Vec, + notices: Vec, + mut extensions: Vec, + redacted: Option>, + ) -> Result { + let mut net_exts = vec![ExtensionId::Cidr0.to_extension()]; + net_exts.append(&mut extensions); + let cidr = IpInet::from_str(&cidr)?; + Ok(Self { + common: Common::level0() + .extensions(net_exts) + .and_notices(to_opt_vec(notices)) + .build(), + object_common: ObjectCommon::ip_network() + .and_handle(handle) + .and_remarks(to_opt_vec(remarks)) + .and_links(to_opt_vec(links)) + .and_events(to_opt_vec(events)) + .status(statuses) + .and_port_43(port_43) + .and_entities(to_opt_vec(entities)) + .and_redacted(redacted) + .build(), + start_address: Some(cidr.first_address().to_string()), + end_address: Some(cidr.last_address().to_string()), + ip_version: Some( + match cidr { + IpInet::V4(_) => "v4", + IpInet::V6(_) => "v6", + } + .to_string(), + ), + name, + network_type, + parent_handle, + country, + cidr0_cidrs: match cidr { + IpInet::V4(cidr) => Some(vec![Cidr0Cidr::V4Cidr(V4Cidr { + v4prefix: Some(cidr.first_address().to_string()), + length: Some(Numberish::::from(cidr.network_length())), + })]), + IpInet::V6(cidr) => Some(vec![Cidr0Cidr::V6Cidr(V6Cidr { + v6prefix: Some(cidr.first_address().to_string()), + length: Some(Numberish::::from(cidr.network_length())), + })]), + }, + }) + } + + #[builder(entry = "illegal", visibility = "pub(crate)")] + #[allow(clippy::too_many_arguments)] + #[allow(dead_code)] + fn new_illegal( + start_address: Option, + end_address: Option, + ip_version: Option, + cidr0_cidrs: Option>, + country: Option, + name: Option, + network_type: Option, + parent_handle: Option, + notices: Vec, + ) -> Self { + Self { + common: Common::level0() + .extension(ExtensionId::Cidr0.to_extension()) + .and_notices(to_opt_vec(notices)) + .build(), + object_common: ObjectCommon::ip_network().build(), + start_address, + end_address, + ip_version, + name, + network_type, + parent_handle, + country, + cidr0_cidrs, + } + } + + /// Returns the start address of the network. + pub fn start_address(&self) -> Option<&str> { + self.start_address.as_deref() + } + + /// Returns the end address of the network. + pub fn end_address(&self) -> Option<&str> { + self.end_address.as_deref() + } + + /// Returns the IP version of the network. + pub fn ip_version(&self) -> Option<&str> { + self.ip_version.as_deref() + } + + /// Returns the name of the network. + pub fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + /// Returns the type of the network. + pub fn network_type(&self) -> Option<&str> { + self.network_type.as_deref() + } + + /// Returns the parent handle of the network. + pub fn parent_handle(&self) -> Option<&str> { + self.parent_handle.as_deref() + } + + /// Returns the country of the network. + pub fn country(&self) -> Option<&str> { + self.country.as_deref() + } + + /// Returns the CIDR0 CIDRs of the network. + pub fn cidr0_cidrs(&self) -> &Vec { + self.cidr0_cidrs.as_ref().unwrap_or(&EMPTY_CIDR0CIDRS) + } +} + +impl ToResponse for Network { + fn to_response(self) -> super::RdapResponse { + super::RdapResponse::Network(Box::new(self)) + } +} + +impl GetSelfLink for Network { + fn get_self_link(&self) -> Option<&Link> { + self.object_common.get_self_link() + } +} + +impl SelfLink for Network { + fn set_self_link(mut self, link: Link) -> Self { + self.object_common = self.object_common.set_self_link(link); + self + } +} + +impl ToChild for Network { + fn to_child(mut self) -> Self { + self.common = Common { + rdap_conformance: None, + notices: None, + }; + self + } +} + +impl CommonFields for Network { + fn common(&self) -> &Common { + &self.common + } +} + +impl ObjectCommonFields for Network { + fn object_common(&self) -> &ObjectCommon { + &self.object_common + } +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use crate::response::network::Network; + + #[test] + fn GIVEN_network_WHEN_deserialize_THEN_success() { + let expected = r#" + { + "objectClassName" : "ip network", + "handle" : "XXXX-RIR", + "startAddress" : "2001:db8::", + "endAddress" : "2001:db8:0:ffff:ffff:ffff:ffff:ffff", + "ipVersion" : "v6", + "name": "NET-RTR-1", + "type" : "DIRECT ALLOCATION", + "country" : "AU", + "parentHandle" : "YYYY-RIR", + "status" : [ "active" ], + "remarks" : + [ + { + "description" : + [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links" : + [ + { + "value" : "https://example.net/ip/2001:db8::/48", + "rel" : "self", + "href" : "https://example.net/ip/2001:db8::/48", + "type" : "application/rdap+json" + }, + { + "value" : "https://example.net/ip/2001:db8::/48", + "rel" : "up", + "href" : "https://example.net/ip/2001:db8::/32", + "type" : "application/rdap+json" + } + ], + "events" : + [ + { + "eventAction" : "registration", + "eventDate" : "1990-12-31T23:59:59Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "1991-12-31T23:59:59Z" + } + ], + "entities" : + [ + { + "objectClassName" : "entity", + "handle" : "XXXX", + "vcardArray":[ + "vcard", + [ + ["version", {}, "text", "4.0"], + ["fn", {}, "text", "Joe User"], + ["kind", {}, "text", "individual"], + ["lang", { + "pref":"1" + }, "language-tag", "fr"], + ["lang", { + "pref":"2" + }, "language-tag", "en"], + ["org", { + "type":"work" + }, "text", "Example"], + ["title", {}, "text", "Research Scientist"], + ["role", {}, "text", "Project Lead"], + ["adr", + { "type":"work" }, + "text", + [ + "", + "Suite 1234", + "4321 Rue Somewhere", + "Quebec", + "QC", + "G1V 2M2", + "Canada" + ] + ], + ["tel", + { "type":["work", "voice"], "pref":"1" }, + "uri", "tel:+1-555-555-1234;ext=102" + ], + ["email", + { "type":"work" }, + "text", "joe.user@example.com" + ] + ] + ], + "roles" : [ "registrant" ], + "remarks" : + [ + { + "description" : + [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links" : + [ + { + "value" : "https://example.net/entity/xxxx", + "rel" : "self", + "href" : "https://example.net/entity/xxxx", + "type" : "application/rdap+json" + } + ], + "events" : + [ + { + "eventAction" : "registration", + "eventDate" : "1990-12-31T23:59:59Z" + + }, + { + "eventAction" : "last changed", + "eventDate" : "1991-12-31T23:59:59Z" + } + ] + } + ] + } + "#; + + // WHEN + let actual = serde_json::from_str::(expected); + + // THEN + let actual = actual.unwrap(); + assert_eq!(actual.object_common.object_class_name, "ip network"); + assert!(actual.object_common.handle.is_some()); + assert!(actual.start_address.is_some()); + assert!(actual.end_address.is_some()); + assert!(actual.ip_version.is_some()); + assert!(actual.name.is_some()); + assert!(actual.network_type.is_some()); + assert!(actual.parent_handle.is_some()); + assert!(actual.object_common.status.is_some()); + assert!(actual.country.is_some()); + assert!(actual.object_common.remarks.is_some()); + assert!(actual.object_common.links.is_some()); + assert!(actual.object_common.events.is_some()); + assert!(actual.object_common.entities.is_some()); + } +} diff --git a/icann-rdap-common/src/response/obj_common.rs b/icann-rdap-common/src/response/obj_common.rs new file mode 100644 index 0000000..ede1f2a --- /dev/null +++ b/icann-rdap-common/src/response/obj_common.rs @@ -0,0 +1,266 @@ +use serde::{Deserialize, Serialize}; + +use super::{ + redacted::Redacted, to_opt_vectorstringish, Entity, Events, Link, Links, Port43, Remarks, + VectorStringish, EMPTY_VEC_STRING, +}; + +/// Holds those types that are common in all object classes. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct ObjectCommon { + #[serde(rename = "objectClassName")] + pub object_class_name: String, + + #[serde(skip_serializing_if = "Option::is_none")] + pub handle: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub remarks: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub links: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub events: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "port43")] + pub port_43: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub entities: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub redacted: Option>, +} + +#[buildstructor::buildstructor] +impl ObjectCommon { + /// Builds [ObjectCommon] for a [crate::response::domain::Domain]. + #[builder(entry = "domain", visibility = "pub(crate)")] + #[allow(clippy::too_many_arguments)] + fn new_domain( + handle: Option, + remarks: Option, + links: Option, + events: Option, + status: Option>, + port_43: Option, + entities: Option>, + redacted: Option>, + ) -> Self { + Self { + object_class_name: "domain".to_string(), + handle, + remarks, + links, + events, + status: to_opt_vectorstringish(status.unwrap_or_default()), + port_43, + entities, + redacted, + } + } + + /// Builds [ObjectCommon] for a [crate::response::network::Network]. + #[builder(entry = "ip_network", visibility = "pub(crate)")] + #[allow(clippy::too_many_arguments)] + fn new_ip_network( + handle: Option, + remarks: Option, + links: Option, + events: Option, + status: Option>, + port_43: Option, + entities: Option>, + redacted: Option>, + ) -> Self { + Self { + object_class_name: "ip network".to_string(), + handle, + remarks, + links, + events, + status: to_opt_vectorstringish(status.unwrap_or_default()), + port_43, + entities, + redacted, + } + } + + /// Builds an [ObjectCommon] for an [crate::response::autnum::Autnum]. + #[builder(entry = "autnum", visibility = "pub(crate)")] + #[allow(clippy::too_many_arguments)] + fn new_autnum( + handle: Option, + remarks: Option, + links: Option, + events: Option, + status: Option>, + port_43: Option, + entities: Option>, + redacted: Option>, + ) -> Self { + Self { + object_class_name: "autnum".to_string(), + handle, + remarks, + links, + events, + status: to_opt_vectorstringish(status.unwrap_or_default()), + port_43, + entities, + redacted, + } + } + + /// Builds an [ObjectCommon] for a [crate::response::nameserver::Nameserver]. + #[builder(entry = "nameserver", visibility = "pub(crate)")] + #[allow(clippy::too_many_arguments)] + fn new_nameserver( + handle: Option, + remarks: Option, + links: Option, + events: Option, + status: Option>, + port_43: Option, + entities: Option>, + redacted: Option>, + ) -> Self { + Self { + object_class_name: "nameserver".to_string(), + handle, + remarks, + links, + events, + status: to_opt_vectorstringish(status.unwrap_or_default()), + port_43, + entities, + redacted, + } + } + + /// Builds an [ObjectCommon] for an [crate::response::entity::Entity]. + #[builder(entry = "entity", visibility = "pub(crate)")] + #[allow(clippy::too_many_arguments)] + fn new_entity( + handle: Option, + remarks: Option, + links: Option, + events: Option, + status: Option>, + port_43: Option, + entities: Option>, + redacted: Option>, + ) -> Self { + Self { + object_class_name: "entity".to_string(), + handle, + remarks, + links, + events, + status: to_opt_vectorstringish(status.unwrap_or_default()), + port_43, + entities, + redacted, + } + } + + /// This will remove all other self links and place the provided link + /// into the Links. This method will also set the "rel" attribute + /// to "self" on the provided link. + pub fn set_self_link(mut self, mut link: Link) -> Self { + link.rel = Some("self".to_string()); + if let Some(links) = self.links { + let mut new_links = links + .into_iter() + .filter(|link| !link.is_relation("self")) + .collect::>(); + new_links.push(link); + self.links = Some(new_links); + } else { + self.links = Some(vec![link]); + } + self + } + + /// Get the link with a `rel` of "self". + pub fn get_self_link(&self) -> Option<&Link> { + if let Some(links) = &self.links { + links.iter().find(|link| link.is_relation("self")) + } else { + None + } + } +} + +/// Empty Remarks. +static EMPTY_REMARKS: Remarks = vec![]; +/// Empty Links. +static EMPTY_LINKS: Links = vec![]; +/// Empty Events. +static EMPTY_EVENTS: Events = vec![]; +/// Empty Entities. +static EMPTY_ENTITIES: Vec = vec![]; + +/// Convenience methods for fields in [ObjectCommon]. +pub trait ObjectCommonFields { + /// Getter for [ObjectCommon]. + fn object_common(&self) -> &ObjectCommon; + + /// Returns the object class name. + fn object_class_name(&self) -> &str { + &self.object_common().object_class_name + } + + /// Returns the handle, if present. + fn handle(&self) -> Option<&str> { + self.object_common().handle.as_deref() + } + + /// Returns the port 43 information, if present. + fn port_43(&self) -> Option<&Port43> { + self.object_common().port_43.as_ref() + } + + /// Getter for [Remarks]. + fn remarks(&self) -> &Remarks { + self.object_common() + .remarks + .as_ref() + .unwrap_or(&EMPTY_REMARKS) + } + + /// Getter for [Links]. + fn links(&self) -> &Links { + self.object_common().links.as_ref().unwrap_or(&EMPTY_LINKS) + } + + /// Getter for [Events]. + fn events(&self) -> &Events { + self.object_common() + .events + .as_ref() + .unwrap_or(&EMPTY_EVENTS) + } + + /// Getter for status. + fn status(&self) -> &Vec { + self.object_common() + .status + .as_ref() + .map(|v| v.vec()) + .unwrap_or(&EMPTY_VEC_STRING) + } + + /// Getter for Vec of [Entity]. + fn entities(&self) -> &Vec { + self.object_common() + .entities + .as_ref() + .unwrap_or(&EMPTY_ENTITIES) + } +} diff --git a/icann-rdap-common/src/response/redacted.rs b/icann-rdap-common/src/response/redacted.rs new file mode 100644 index 0000000..9bde314 --- /dev/null +++ b/icann-rdap-common/src/response/redacted.rs @@ -0,0 +1,251 @@ +//! RFC 9537. +use { + buildstructor::Builder, + serde::{Deserialize, Serialize}, + std::{any::TypeId, fmt}, +}; + +use crate::check::Checks; + +/// Redacted registered name. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Name { + #[serde(rename = "description")] + pub description: Option, + + #[serde(rename = "type")] + pub type_field: Option, +} + +impl Name { + /// Get the description. + pub fn description(&self) -> Option<&String> { + self.description.as_ref() + } + + /// Get the redaction type. + pub fn type_field(&self) -> Option<&String> { + self.type_field.as_ref() + } +} + +/// Redaction reason. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] +pub struct Reason { + #[serde(rename = "description")] + pub description: Option, + + #[serde(rename = "type")] + pub type_field: Option, +} + +impl std::fmt::Display for Reason { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let output = self.description.clone().unwrap_or_default(); + write!(f, "{}", output) + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub enum Method { + Removal, + EmptyValue, + PartialValue, + ReplacementValue, +} + +/// RFC 9537 redaction structure. +#[derive(Builder, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Redacted { + #[serde[rename = "name"]] + pub name: Name, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "reason")] + pub reason: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "prePath")] + pub pre_path: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "postPath")] + pub post_path: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "pathLang")] + pub path_lang: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "replacementPath")] + pub replacement_path: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "method")] + pub method: Option, +} + +impl Default for Name { + fn default() -> Self { + Self { + description: Some(String::default()), + type_field: None, + } + } +} + +impl Default for Method { + fn default() -> Self { + Self::Removal // according to IETF draft this is the default + } +} + +impl fmt::Display for Method { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Removal => write!(f, "Removal"), + Self::EmptyValue => write!(f, "EmptyValue"), + Self::PartialValue => write!(f, "PartialValue"), + Self::ReplacementValue => write!(f, "ReplacementValue"), + } + } +} + +impl Redacted { + /// Get the checks from Redactions. + pub fn get_checks(&self, _check_params: crate::check::CheckParams<'_>) -> crate::check::Checks { + Checks { + rdap_struct: crate::check::RdapStructure::Redacted, + items: vec![], + sub_checks: vec![], + } + } + + /// Get the type. + pub fn get_type(&self) -> std::any::TypeId { + TypeId::of::() + } +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use super::*; + + #[test] + fn GIVEN_redaction_WHEN_set_THEN_success() { + // GIVEN + let name = Name { + description: Some("Registry Domain ID".to_string()), + type_field: None, + }; + + // WHEN + let redacted = Redacted::builder() + .name(name) + .reason(Reason::default()) + .pre_path("$.handle".to_string()) + .post_path("$.entities[?(@.roles[0]=='registrant'".to_string()) + .path_lang("jsonpath".to_string()) + .replacement_path( + "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='contact-uri')]" + .to_string(), + ) + .method(Method::Removal) + .build(); + + // THEN + assert_eq!( + redacted.name.description, + Some("Registry Domain ID".to_string()) + ); + assert_eq!(redacted.pre_path, Some("$.handle".to_string())); + assert_eq!( + redacted.post_path, + Some("$.entities[?(@.roles[0]=='registrant'".to_string()) + ); + assert_eq!(redacted.path_lang, Some("jsonpath".to_string())); + assert_eq!( + redacted.replacement_path, + Some( + "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='contact-uri')]" + .to_string() + ) + ); + assert_eq!(redacted.method, Some(Method::Removal)); + } + + #[test] + fn GIVEN_redaction_WHEN_deserialize_THEN_success() { + // GIVEN + let expected = r#" + { + "name": { + "type": "Registry Domain ID" + }, + "prePath": "$.handle", + "pathLang": "jsonpath", + "postPath": "$.entities[?(@.roles[0]=='registrant'", + "replacementPath": "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='contact-uri')]", + "method": "removal", + "reason": { + "description": "Server policy" + } + } + "#; + + // in this one we swap the two fields + let name = Name { + type_field: Some("Registry Domain ID".to_string()), + description: None, + }; + + let reason: Reason = Reason { + description: Some("Server policy".to_string()), + type_field: None, + }; + + // WHEN + // use the builder for most of the fields but not all + let mut sample_redact: Redacted = Redacted::builder() + .name(name) + .pre_path("$.handle".to_string()) + .path_lang("jsonpath".to_string()) + .post_path("$.entities[?(@.roles[0]=='registrant'".to_string()) + .replacement_path( + "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='contact-uri')]" + .to_string(), + ) + .build(); + + // also make sure we can set the rest + sample_redact.method = Some(Method::Removal); + sample_redact.reason = Some(reason); + + let actual: Result = + serde_json::from_str::(expected); + + // THEN + let actual: Redacted = actual.unwrap(); + assert_eq!(actual, sample_redact); // sanity check + assert_eq!( + actual.name.type_field, + Some("Registry Domain ID".to_string()) + ); + assert_eq!(actual.pre_path, Some("$.handle".to_string())); + assert_eq!( + actual.post_path, + Some("$.entities[?(@.roles[0]=='registrant'".to_string()) + ); + assert_eq!(actual.path_lang, Some("jsonpath".to_string())); + assert_eq!( + actual.replacement_path, + Some( + "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='contact-uri')]" + .to_string() + ) + ); + assert_eq!(actual.method, Some(Method::Removal)); + } +} diff --git a/icann-rdap-common/src/response/search.rs b/icann-rdap-common/src/response/search.rs new file mode 100644 index 0000000..8c8ddea --- /dev/null +++ b/icann-rdap-common/src/response/search.rs @@ -0,0 +1,109 @@ +//! RDAP Search Results. +use { + crate::prelude::{Common, Extension}, + serde::{Deserialize, Serialize}, +}; + +use super::{domain::Domain, entity::Entity, nameserver::Nameserver, CommonFields, ToResponse}; + +/// Represents RDAP domain search results. +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Eq)] +pub struct DomainSearchResults { + #[serde(flatten)] + pub common: Common, + + #[serde(rename = "domainSearchResults")] + pub results: Vec, +} + +#[buildstructor::buildstructor] +impl DomainSearchResults { + /// Builds a domain search result. + #[builder(visibility = "pub")] + fn new(results: Vec, extensions: Vec) -> Self { + Self { + common: Common::level0().extensions(extensions).build(), + results, + } + } +} + +impl CommonFields for DomainSearchResults { + fn common(&self) -> &Common { + &self.common + } +} + +impl ToResponse for DomainSearchResults { + fn to_response(self) -> super::RdapResponse { + super::RdapResponse::DomainSearchResults(Box::new(self)) + } +} + +/// Represents RDAP nameserver search results. +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Eq)] +pub struct NameserverSearchResults { + #[serde(flatten)] + pub common: Common, + + #[serde(rename = "nameserverSearchResults")] + pub results: Vec, +} + +#[buildstructor::buildstructor] +impl NameserverSearchResults { + /// Builds a nameserver search result. + #[builder(visibility = "pub")] + fn new(results: Vec, extensions: Vec) -> Self { + Self { + common: Common::level0().extensions(extensions).build(), + results, + } + } +} + +impl CommonFields for NameserverSearchResults { + fn common(&self) -> &Common { + &self.common + } +} + +impl ToResponse for NameserverSearchResults { + fn to_response(self) -> super::RdapResponse { + super::RdapResponse::NameserverSearchResults(Box::new(self)) + } +} + +/// Represents RDAP entity search results. +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Eq)] +pub struct EntitySearchResults { + #[serde(flatten)] + pub common: Common, + + #[serde(rename = "entitySearchResults")] + pub results: Vec, +} + +#[buildstructor::buildstructor] +impl EntitySearchResults { + /// Builds an entity search result. + #[builder(visibility = "pub")] + fn new(results: Vec, extensions: Vec) -> Self { + Self { + common: Common::level0().extensions(extensions).build(), + results, + } + } +} + +impl CommonFields for EntitySearchResults { + fn common(&self) -> &Common { + &self.common + } +} + +impl ToResponse for EntitySearchResults { + fn to_response(self) -> super::RdapResponse { + super::RdapResponse::EntitySearchResults(Box::new(self)) + } +} diff --git a/icann-rdap-common/src/response/test_files/autnum_16509.json b/icann-rdap-common/src/response/test_files/autnum_16509.json new file mode 100644 index 0000000..bde6d36 --- /dev/null +++ b/icann-rdap-common/src/response/test_files/autnum_16509.json @@ -0,0 +1,866 @@ +{ + "endAutnum" : 16509, + "entities" : [ + { + "entities" : [ + { + "events" : [ + { + "eventAction" : "last changed", + "eventDate" : "2022-09-30T16:26:55-04:00" + }, + { + "eventAction" : "registration", + "eventDate" : "2021-07-22T10:42:42-04:00" + } + ], + "handle" : "ARMP-ARIN", + "links" : [ + { + "href" : "https://rdap.arin.net/registry/entity/ARMP-ARIN", + "rel" : "self", + "type" : "application/rdap+json", + "value" : "https://rdap.arin.net/registry/autnum/16509" + }, + { + "href" : "https://whois.arin.net/rest/poc/ARMP-ARIN", + "rel" : "alternate", + "type" : "application/xml", + "value" : "https://rdap.arin.net/registry/autnum/16509" + } + ], + "objectClassName" : "entity", + "port43" : "whois.arin.net", + "roles" : [ + "routing" + ], + "status" : [ + "validated" + ], + "vcardArray" : [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "adr", + { + "label" : "13200 Woodland Park Dr\nHerndon\nHerndon\nVA\n20171\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "fn", + {}, + "text", + "AWS RPKI Management POC" + ], + [ + "org", + {}, + "text", + "AWS RPKI Management POC" + ], + [ + "kind", + {}, + "text", + "group" + ], + [ + "email", + {}, + "text", + "aws-rpki-routing-poc@amazon.com" + ], + [ + "tel", + { + "type" : [ + "work", + "voice" + ] + }, + "text", + "+1-206-555-0000" + ] + ] + ] + }, + { + "events" : [ + { + "eventAction" : "last changed", + "eventDate" : "2022-09-07T22:23:27-04:00" + }, + { + "eventAction" : "registration", + "eventDate" : "2019-07-24T13:17:11-04:00" + } + ], + "handle" : "IPROU3-ARIN", + "links" : [ + { + "href" : "https://rdap.arin.net/registry/entity/IPROU3-ARIN", + "rel" : "self", + "type" : "application/rdap+json", + "value" : "https://rdap.arin.net/registry/autnum/16509" + }, + { + "href" : "https://whois.arin.net/rest/poc/IPROU3-ARIN", + "rel" : "alternate", + "type" : "application/xml", + "value" : "https://rdap.arin.net/registry/autnum/16509" + } + ], + "objectClassName" : "entity", + "port43" : "whois.arin.net", + "remarks" : [ + { + "description" : [ + "Report abuse incidents to our Abuse POC AEA8-ARIN. ", + "", + "Thank you for your cooperation." + ], + "title" : "Registration Comments" + } + ], + "roles" : [ + "routing" + ], + "status" : [ + "validated" + ], + "vcardArray" : [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "adr", + { + "label" : "1918 8th Ave\nSeattle\nWA\n98109\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "fn", + {}, + "text", + "IP Routing" + ], + [ + "org", + {}, + "text", + "IP Routing" + ], + [ + "kind", + {}, + "text", + "group" + ], + [ + "email", + {}, + "text", + "aws-routing-poc@amazon.com" + ], + [ + "tel", + { + "type" : [ + "work", + "voice" + ] + }, + "text", + "+1-206-555-0000" + ] + ] + ] + }, + { + "events" : [ + { + "eventAction" : "last changed", + "eventDate" : "2022-08-22T19:08:51-04:00" + }, + { + "eventAction" : "registration", + "eventDate" : "2008-03-24T14:12:07-04:00" + } + ], + "handle" : "AEA8-ARIN", + "links" : [ + { + "href" : "https://rdap.arin.net/registry/entity/AEA8-ARIN", + "rel" : "self", + "type" : "application/rdap+json", + "value" : "https://rdap.arin.net/registry/autnum/16509" + }, + { + "href" : "https://whois.arin.net/rest/poc/AEA8-ARIN", + "rel" : "alternate", + "type" : "application/xml", + "value" : "https://rdap.arin.net/registry/autnum/16509" + } + ], + "objectClassName" : "entity", + "port43" : "whois.arin.net", + "remarks" : [ + { + "description" : [ + "Amazon Web Services Abuse - The activity you have detected originates from a dynamic hosting environment. For fastest response, please submit abuse reports to abuse@amazonaws.com", + "All reports MUST include:", + "* src IP", + "* dest IP (your IP)", + "* dest port", + "* Accurate date/timestamp and timezone of activity", + "* Intensity/frequency (short log extracts)", + "* Your contact details (phone and email)", + "Without these we will be unable to identify the correct owner of the IP address at that point in time." + ], + "title" : "Registration Comments" + } + ], + "roles" : [ + "abuse" + ], + "status" : [ + "validated" + ], + "vcardArray" : [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "adr", + { + "label" : "Amazon Web Services Elastic Compute Cloud, EC2\n410 Terry Avenue North\nSeattle\nWA\n98109-5210\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "fn", + {}, + "text", + "Amazon EC2 Abuse" + ], + [ + "org", + {}, + "text", + "Amazon EC2 Abuse" + ], + [ + "kind", + {}, + "text", + "group" + ], + [ + "email", + {}, + "text", + "abuse@amazonaws.com" + ], + [ + "tel", + { + "type" : [ + "work", + "voice" + ] + }, + "text", + "+1-206-555-0000" + ] + ] + ] + }, + { + "events" : [ + { + "eventAction" : "last changed", + "eventDate" : "2022-08-24T13:17:35-04:00" + }, + { + "eventAction" : "registration", + "eventDate" : "2005-09-19T06:00:05-04:00" + } + ], + "handle" : "ANO24-ARIN", + "links" : [ + { + "href" : "https://rdap.arin.net/registry/entity/ANO24-ARIN", + "rel" : "self", + "type" : "application/rdap+json", + "value" : "https://rdap.arin.net/registry/autnum/16509" + }, + { + "href" : "https://whois.arin.net/rest/poc/ANO24-ARIN", + "rel" : "alternate", + "type" : "application/xml", + "value" : "https://rdap.arin.net/registry/autnum/16509" + } + ], + "objectClassName" : "entity", + "port43" : "whois.arin.net", + "roles" : [ + "technical" + ], + "status" : [ + "validated" + ], + "vcardArray" : [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "adr", + { + "label" : "PO BOX 81226\nSeattle\nWA\n98108-1226\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "fn", + {}, + "text", + "Amazon EC2 Network Operations" + ], + [ + "org", + {}, + "text", + "Amazon EC2 Network Operations" + ], + [ + "kind", + {}, + "text", + "group" + ], + [ + "email", + {}, + "text", + "amzn-noc-contact@amazon.com" + ], + [ + "tel", + { + "type" : [ + "work", + "voice" + ] + }, + "text", + "+1-206-555-0000" + ] + ] + ] + }, + { + "events" : [ + { + "eventAction" : "last changed", + "eventDate" : "2022-08-24T13:17:49-04:00" + }, + { + "eventAction" : "registration", + "eventDate" : "2010-03-04T18:38:30-05:00" + } + ], + "handle" : "AANO1-ARIN", + "links" : [ + { + "href" : "https://rdap.arin.net/registry/entity/AANO1-ARIN", + "rel" : "self", + "type" : "application/rdap+json", + "value" : "https://rdap.arin.net/registry/autnum/16509" + }, + { + "href" : "https://whois.arin.net/rest/poc/AANO1-ARIN", + "rel" : "alternate", + "type" : "application/xml", + "value" : "https://rdap.arin.net/registry/autnum/16509" + } + ], + "objectClassName" : "entity", + "port43" : "whois.arin.net", + "roles" : [ + "noc" + ], + "status" : [ + "validated" + ], + "vcardArray" : [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "adr", + { + "label" : "410 Terry Ave N\nSeattle\nWA\n98109\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "fn", + {}, + "text", + "Amazon AWS Network Operations" + ], + [ + "org", + {}, + "text", + "Amazon AWS Network Operations" + ], + [ + "kind", + {}, + "text", + "group" + ], + [ + "email", + {}, + "text", + "amzn-noc-contact@amazon.com" + ], + [ + "tel", + { + "type" : [ + "work", + "voice" + ] + }, + "text", + "+1-206-555-0000" + ] + ] + ] + }, + { + "events" : [ + { + "eventAction" : "last changed", + "eventDate" : "2022-09-23T09:50:49-04:00" + }, + { + "eventAction" : "registration", + "eventDate" : "2013-11-12T22:06:06-05:00" + } + ], + "handle" : "IPMAN40-ARIN", + "links" : [ + { + "href" : "https://rdap.arin.net/registry/entity/IPMAN40-ARIN", + "rel" : "self", + "type" : "application/rdap+json", + "value" : "https://rdap.arin.net/registry/autnum/16509" + }, + { + "href" : "https://whois.arin.net/rest/poc/IPMAN40-ARIN", + "rel" : "alternate", + "type" : "application/xml", + "value" : "https://rdap.arin.net/registry/autnum/16509" + } + ], + "objectClassName" : "entity", + "port43" : "whois.arin.net", + "remarks" : [ + { + "description" : [ + "Report abuse incidents to our Abuse POC AEA8-ARIN. ", + "", + "Thank you for your cooperation." + ], + "title" : "Registration Comments" + } + ], + "roles" : [ + "administrative" + ], + "status" : [ + "validated" + ], + "vcardArray" : [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "adr", + { + "label" : "1918 8th Ave\nSeattle\nWA\n98109\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "fn", + {}, + "text", + "IP Management" + ], + [ + "org", + {}, + "text", + "IP Management" + ], + [ + "kind", + {}, + "text", + "group" + ], + [ + "email", + {}, + "text", + "ipmanagement@amazon.com" + ], + [ + "tel", + { + "type" : [ + "work", + "voice" + ] + }, + "text", + "+1-206-555-0000" + ] + ] + ] + } + ], + "events" : [ + { + "eventAction" : "last changed", + "eventDate" : "2022-09-30T16:19:20-04:00" + }, + { + "eventAction" : "registration", + "eventDate" : "1995-01-23T00:00:00-05:00" + } + ], + "handle" : "AMAZON-4", + "links" : [ + { + "href" : "https://rdap.arin.net/registry/entity/AMAZON-4", + "rel" : "self", + "type" : "application/rdap+json", + "value" : "https://rdap.arin.net/registry/autnum/16509" + }, + { + "href" : "https://whois.arin.net/rest/org/AMAZON-4", + "rel" : "alternate", + "type" : "application/xml", + "value" : "https://rdap.arin.net/registry/autnum/16509" + } + ], + "objectClassName" : "entity", + "port43" : "whois.arin.net", + "roles" : [ + "registrant" + ], + "vcardArray" : [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Amazon.com, Inc." + ], + [ + "adr", + { + "label" : "1918 8th Ave\nSEATTLE\nWA\n98101-1244\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "kind", + {}, + "text", + "org" + ] + ] + ] + }, + { + "events" : [ + { + "eventAction" : "last changed", + "eventDate" : "2022-01-14T12:21:19-05:00" + }, + { + "eventAction" : "registration", + "eventDate" : "1999-06-11T10:53:13-04:00" + } + ], + "handle" : "AC6-ORG-ARIN", + "links" : [ + { + "href" : "https://rdap.arin.net/registry/entity/AC6-ORG-ARIN", + "rel" : "self", + "type" : "application/rdap+json", + "value" : "https://rdap.arin.net/registry/autnum/16509" + }, + { + "href" : "https://whois.arin.net/rest/poc/AC6-ORG-ARIN", + "rel" : "alternate", + "type" : "application/xml", + "value" : "https://rdap.arin.net/registry/autnum/16509" + } + ], + "objectClassName" : "entity", + "port43" : "whois.arin.net", + "roles" : [ + "technical" + ], + "status" : [ + "validated" + ], + "vcardArray" : [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "adr", + { + "label" : "PO BOX 81226\nSeattle\nWA\n98108-1226\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "fn", + {}, + "text", + "Amazon-com Incorporated" + ], + [ + "org", + {}, + "text", + "Amazon-com Incorporated" + ], + [ + "kind", + {}, + "text", + "group" + ], + [ + "email", + {}, + "text", + "ipmanagement@amazon.com" + ], + [ + "tel", + { + "type" : [ + "work", + "voice" + ] + }, + "text", + "+1-206-266-2187" + ] + ] + ] + } + ], + "events" : [ + { + "eventAction" : "last changed", + "eventDate" : "2012-03-02T08:03:18-05:00" + }, + { + "eventAction" : "registration", + "eventDate" : "2000-05-04T00:00:00-04:00" + } + ], + "handle" : "AS16509", + "links" : [ + { + "href" : "https://rdap.arin.net/registry/autnum/16509", + "rel" : "self", + "type" : "application/rdap+json", + "value" : "https://rdap.arin.net/registry/autnum/16509" + }, + { + "href" : "https://whois.arin.net/rest/asn/AS16509", + "rel" : "alternate", + "type" : "application/xml", + "value" : "https://rdap.arin.net/registry/autnum/16509" + } + ], + "name" : "AMAZON-02", + "notices" : [ + { + "description" : [ + "By using the ARIN RDAP/Whois service, you are agreeing to the RDAP/Whois Terms of Use" + ], + "links" : [ + { + "href" : "https://www.arin.net/resources/registry/whois/tou/", + "rel" : "terms-of-service", + "type" : "text/html", + "value" : "https://rdap.arin.net/registry/autnum/16509" + } + ], + "title" : "Terms of Service" + }, + { + "description" : [ + "If you see inaccuracies in the results, please visit: " + ], + "links" : [ + { + "href" : "https://www.arin.net/resources/registry/whois/inaccuracy_reporting/", + "rel" : "inaccuracy-report", + "type" : "text/html", + "value" : "https://rdap.arin.net/registry/autnum/16509" + } + ], + "title" : "Whois Inaccuracy Reporting" + }, + { + "description" : [ + "Copyright 1997-2023, American Registry for Internet Numbers, Ltd." + ], + "title" : "Copyright Notice" + } + ], + "objectClassName" : "autnum", + "port43" : "whois.arin.net", + "rdapConformance" : [ + "nro_rdap_profile_0", + "rdap_level_0", + "nro_rdap_profile_asn_flat_0" + ], + "startAutnum" : 16509, + "status" : [ + "active" + ] +} diff --git a/icann-rdap-common/src/response/test_files/domain_afnic_fr.json b/icann-rdap-common/src/response/test_files/domain_afnic_fr.json new file mode 100644 index 0000000..a2ecb8d --- /dev/null +++ b/icann-rdap-common/src/response/test_files/domain_afnic_fr.json @@ -0,0 +1,1068 @@ +{ + "entities" : [ + { + "events" : [ + { + "eventAction" : "registration", + "eventDate" : "2001-10-02T00:00:00Z" + } + ], + "handle" : "JP-FRNIC", + "links" : [ + { + "href" : "https://rdap.nic.fr/entity/JP-FRNIC", + "rel" : "self", + "value" : "https://rdap.nic.fr/entity/JP-FRNIC" + } + ], + "objectClassName" : "entity", + "port43" : "whois.nic.fr", + "remarks" : [ + { + "description" : [ + "NO" + ], + "type" : "contact restricted publication" + }, + { + "description" : [ + "NO" + ], + "type" : "contact obsolete" + }, + { + "description" : [ + "PERSON" + ], + "type" : "contact type" + }, + { + "description" : [ + "NO" + ], + "type" : "reachable" + }, + { + "description" : [ + "NotIdentified" + ], + "type" : "eligibility status" + }, + { + "description" : [ + "Registry Operations" + ], + "type" : "registrar name" + } + ], + "roles" : [ + "technical" + ], + "status" : [ + "associated", + "active" + ], + "vcardArray" : [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Jean-Philippe Pick" + ], + [ + "org", + {}, + "text", + "AFNIC" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + [ + "immeuble le Stephenson", + "1, rue Stephenson", + "Hall A2 - 3eme etage" + ], + "Montigny-Le-Bretonneux", + "", + "78180", + "FR" + ] + ], + [ + "email", + {}, + "text", + "nic@nic.fr" + ], + [ + "tel", + {}, + "text", + "+33.139308300" + ] + ] + ] + }, + { + "events" : [ + { + "eventAction" : "registration", + "eventDate" : "2017-07-18T00:00:00Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "2018-02-22T08:22:26Z" + } + ], + "handle" : "AFNI30-FRNIC", + "links" : [ + { + "href" : "https://rdap.nic.fr/entity/AFNI30-FRNIC", + "rel" : "self", + "value" : "https://rdap.nic.fr/entity/AFNI30-FRNIC" + } + ], + "objectClassName" : "entity", + "port43" : "whois.nic.fr", + "remarks" : [ + { + "description" : [ + "NO" + ], + "type" : "contact restricted publication" + }, + { + "description" : [ + "NO" + ], + "type" : "contact obsolete" + }, + { + "description" : [ + "ORGANIZATION" + ], + "type" : "contact type" + }, + { + "description" : [ + "NO" + ], + "type" : "reachable" + }, + { + "description" : [ + "NotIdentified" + ], + "type" : "eligibility status" + }, + { + "description" : [ + "Registry Operations" + ], + "type" : "registrar name" + } + ], + "roles" : [ + "registrant" + ], + "status" : [ + "associated", + "active" + ], + "vcardArray" : [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "ASS FRANC NOMMAGE INTERNET EN COOP" + ], + [ + "org", + {}, + "text", + "" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + "Internet en Cooperation2, rue StephensonMontigny le Bretonneux", + "Saint Quentin en Yveline", + "", + "78181", + "FR" + ] + ], + [ + "email", + {}, + "text", + "contact@nic.fr" + ], + [ + "tel", + {}, + "text", + "+33.139308300" + ], + [ + "tel", + { + "type" : "fax" + }, + "text", + "+33.139308301" + ] + ] + ] + }, + { + "events" : [ + { + "eventAction" : "registration", + "eventDate" : "2000-10-24T00:00:00Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "2022-09-28T11:20:41Z" + } + ], + "handle" : "DC7-FRNIC", + "links" : [ + { + "href" : "https://rdap.nic.fr/entity/DC7-FRNIC", + "rel" : "self", + "value" : "https://rdap.nic.fr/entity/DC7-FRNIC" + } + ], + "objectClassName" : "entity", + "port43" : "whois.nic.fr", + "remarks" : [ + { + "description" : [ + "NO" + ], + "type" : "contact restricted publication" + }, + { + "description" : [ + "NO" + ], + "type" : "contact obsolete" + }, + { + "description" : [ + "PERSON" + ], + "type" : "contact type" + }, + { + "description" : [ + "NO" + ], + "type" : "reachable" + }, + { + "description" : [ + "NotIdentified" + ], + "type" : "eligibility status" + }, + { + "description" : [ + "Registry Operations" + ], + "type" : "registrar name" + } + ], + "roles" : [ + "administrative" + ], + "status" : [ + "associated", + "active" + ], + "vcardArray" : [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "David Chansard" + ], + [ + "org", + {}, + "text", + "AFNIC" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + [ + "immeuble le Stephenson", + "1, rue Stephenson", + "Hall A2 - 3eme etage" + ], + "Montigny-Le-Bretonneux", + "", + "78180", + "FR" + ] + ], + [ + "email", + {}, + "text", + "david.chansard@nic.fr" + ], + [ + "tel", + {}, + "text", + "+33.139308353" + ] + ] + ] + }, + { + "events" : [ + { + "eventAction" : "registration", + "eventDate" : "2014-07-29T15:35:33Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "2022-10-12T18:54:01.785057Z" + } + ], + "handle" : "RAR939-FRNIC", + "links" : [ + { + "href" : "https://rdap.nic.fr/entity/RAR939-FRNIC", + "rel" : "self", + "value" : "https://rdap.nic.fr/entity/RAR939-FRNIC" + } + ], + "objectClassName" : "entity", + "port43" : "whois.nic.fr", + "publicIds" : [ + { + "identifier" : "9999", + "type" : "IANA Registrar ID" + } + ], + "remarks" : [ + { + "description" : [ + "No" + ], + "type" : "registrar restricted publication" + } + ], + "roles" : [ + "registrar", + "sponsor" + ], + "status" : [ + "active" + ], + "vcardArray" : [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Registry Operations" + ], + [ + "tel", + {}, + "text", + "+33.139308300" + ], + [ + "tel", + { + "type" : "fax" + }, + "text", + "+33.139308301" + ], + [ + "email", + {}, + "text", + "support@afnic.fr" + ], + [ + "url", + {}, + "uri", + "https://www.afnic.fr" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + [ + "AFNIC", + "immeuble le Stephenson", + "1, rue Stephenson" + ], + "Montigny-Le-Bretonneux", + "", + "78180", + "FR" + ] + ] + ] + ] + }, + { + "events" : [ + { + "eventAction" : "registration", + "eventDate" : "2001-04-09T00:00:00Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "2019-01-28T16:11:55Z" + } + ], + "handle" : "VL-FRNIC", + "links" : [ + { + "href" : "https://rdap.nic.fr/entity/VL-FRNIC", + "rel" : "self", + "value" : "https://rdap.nic.fr/entity/VL-FRNIC" + } + ], + "objectClassName" : "entity", + "port43" : "whois.nic.fr", + "remarks" : [ + { + "description" : [ + "NO" + ], + "type" : "contact restricted publication" + }, + { + "description" : [ + "NO" + ], + "type" : "contact obsolete" + }, + { + "description" : [ + "PERSON" + ], + "type" : "contact type" + }, + { + "description" : [ + "NO" + ], + "type" : "reachable" + }, + { + "description" : [ + "NotIdentified" + ], + "type" : "eligibility status" + }, + { + "description" : [ + "Registry Operations" + ], + "type" : "registrar name" + } + ], + "roles" : [ + "technical" + ], + "status" : [ + "associated", + "active" + ], + "vcardArray" : [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Vincent Levigneron" + ], + [ + "org", + {}, + "text", + "AFNIC" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + [ + "Immeuble Le Stephenson", + "1, rue Stephenson" + ], + "Montigny-le-Bretonneux", + "", + "78180", + "FR" + ] + ], + [ + "email", + {}, + "text", + "vincent.levigneron@afnic.fr" + ], + [ + "tel", + {}, + "text", + "+33.139308300" + ], + [ + "tel", + { + "type" : "fax" + }, + "text", + "+33.139308301" + ] + ] + ] + } + ], + "events" : [ + { + "eventAction" : "registration", + "eventDate" : "2001-12-10T23:00:00Z" + }, + { + "eventAction" : "expiration", + "eventDate" : "2029-07-18T08:26:59Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "2022-09-28T11:21:02Z" + }, + { + "eventAction" : "transfer", + "eventDate" : "2017-07-18T08:26:59Z" + } + ], + "handle" : "DOM000000181261-FRNIC", + "ldhName" : "afnic.fr", + "links" : [ + { + "href" : "https://rdap.nic.fr/domain/afnic.fr", + "rel" : "self", + "value" : "https://rdap.nic.fr/domain/afnic.fr" + } + ], + "nameservers" : [ + { + "entities" : [ + { + "events" : [ + { + "eventAction" : "registration", + "eventDate" : "2014-07-29T15:35:33Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "2022-10-12T18:54:01.785057Z" + } + ], + "handle" : "RAR939-FRNIC", + "links" : [ + { + "href" : "https://rdap.nic.fr/entity/RAR939-FRNIC", + "rel" : "self", + "value" : "https://rdap.nic.fr/entity/RAR939-FRNIC" + } + ], + "objectClassName" : "entity", + "port43" : "whois.nic.fr", + "publicIds" : [ + { + "identifier" : "9999", + "type" : "IANA Registrar ID" + } + ], + "remarks" : [ + { + "description" : [ + "No" + ], + "type" : "registrar restricted publication" + } + ], + "roles" : [ + "registrar", + "sponsor" + ], + "status" : [ + "active" + ], + "vcardArray" : [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Registry Operations" + ], + [ + "tel", + {}, + "text", + "+33.139308300" + ], + [ + "tel", + { + "type" : "fax" + }, + "text", + "+33.139308301" + ], + [ + "email", + {}, + "text", + "support@afnic.fr" + ], + [ + "url", + {}, + "uri", + "https://www.afnic.fr" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + [ + "AFNIC", + "immeuble le Stephenson", + "1, rue Stephenson" + ], + "Montigny-Le-Bretonneux", + "", + "78180", + "FR" + ] + ] + ] + ] + } + ], + "handle" : "HOST06-FRNIC", + "ipAddresses" : { + "v4" : [ + "192.93.0.4" + ], + "v6" : [ + "2001:660:3005:1::1:2" + ] + }, + "ldhName" : "ns2.nic.fr", + "links" : [ + { + "href" : "https://rdap.nic.fr/nameserver/ns2.nic.fr", + "rel" : "self", + "value" : "https://rdap.nic.fr/nameserver/ns2.nic.fr" + } + ], + "objectClassName" : "nameserver", + "port43" : "whois.nic.fr", + "remarks" : [ + { + "description" : [ + "The list of results does not contain all results due to lack of authorization.", + "This may indicate to some clients that proper authorization will yield a longer result set." + ], + "title" : "result set truncated due to authorization" + } + ], + "status" : [ + "server update prohibited", + "server delete prohibited", + "associated" + ] + }, + { + "entities" : [ + { + "events" : [ + { + "eventAction" : "registration", + "eventDate" : "2014-07-29T15:35:33Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "2022-10-12T18:54:01.785057Z" + } + ], + "handle" : "RAR939-FRNIC", + "links" : [ + { + "href" : "https://rdap.nic.fr/entity/RAR939-FRNIC", + "rel" : "self", + "value" : "https://rdap.nic.fr/entity/RAR939-FRNIC" + } + ], + "objectClassName" : "entity", + "port43" : "whois.nic.fr", + "publicIds" : [ + { + "identifier" : "9999", + "type" : "IANA Registrar ID" + } + ], + "remarks" : [ + { + "description" : [ + "No" + ], + "type" : "registrar restricted publication" + } + ], + "roles" : [ + "registrar", + "sponsor" + ], + "status" : [ + "active" + ], + "vcardArray" : [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Registry Operations" + ], + [ + "tel", + {}, + "text", + "+33.139308300" + ], + [ + "tel", + { + "type" : "fax" + }, + "text", + "+33.139308301" + ], + [ + "email", + {}, + "text", + "support@afnic.fr" + ], + [ + "url", + {}, + "uri", + "https://www.afnic.fr" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + [ + "AFNIC", + "immeuble le Stephenson", + "1, rue Stephenson" + ], + "Montigny-Le-Bretonneux", + "", + "78180", + "FR" + ] + ] + ] + ] + } + ], + "handle" : "HOST05-FRNIC", + "ipAddresses" : { + "v4" : [ + "192.134.4.1" + ], + "v6" : [ + "2001:67c:2218:2::4:1" + ] + }, + "ldhName" : "ns1.nic.fr", + "links" : [ + { + "href" : "https://rdap.nic.fr/nameserver/ns1.nic.fr", + "rel" : "self", + "value" : "https://rdap.nic.fr/nameserver/ns1.nic.fr" + } + ], + "objectClassName" : "nameserver", + "port43" : "whois.nic.fr", + "remarks" : [ + { + "description" : [ + "The list of results does not contain all results due to lack of authorization.", + "This may indicate to some clients that proper authorization will yield a longer result set." + ], + "title" : "result set truncated due to authorization" + } + ], + "status" : [ + "server update prohibited", + "server delete prohibited", + "associated" + ] + }, + { + "entities" : [ + { + "events" : [ + { + "eventAction" : "registration", + "eventDate" : "2014-07-29T15:35:33Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "2022-10-12T18:54:01.785057Z" + } + ], + "handle" : "RAR939-FRNIC", + "links" : [ + { + "href" : "https://rdap.nic.fr/entity/RAR939-FRNIC", + "rel" : "self", + "value" : "https://rdap.nic.fr/entity/RAR939-FRNIC" + } + ], + "objectClassName" : "entity", + "port43" : "whois.nic.fr", + "publicIds" : [ + { + "identifier" : "9999", + "type" : "IANA Registrar ID" + } + ], + "remarks" : [ + { + "description" : [ + "No" + ], + "type" : "registrar restricted publication" + } + ], + "roles" : [ + "registrar", + "sponsor" + ], + "status" : [ + "active" + ], + "vcardArray" : [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Registry Operations" + ], + [ + "tel", + {}, + "text", + "+33.139308300" + ], + [ + "tel", + { + "type" : "fax" + }, + "text", + "+33.139308301" + ], + [ + "email", + {}, + "text", + "support@afnic.fr" + ], + [ + "url", + {}, + "uri", + "https://www.afnic.fr" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + [ + "AFNIC", + "immeuble le Stephenson", + "1, rue Stephenson" + ], + "Montigny-Le-Bretonneux", + "", + "78180", + "FR" + ] + ] + ] + ] + } + ], + "handle" : "HOST07-FRNIC", + "ipAddresses" : { + "v4" : [ + "192.134.0.49" + ], + "v6" : [ + "2001:660:3006:1::1:1" + ] + }, + "ldhName" : "ns3.nic.fr", + "links" : [ + { + "href" : "https://rdap.nic.fr/nameserver/ns3.nic.fr", + "rel" : "self", + "value" : "https://rdap.nic.fr/nameserver/ns3.nic.fr" + } + ], + "objectClassName" : "nameserver", + "port43" : "whois.nic.fr", + "remarks" : [ + { + "description" : [ + "The list of results does not contain all results due to lack of authorization.", + "This may indicate to some clients that proper authorization will yield a longer result set." + ], + "title" : "result set truncated due to authorization" + } + ], + "status" : [ + "server update prohibited", + "server delete prohibited", + "associated" + ] + } + ], + "objectClassName" : "domain", + "port43" : "whois.nic.fr", + "rdapConformance" : [ + "rdap_level_0", + "icann_rdap_technical_implementation_guide_0", + "icann_rdap_response_profile_0" + ], + "secureDns" : { + "delegationSigned" : true, + "dsData" : [ + { + "algorithm" : 13, + "digest" : "EF2DC0C8CF1FDF2994E8C771E0F871949B83AF41A1BF594B484677F5A85A657B", + "digestType" : 2, + "keyTag" : 53080 + } + ] + }, + "status" : [ + "active" + ] +} diff --git a/icann-rdap-common/src/response/test_files/domain_search_with_redaction.json b/icann-rdap-common/src/response/test_files/domain_search_with_redaction.json new file mode 100644 index 0000000..345fec0 --- /dev/null +++ b/icann-rdap-common/src/response/test_files/domain_search_with_redaction.json @@ -0,0 +1,70 @@ +{ + "rdapConformance": [ + "rdap_level_0", + "redacted" + ], + "domainSearchResults":[ + { + "objectClassName": "domain", + "ldhName": "example1.com", + "links":[ + { + "value":"https://example.com/rdap/domain/example1.com", + "rel":"self", + "href":"https://example.com/rdap/domain/example1.com", + "type":"application/rdap+json" + }, + { + "value":"https://example.com/rdap/domain/example1.com", + "rel":"related", + "href":"https://example.com/rdap/domain/example1.com", + "type":"application/rdap+json" + } + ], + "redacted": [ + { + "name": { + "type": "Registry Domain ID" + }, + "prePath": "$.domainSearchResults[0].handle", + "pathLang": "jsonpath", + "method": "removal", + "reason": { + "type": "Server policy" + } + } + ] + }, + { + "objectClassName": "domain", + "ldhName": "example2.com", + "links":[ + { + "value":"https://example.com/rdap/domain/example2.com", + "rel":"self", + "href":"https://example.com/rdap/domain/example2.com", + "type":"application/rdap+json" + }, + { + "value":"https://example.com/rdap/domain/example2.com", + "rel":"related", + "href":"https://example.com/rdap/domain/example2.com", + "type":"application/rdap+json" + } + ], + "redacted": [ + { + "name": { + "description": "Registry Domain ID" + }, + "prePath": "$.domainSearchResults[1].handle", + "pathLang": "jsonpath", + "method": "removal", + "reason": { + "description": "Server policy" + } + } + ] + } + ] +} diff --git a/icann-rdap-common/src/response/test_files/domains_ldhname_ns1_arin_net.json b/icann-rdap-common/src/response/test_files/domains_ldhname_ns1_arin_net.json new file mode 100644 index 0000000..8b9be46 --- /dev/null +++ b/icann-rdap-common/src/response/test_files/domains_ldhname_ns1_arin_net.json @@ -0,0 +1,8534 @@ +{ + "rdapConformance" : [ "nro_rdap_profile_0", "rdap_level_0" ], + "notices" : [ { + "title" : "Terms of Service", + "description" : [ "By using the ARIN RDAP/Whois service, you are agreeing to the RDAP/Whois Terms of Use" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "terms-of-service", + "type" : "text/html", + "href" : "https://www.arin.net/resources/registry/whois/tou/" + } ] + }, { + "title" : "Whois Inaccuracy Reporting", + "description" : [ "If you see inaccuracies in the results, please visit: " ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "inaccuracy-report", + "type" : "text/html", + "href" : "https://www.arin.net/resources/registry/whois/inaccuracy_reporting/" + } ] + }, { + "title" : "Copyright Notice", + "description" : [ "Copyright 1997-2023, American Registry for Internet Numbers, Ltd." ] + } ], + "domainSearchResults" : [ { + "handle" : "252.149.192.in-addr.arpa.", + "ldhName" : "252.149.192.in-addr.arpa.", + "nameservers" : [ { + "ldhName" : "NS4.APNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "ARIN.AUTHDNS.RIPE.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.LACNIC.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 48616, + "algorithm" : 8, + "digestType" : 2, + "digest" : "aac284d67ad1f149f167631e962a2fc4e99e82b059e2eb127165cf33da8f1b0e" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-08-01T16:22:27-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/252.149.192.in-addr.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/252.149.192.in-addr.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET-192-149-252-0-1", + "startAddress" : "192.149.252.0", + "endAddress" : "192.149.252.255", + "ipVersion" : "v4", + "name" : "ARIN-CHA-1", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET-192-0-0-0-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-14T20:28:45-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "1997-11-05T00:00:00-05:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/192.149.252.0" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET-192-149-252-0-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v4prefix" : "192.149.252.0", + "length" : 24 + } ], + "arin_originas0_originautnums" : [ 10745 ] + }, + "objectClassName" : "domain" + }, { + "handle" : "0.212.199.in-addr.arpa.", + "ldhName" : "0.212.199.in-addr.arpa.", + "nameservers" : [ { + "ldhName" : "ARIN.AUTHDNS.RIPE.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS4.APNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.LACNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 8830, + "algorithm" : 8, + "digestType" : 2, + "digest" : "1417bc26e95cdb3e873c14f3ae4feac8febfa046ffff81dec0982ea32a7d15ea" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-28T14:25:32-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/0.212.199.in-addr.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/0.212.199.in-addr.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET-199-212-0-0-1", + "startAddress" : "199.212.0.0", + "endAddress" : "199.212.0.255", + "ipVersion" : "v4", + "name" : "ARIN-PFS-IAD", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET-199-0-0-0-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-14T20:28:44-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2008-06-10T14:16:57-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/199.212.0.0" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET-199-212-0-0-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v4prefix" : "199.212.0.0", + "length" : 24 + } ], + "arin_originas0_originautnums" : [ 393225 ] + }, + "objectClassName" : "domain" + }, { + "handle" : "0.43.199.in-addr.arpa.", + "ldhName" : "0.43.199.in-addr.arpa.", + "nameservers" : [ { + "ldhName" : "NS4.APNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "ARIN.AUTHDNS.RIPE.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.LACNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 4600, + "algorithm" : 8, + "digestType" : 2, + "digest" : "ca936224a8cb81078f499ef6edfecde6418981ef06d76843c0fbf3ecf58da30b" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-28T14:25:32-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/0.43.199.in-addr.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/0.43.199.in-addr.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET-199-43-0-0-1", + "startAddress" : "199.43.0.0", + "endAddress" : "199.43.0.255", + "ipVersion" : "v4", + "name" : "ARIN-ASH", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET-199-0-0-0-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-14T20:28:45-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2005-11-09T09:20:43-05:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/199.43.0.0" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET-199-43-0-0-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v4prefix" : "199.43.0.0", + "length" : 24 + } ], + "arin_originas0_originautnums" : [ 10745 ] + }, + "objectClassName" : "domain" + }, { + "handle" : "0.3.0.0.0.0.5.0.1.0.0.2.ip6.arpa.", + "ldhName" : "0.3.0.0.0.0.5.0.1.0.0.2.ip6.arpa.", + "nameservers" : [ { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "ARIN.AUTHDNS.RIPE.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS4.APNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.LACNIC.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 29219, + "algorithm" : 8, + "digestType" : 2, + "digest" : "1b4ecb813ec04b57cd51478fd6b7c66f8a41f9cb2f5c2b9256f249e6866c3a9e" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-28T14:25:30-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/0.3.0.0.0.0.5.0.1.0.0.2.ip6.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/0.3.0.0.0.0.5.0.1.0.0.2.ip6.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET6-2001-500-30-1", + "startAddress" : "2001:500:30::", + "endAddress" : "2001:500:30:ffff:ffff:ffff:ffff:ffff", + "ipVersion" : "v6", + "name" : "ARIN-MEETING-NET", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET6-2001-400-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-14T20:28:37-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2008-03-05T08:26:20-05:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/2001:500:30::" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET6-2001-500-30-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v6prefix" : "2001:500:30::", + "length" : 48 + } ], + "arin_originas0_originautnums" : [ ] + }, + "objectClassName" : "domain" + }, { + "handle" : "1.3.0.0.0.0.5.0.1.0.0.2.ip6.arpa.", + "ldhName" : "1.3.0.0.0.0.5.0.1.0.0.2.ip6.arpa.", + "nameservers" : [ { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS4.APNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.LACNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "ARIN.AUTHDNS.RIPE.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 49090, + "algorithm" : 8, + "digestType" : 2, + "digest" : "9e650963d4c10df13cf398fff48b3720fa6c579831b32779135cf0441543e666" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-28T14:25:34-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/1.3.0.0.0.0.5.0.1.0.0.2.ip6.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/1.3.0.0.0.0.5.0.1.0.0.2.ip6.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET6-2001-500-31-1", + "startAddress" : "2001:500:31::", + "endAddress" : "2001:500:31:ffff:ffff:ffff:ffff:ffff", + "ipVersion" : "v6", + "name" : "ARIN-PFS-SJC-1", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET6-2001-400-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-14T20:28:38-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2008-10-21T12:38:03-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/2001:500:31::" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET6-2001-500-31-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v6prefix" : "2001:500:31::", + "length" : 48 + } ], + "arin_originas0_originautnums" : [ 393220 ] + }, + "objectClassName" : "domain" + }, { + "handle" : "0.71.199.in-addr.arpa.", + "ldhName" : "0.71.199.in-addr.arpa.", + "nameservers" : [ { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "ARIN.AUTHDNS.RIPE.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.LACNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS4.APNIC.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 53506, + "algorithm" : 8, + "digestType" : 2, + "digest" : "2e3f3de598a721e9f19fc99e50c553aa3da5ada598c9c594e341bdf7e6be3d5f" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-28T14:25:33-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/0.71.199.in-addr.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/0.71.199.in-addr.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET-199-71-0-0-1", + "startAddress" : "199.71.0.0", + "endAddress" : "199.71.0.255", + "ipVersion" : "v4", + "name" : "ARIN-PFS-SJC", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET-199-0-0-0-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-14T20:28:42-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2008-10-21T12:30:39-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/199.71.0.0" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET-199-71-0-0-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v4prefix" : "199.71.0.0", + "length" : 24 + } ], + "arin_originas0_originautnums" : [ 393220 ] + }, + "objectClassName" : "domain" + }, { + "handle" : "136.136.192.in-addr.arpa.", + "ldhName" : "136.136.192.in-addr.arpa.", + "nameservers" : [ { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "ARIN.AUTHDNS.RIPE.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.LACNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS4.APNIC.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 53191, + "algorithm" : 8, + "digestType" : 2, + "digest" : "8a357ec31d8017dfaec0187d2fa8a92a79b5683d29780625f50685a65ab799d1" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-28T14:25:34-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/136.136.192.in-addr.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/136.136.192.in-addr.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET-192-136-136-0-1", + "startAddress" : "192.136.136.0", + "endAddress" : "192.136.136.255", + "ipVersion" : "v4", + "name" : "ARIN-CHA-1", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET-192-0-0-0-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-14T20:28:26-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2002-04-17T00:00:00-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/192.136.136.0" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET-192-136-136-0-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v4prefix" : "192.136.136.0", + "length" : 24 + } ], + "arin_originas0_originautnums" : [ 10745 ] + }, + "objectClassName" : "domain" + }, { + "handle" : "193.38.65.in-addr.arpa.", + "ldhName" : "193.38.65.in-addr.arpa.", + "nameservers" : [ { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2011-03-24T10:15:05-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/193.38.65.in-addr.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/193.38.65.in-addr.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET-65-38-192-0-1", + "startAddress" : "65.38.192.0", + "endAddress" : "65.38.223.255", + "ipVersion" : "v4", + "name" : "CENT-IP", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET-65-0-0-0-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2017-03-01T14:25:22-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2002-06-10T00:00:00-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/65.38.192.0" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET-65-38-192-0-1" + } ], + "entities" : [ { + "handle" : "AMPR", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "AT&T Mobility Puerto Rico" ], [ "adr", { + "label" : "The Prentice Hall Corp\nP.O. Box 192830\nSan Juan\nPR\n00902-2946\nPuerto Rico" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AMPR" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/AMPR" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2017-04-25T16:51:38-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2014-01-30T16:26:54-05:00" + } ], + "entities" : [ { + "handle" : "DVE19-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1000 San Roberto Street\nRio Piedras\nSan Juan\n00926\nPuerto Rico" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Vega" ], [ "n", { }, "text", [ "Vega", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david.vega1@libertypr.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-787-717-9600" ], [ "tel", { + "type" : [ "work", "fax" ] + }, "text", "+1-787-620-8849" ] ] ], + "roles" : [ "administrative", "technical", "abuse", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/DVE19-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/DVE19-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-01T13:06:36-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2006-02-22T15:46:16-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v4prefix" : "65.38.192.0", + "length" : 19 + } ], + "arin_originas0_originautnums" : [ 11992 ] + }, + "objectClassName" : "domain" + }, { + "handle" : "180.180.199.in-addr.arpa.", + "ldhName" : "180.180.199.in-addr.arpa.", + "nameservers" : [ { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "ARIN.AUTHDNS.RIPE.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.LACNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS4.APNIC.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 1729, + "algorithm" : 8, + "digestType" : 2, + "digest" : "d1ee7388fc8eba9d7d90b6fad52d23e4476f4fbc1c18d1bd80a0acb417c51ffe" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-28T14:25:34-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/180.180.199.in-addr.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/180.180.199.in-addr.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET-199-180-180-0-1", + "startAddress" : "199.180.180.0", + "endAddress" : "199.180.183.255", + "ipVersion" : "v4", + "name" : "ARIN-ANYCAST", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET-199-0-0-0-0", + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "contact peering@arin.net to arrange peering." ] + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-14T20:28:44-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2011-07-06T11:07:01-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/199.180.180.0" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET-199-180-180-0-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v4prefix" : "199.180.180.0", + "length" : 22 + } ], + "arin_originas0_originautnums" : [ 53535 ] + }, + "objectClassName" : "domain" + }, { + "handle" : "181.180.199.in-addr.arpa.", + "ldhName" : "181.180.199.in-addr.arpa.", + "nameservers" : [ { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS4.APNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "ARIN.AUTHDNS.RIPE.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.LACNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 10222, + "algorithm" : 8, + "digestType" : 2, + "digest" : "0917e9deddec02e4372720f8d4409238f52dc9c08dbaa641f4d862e2d3c21267" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-28T14:25:35-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/181.180.199.in-addr.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/181.180.199.in-addr.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET-199-180-180-0-1", + "startAddress" : "199.180.180.0", + "endAddress" : "199.180.183.255", + "ipVersion" : "v4", + "name" : "ARIN-ANYCAST", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET-199-0-0-0-0", + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "contact peering@arin.net to arrange peering." ] + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-14T20:28:44-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2011-07-06T11:07:01-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/199.180.180.0" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET-199-180-180-0-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v4prefix" : "199.180.180.0", + "length" : 22 + } ], + "arin_originas0_originautnums" : [ 53535 ] + }, + "objectClassName" : "domain" + }, { + "handle" : "182.180.199.in-addr.arpa.", + "ldhName" : "182.180.199.in-addr.arpa.", + "nameservers" : [ { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "ARIN.AUTHDNS.RIPE.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.LACNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS4.APNIC.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 21094, + "algorithm" : 8, + "digestType" : 2, + "digest" : "27770d57a57f2b895f13964cb79ecfb172f8ce9686214562ca3def51de959f5f" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-28T14:25:35-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/182.180.199.in-addr.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/182.180.199.in-addr.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET-199-180-180-0-1", + "startAddress" : "199.180.180.0", + "endAddress" : "199.180.183.255", + "ipVersion" : "v4", + "name" : "ARIN-ANYCAST", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET-199-0-0-0-0", + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "contact peering@arin.net to arrange peering." ] + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-14T20:28:44-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2011-07-06T11:07:01-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/199.180.180.0" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET-199-180-180-0-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v4prefix" : "199.180.180.0", + "length" : 22 + } ], + "arin_originas0_originautnums" : [ 53535 ] + }, + "objectClassName" : "domain" + }, { + "handle" : "183.180.199.in-addr.arpa.", + "ldhName" : "183.180.199.in-addr.arpa.", + "nameservers" : [ { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS4.APNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.LACNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "ARIN.AUTHDNS.RIPE.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 45409, + "algorithm" : 8, + "digestType" : 2, + "digest" : "5fc0236c0963a3f7f5d08b5a050b4ee0f7ac3c9fdee57f5ef918daf13f435baf" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-28T14:25:36-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/183.180.199.in-addr.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/183.180.199.in-addr.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET-199-180-180-0-1", + "startAddress" : "199.180.180.0", + "endAddress" : "199.180.183.255", + "ipVersion" : "v4", + "name" : "ARIN-ANYCAST", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET-199-0-0-0-0", + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "contact peering@arin.net to arrange peering." ] + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-14T20:28:44-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2011-07-06T11:07:01-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/199.180.180.0" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET-199-180-180-0-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v4prefix" : "199.180.180.0", + "length" : 22 + } ], + "arin_originas0_originautnums" : [ 53535 ] + }, + "objectClassName" : "domain" + }, { + "handle" : "0.f.0.0.0.0.5.0.1.0.0.2.ip6.arpa.", + "ldhName" : "0.f.0.0.0.0.5.0.1.0.0.2.ip6.arpa.", + "nameservers" : [ { + "ldhName" : "NS4.APNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "ARIN.AUTHDNS.RIPE.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.LACNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 40249, + "algorithm" : 8, + "digestType" : 2, + "digest" : "53e371b3943eef93e716cf6bb2c2bee10db2a625e2d3e8010425f739509836e2" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-28T14:25:33-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/0.f.0.0.0.0.5.0.1.0.0.2.ip6.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/0.f.0.0.0.0.5.0.1.0.0.2.ip6.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET6-2001-500-F0-1", + "startAddress" : "2001:500:f0::", + "endAddress" : "2001:500:f0:ffff:ffff:ffff:ffff:ffff", + "ipVersion" : "v6", + "name" : "ARIN-R-ANYCAST-1", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET6-2001-400-0", + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "contact peering@arin.net to request peering" ] + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-14T20:28:26-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2011-07-06T11:10:22-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/2001:500:f0::" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET6-2001-500-F0-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v6prefix" : "2001:500:F0::", + "length" : 48 + } ], + "arin_originas0_originautnums" : [ 53535 ] + }, + "objectClassName" : "domain" + }, { + "handle" : "9.a.0.0.0.0.5.0.1.0.0.2.ip6.arpa.", + "ldhName" : "9.a.0.0.0.0.5.0.1.0.0.2.ip6.arpa.", + "nameservers" : [ { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS4.APNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "ARIN.AUTHDNS.RIPE.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.LACNIC.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 2872, + "algorithm" : 8, + "digestType" : 2, + "digest" : "78c0feed30445dc4a7921f85085ff7843cd28dbfd2638354e01f63e5ac6460ea" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-28T14:25:37-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/9.a.0.0.0.0.5.0.1.0.0.2.ip6.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/9.a.0.0.0.0.5.0.1.0.0.2.ip6.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET6-2001-500-A9-1", + "startAddress" : "2001:500:a9::", + "endAddress" : "2001:500:a9:ffff:ffff:ffff:ffff:ffff", + "ipVersion" : "v6", + "name" : "ARIN-PFS-SEA-1", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET6-2001-400-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-14T20:28:36-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2015-05-22T12:42:43-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/2001:500:a9::" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET6-2001-500-A9-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v6prefix" : "2001:500:A9::", + "length" : 48 + } ], + "arin_originas0_originautnums" : [ 394018 ] + }, + "objectClassName" : "domain" + }, { + "handle" : "26.5.199.in-addr.arpa.", + "ldhName" : "26.5.199.in-addr.arpa.", + "nameservers" : [ { + "ldhName" : "NS3.LACNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "ARIN.AUTHDNS.RIPE.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS4.APNIC.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 34519, + "algorithm" : 8, + "digestType" : 2, + "digest" : "07ab41572d6230cc5db71ecd6f3a819ecdc55b3e8c8ab36ef02b5cf9bd12236b" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-28T14:25:36-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/26.5.199.in-addr.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/26.5.199.in-addr.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET-199-5-26-0-1", + "startAddress" : "199.5.26.0", + "endAddress" : "199.5.26.255", + "ipVersion" : "v4", + "name" : "ARIN-PFS-SEA", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET-199-0-0-0-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-14T20:28:39-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2015-05-22T12:38:44-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/199.5.26.0" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET-199-5-26-0-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v4prefix" : "199.5.26.0", + "length" : 24 + } ], + "arin_originas0_originautnums" : [ 394018 ] + }, + "objectClassName" : "domain" + }, { + "handle" : "0.1.1.0.0.0.5.0.1.0.0.2.ip6.arpa.", + "ldhName" : "0.1.1.0.0.0.5.0.1.0.0.2.ip6.arpa.", + "nameservers" : [ { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS4.APNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.LACNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "ARIN.AUTHDNS.RIPE.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 58234, + "algorithm" : 8, + "digestType" : 2, + "digest" : "ba624f016f78cb16f9fcb2fe35418dd8a2c3b75c302c640b0c4421f735f07052" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-28T14:25:31-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/0.1.1.0.0.0.5.0.1.0.0.2.ip6.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/0.1.1.0.0.0.5.0.1.0.0.2.ip6.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET6-2001-500-110-1", + "startAddress" : "2001:500:110::", + "endAddress" : "2001:500:110:ffff:ffff:ffff:ffff:ffff", + "ipVersion" : "v6", + "name" : "ARIN-CHA-CHA", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET6-2001-400-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-14T20:28:33-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2016-05-10T10:36:01-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/2001:500:110::" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET6-2001-500-110-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v6prefix" : "2001:500:110::", + "length" : 48 + } ], + "arin_originas0_originautnums" : [ 10745 ] + }, + "objectClassName" : "domain" + }, { + "handle" : "0.0.0.e.7.3.0.0.0.2.6.2.ip6.arpa.", + "ldhName" : "0.0.0.e.7.3.0.0.0.2.6.2.ip6.arpa.", + "nameservers" : [ { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "ARIN.AUTHDNS.RIPE.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS4.APNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.LACNIC.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 60559, + "algorithm" : 8, + "digestType" : 2, + "digest" : "473e202ac0a9322034c5f9570caf59c727cf63e63c8dc486e1958cb0927418e9" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-28T14:25:31-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/0.0.0.e.7.3.0.0.0.2.6.2.ip6.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/0.0.0.e.7.3.0.0.0.2.6.2.ip6.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET6-2620-37-E000-1", + "startAddress" : "2620:37:e000::", + "endAddress" : "2620:37:e000:ffff:ffff:ffff:ffff:ffff", + "ipVersion" : "v6", + "name" : "ARIN-A-ANYCAST-1", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET6-2620-1", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-14T20:28:39-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2017-10-10T14:53:43-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/2620:37:e000::" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET6-2620-37-E000-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v6prefix" : "2620:37:E000::", + "length" : 48 + } ], + "arin_originas0_originautnums" : [ 53535 ] + }, + "objectClassName" : "domain" + }, { + "handle" : "216.187.199.in-addr.arpa.", + "ldhName" : "216.187.199.in-addr.arpa.", + "nameservers" : [ { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 39619, + "algorithm" : 8, + "digestType" : 2, + "digest" : "B8C4FCADEB63DA56491CAC06F3457E082347735438CF3D255CE1FE3235067129" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-19T14:21:05-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/216.187.199.in-addr.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/216.187.199.in-addr.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET-199-187-216-0-1", + "startAddress" : "199.187.216.0", + "endAddress" : "199.187.223.255", + "ipVersion" : "v4", + "name" : "ARINOPS", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET-199-0-0-0-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-08-01T08:07:18-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-07-14T19:49:24-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/199.187.216.0" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET-199-187-216-0-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v4prefix" : "199.187.216.0", + "length" : 21 + } ], + "arin_originas0_originautnums" : [ ] + }, + "objectClassName" : "domain" + }, { + "handle" : "217.187.199.in-addr.arpa.", + "ldhName" : "217.187.199.in-addr.arpa.", + "nameservers" : [ { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 19165, + "algorithm" : 8, + "digestType" : 2, + "digest" : "DE4C21A8973B48A2FB0E37E65CAE01BBBA28A029C3713A16F8B8A0245B428ADE" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-19T14:21:25-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/217.187.199.in-addr.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/217.187.199.in-addr.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET-199-187-216-0-1", + "startAddress" : "199.187.216.0", + "endAddress" : "199.187.223.255", + "ipVersion" : "v4", + "name" : "ARINOPS", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET-199-0-0-0-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-08-01T08:07:18-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-07-14T19:49:24-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/199.187.216.0" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET-199-187-216-0-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v4prefix" : "199.187.216.0", + "length" : 21 + } ], + "arin_originas0_originautnums" : [ ] + }, + "objectClassName" : "domain" + }, { + "handle" : "218.187.199.in-addr.arpa.", + "ldhName" : "218.187.199.in-addr.arpa.", + "nameservers" : [ { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 40639, + "algorithm" : 8, + "digestType" : 2, + "digest" : "F5F100A3F404CA3D2BD0420430F9063F7548906BC11FB11CE217C7ADD5AB7CDF" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-19T14:22:21-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/218.187.199.in-addr.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/218.187.199.in-addr.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET-199-187-216-0-1", + "startAddress" : "199.187.216.0", + "endAddress" : "199.187.223.255", + "ipVersion" : "v4", + "name" : "ARINOPS", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET-199-0-0-0-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-08-01T08:07:18-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-07-14T19:49:24-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/199.187.216.0" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET-199-187-216-0-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v4prefix" : "199.187.216.0", + "length" : 21 + } ], + "arin_originas0_originautnums" : [ ] + }, + "objectClassName" : "domain" + }, { + "handle" : "219.187.199.in-addr.arpa.", + "ldhName" : "219.187.199.in-addr.arpa.", + "nameservers" : [ { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 59644, + "algorithm" : 8, + "digestType" : 2, + "digest" : "8F0646CAAB7A61A05D57DD183EA808BD241331A935CE68BFBF1D86813675DC61" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-19T14:22:37-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/219.187.199.in-addr.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/219.187.199.in-addr.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET-199-187-216-0-1", + "startAddress" : "199.187.216.0", + "endAddress" : "199.187.223.255", + "ipVersion" : "v4", + "name" : "ARINOPS", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET-199-0-0-0-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-08-01T08:07:18-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-07-14T19:49:24-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/199.187.216.0" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET-199-187-216-0-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v4prefix" : "199.187.216.0", + "length" : 21 + } ], + "arin_originas0_originautnums" : [ ] + }, + "objectClassName" : "domain" + }, { + "handle" : "220.187.199.in-addr.arpa.", + "ldhName" : "220.187.199.in-addr.arpa.", + "nameservers" : [ { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 43489, + "algorithm" : 8, + "digestType" : 2, + "digest" : "250F89CC595F2E566E5F4935CB38433DD64CBA9EE3462A7AE3C88864EDEA743A" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-19T14:22:59-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/220.187.199.in-addr.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/220.187.199.in-addr.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET-199-187-216-0-1", + "startAddress" : "199.187.216.0", + "endAddress" : "199.187.223.255", + "ipVersion" : "v4", + "name" : "ARINOPS", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET-199-0-0-0-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-08-01T08:07:18-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-07-14T19:49:24-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/199.187.216.0" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET-199-187-216-0-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v4prefix" : "199.187.216.0", + "length" : 21 + } ], + "arin_originas0_originautnums" : [ ] + }, + "objectClassName" : "domain" + }, { + "handle" : "221.187.199.in-addr.arpa.", + "ldhName" : "221.187.199.in-addr.arpa.", + "nameservers" : [ { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 65104, + "algorithm" : 8, + "digestType" : 2, + "digest" : "9DC0DF049380352B207486D927E7C0D593AD0CAD00D126B57D1BB68F1C2313FF" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-19T14:23:14-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/221.187.199.in-addr.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/221.187.199.in-addr.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET-199-187-216-0-1", + "startAddress" : "199.187.216.0", + "endAddress" : "199.187.223.255", + "ipVersion" : "v4", + "name" : "ARINOPS", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET-199-0-0-0-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-08-01T08:07:18-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-07-14T19:49:24-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/199.187.216.0" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET-199-187-216-0-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v4prefix" : "199.187.216.0", + "length" : 21 + } ], + "arin_originas0_originautnums" : [ ] + }, + "objectClassName" : "domain" + }, { + "handle" : "222.187.199.in-addr.arpa.", + "ldhName" : "222.187.199.in-addr.arpa.", + "nameservers" : [ { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 48046, + "algorithm" : 8, + "digestType" : 2, + "digest" : "A5DBAE7F34D64AFFAA9FA40C9C8E246AF819178AD28A3C0337936AAC029B5FCB" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-20T14:42:44-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/222.187.199.in-addr.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/222.187.199.in-addr.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET-199-187-216-0-1", + "startAddress" : "199.187.216.0", + "endAddress" : "199.187.223.255", + "ipVersion" : "v4", + "name" : "ARINOPS", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET-199-0-0-0-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-08-01T08:07:18-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-07-14T19:49:24-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/199.187.216.0" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET-199-187-216-0-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v4prefix" : "199.187.216.0", + "length" : 21 + } ], + "arin_originas0_originautnums" : [ ] + }, + "objectClassName" : "domain" + }, { + "handle" : "223.187.199.in-addr.arpa.", + "ldhName" : "223.187.199.in-addr.arpa.", + "nameservers" : [ { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 18454, + "algorithm" : 8, + "digestType" : 2, + "digest" : "08AD65A34D9BE4B1ED90474F9133510A1E456671D933F9F9EF677ED510BB1F24" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-20T14:43:02-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/223.187.199.in-addr.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/223.187.199.in-addr.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET-199-187-216-0-1", + "startAddress" : "199.187.216.0", + "endAddress" : "199.187.223.255", + "ipVersion" : "v4", + "name" : "ARINOPS", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET-199-0-0-0-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-08-01T08:07:18-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-07-14T19:49:24-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/199.187.216.0" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET-199-187-216-0-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v4prefix" : "199.187.216.0", + "length" : 21 + } ], + "arin_originas0_originautnums" : [ ] + }, + "objectClassName" : "domain" + }, { + "handle" : "0.0.0.2.8.3.0.0.0.2.6.2.ip6.arpa.", + "ldhName" : "0.0.0.2.8.3.0.0.0.2.6.2.ip6.arpa.", + "nameservers" : [ { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "ARIN.AUTHDNS.RIPE.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS4.APNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.LACNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 51760, + "algorithm" : 8, + "digestType" : 2, + "digest" : "a2fed90969d466b866157e60fbfcc9a7821b304886261452745601bfca3414c6" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-28T14:25:30-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/0.0.0.2.8.3.0.0.0.2.6.2.ip6.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/0.0.0.2.8.3.0.0.0.2.6.2.ip6.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET6-2620-38-2000-1", + "startAddress" : "2620:38:2000::", + "endAddress" : "2620:38:2000:ffff:ffff:ffff:ffff:ffff", + "ipVersion" : "v6", + "name" : "ARIN-P-ANYCAST-1", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET6-2620-1", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-14T20:28:31-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2017-10-10T14:54:07-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/2620:38:2000::" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET6-2620-38-2000-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v6prefix" : "2620:38:2000::", + "length" : 48 + } ], + "arin_originas0_originautnums" : [ 53535 ] + }, + "objectClassName" : "domain" + }, { + "handle" : "249.253.199.in-addr.arpa.", + "ldhName" : "249.253.199.in-addr.arpa.", + "nameservers" : [ { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.LACNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS4.APNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "ARIN.AUTHDNS.RIPE.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 41824, + "algorithm" : 8, + "digestType" : 2, + "digest" : "359708948bd9e72371042f23372c6ffac257c0a234a9926e446e88e63a66ab15" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-28T14:25:30-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/249.253.199.in-addr.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/249.253.199.in-addr.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET-199-253-249-0-1", + "startAddress" : "199.253.249.0", + "endAddress" : "199.253.249.255", + "ipVersion" : "v4", + "name" : "ARIN-PFS-TOR", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET-199-0-0-0-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-14T20:28:25-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2010-06-03T14:09:57-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/199.253.249.0" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET-199-253-249-0-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v4prefix" : "199.253.249.0", + "length" : 24 + } ], + "arin_originas0_originautnums" : [ 53535 ] + }, + "objectClassName" : "domain" + }, { + "handle" : "3.1.0.0.0.0.5.0.1.0.0.2.ip6.arpa.", + "ldhName" : "3.1.0.0.0.0.5.0.1.0.0.2.ip6.arpa.", + "nameservers" : [ { + "ldhName" : "NS3.LACNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS4.APNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "ARIN.AUTHDNS.RIPE.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 61187, + "algorithm" : 8, + "digestType" : 2, + "digest" : "e61e0c063742bd699fb1cb607455b348cd4033eb44cde474c064b0b9d0c515c1" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-28T14:25:37-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/3.1.0.0.0.0.5.0.1.0.0.2.ip6.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/3.1.0.0.0.0.5.0.1.0.0.2.ip6.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET6-2001-500-13-1", + "startAddress" : "2001:500:13::", + "endAddress" : "2001:500:13:ffff:ffff:ffff:ffff:ffff", + "ipVersion" : "v6", + "name" : "ARIN-PFS-IAD-IAD", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET6-2001-400-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-14T20:28:34-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2008-06-10T14:17:37-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/2001:500:13::" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET6-2001-500-13-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v6prefix" : "2001:500:13::", + "length" : 48 + } ], + "arin_originas0_originautnums" : [ 393225 ] + }, + "objectClassName" : "domain" + }, { + "handle" : "4.0.0.0.0.0.5.0.1.0.0.2.ip6.arpa.", + "ldhName" : "4.0.0.0.0.0.5.0.1.0.0.2.ip6.arpa.", + "nameservers" : [ { + "ldhName" : "NS3.LACNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS4.APNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "ARIN.AUTHDNS.RIPE.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 30604, + "algorithm" : 8, + "digestType" : 2, + "digest" : "aea32139de8a74cff943c1c059188e379d3843b13358174351a7062c4332f41e" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-28T14:25:37-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/4.0.0.0.0.0.5.0.1.0.0.2.ip6.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/4.0.0.0.0.0.5.0.1.0.0.2.ip6.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET6-2001-500-4-1", + "startAddress" : "2001:500:4::", + "endAddress" : "2001:500:4:ffff:ffff:ffff:ffff:ffff", + "ipVersion" : "v6", + "name" : "ARIN-ASH-1", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET6-2001-400-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-14T20:28:27-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2006-03-13T12:37:00-05:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/2001:500:4::" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET6-2001-500-4-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v6prefix" : "2001:500:4::", + "length" : 48 + } ], + "arin_originas0_originautnums" : [ 10745 ] + }, + "objectClassName" : "domain" + }, { + "handle" : "8.9.0.0.0.0.5.0.1.0.0.2.ip6.arpa.", + "ldhName" : "8.9.0.0.0.0.5.0.1.0.0.2.ip6.arpa.", + "nameservers" : [ { + "ldhName" : "NS3.LACNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS1.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS4.APNIC.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS3.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "NS2.ARIN.NET.", + "objectClassName" : "nameserver" + }, { + "ldhName" : "ARIN.AUTHDNS.RIPE.NET.", + "objectClassName" : "nameserver" + } ], + "secureDNS" : { + "delegationSigned" : true, + "dsData" : [ { + "keyTag" : 8440, + "algorithm" : 8, + "digestType" : 2, + "digest" : "2b0841e8ee2c6e3f299829d04dc0cad5650f16073c787c66692db2d0328cb1ef" + } ] + }, + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-28T14:25:38-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/domain/8.9.0.0.0.0.5.0.1.0.0.2.ip6.arpa." + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/rdns/8.9.0.0.0.0.5.0.1.0.0.2.ip6.arpa." + } ], + "port43" : "whois.arin.net", + "network" : { + "handle" : "NET6-2001-500-98-1", + "startAddress" : "2001:500:98::", + "endAddress" : "2001:500:98:ffff:ffff:ffff:ffff:ffff", + "ipVersion" : "v6", + "name" : "ARIN-PFS-TOR-1", + "type" : "DIRECT ALLOCATION", + "parentHandle" : "NET6-2001-400-0", + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-14T20:28:22-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2010-06-04T09:12:53-04:00" + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/ip/2001:500:98::" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/net/NET6-2001-500-98-1" + } ], + "entities" : [ { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "roles" : [ "registrant" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "entities" : [ { + "handle" : "FORST20-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Reggie Forster" ], [ "n", { }, "text", [ "Forster", "Reggie", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "rforster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/FORST20-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/FORST20-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-16T10:55:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-16T16:11:43-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AOA4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Operations Abuse" ], [ "org", { }, "text", "ARIN Operations Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "abuse" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AOA4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AOA4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-15T10:40:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:40:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "KOSTE6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Mark Kosters" ], [ "n", { }, "text", [ "Kosters", "Mark", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "markk@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "administrative" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/KOSTE6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/KOSTE6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T17:00:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:38:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "TOSCA5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Pete Toscano" ], [ "n", { }, "text", [ "Toscano", "Pete", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "pete@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-942-9542" ] ] ], + "roles" : [ "technical" ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-06-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/TOSCA5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/TOSCA5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-14T17:38:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-15T15:04:27-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "JEFFE114-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "David Jeffers" ], [ "n", { }, "text", [ "Jeffers", "David", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "david@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "noc", "dns" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/JEFFE114-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/JEFFE114-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-27T13:03:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-16T16:41:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MJO282-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Michael J O'Neill" ], [ "n", { }, "text", [ "O'Neill", "Michael", "J", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "mjo@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "roles" : [ "technical", "noc" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MJO282-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/domains?nsLdhName=ns1.arin.net", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MJO282-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-26T11:35:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-19T14:33:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ], + "port43" : "whois.arin.net", + "status" : [ "active" ], + "objectClassName" : "ip network", + "cidr0_cidrs" : [ { + "v6prefix" : "2001:500:98::", + "length" : 48 + } ], + "arin_originas0_originautnums" : [ 53535 ] + }, + "objectClassName" : "domain" + } ] +} \ No newline at end of file diff --git a/icann-rdap-common/src/response/test_files/entities_fn_arin.json b/icann-rdap-common/src/response/test_files/entities_fn_arin.json new file mode 100644 index 0000000..6b0632a --- /dev/null +++ b/icann-rdap-common/src/response/test_files/entities_fn_arin.json @@ -0,0 +1,7906 @@ +{ + "rdapConformance" : [ "nro_rdap_profile_0", "rdap_level_0" ], + "notices" : [ { + "title" : "Search Policy", + "type" : "result set truncated due to authorization", + "description" : [ "Search results are limited on a per query basis to prevent data mining." ] + }, { + "title" : "Terms of Service", + "description" : [ "By using the ARIN RDAP/Whois service, you are agreeing to the RDAP/Whois Terms of Use" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "terms-of-service", + "type" : "text/html", + "href" : "https://www.arin.net/resources/registry/whois/tou/" + } ] + }, { + "title" : "Whois Inaccuracy Reporting", + "description" : [ "If you see inaccuracies in the results, please visit: " ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "inaccuracy-report", + "type" : "text/html", + "href" : "https://www.arin.net/resources/registry/whois/inaccuracy_reporting/" + } ] + }, { + "title" : "Copyright Notice", + "description" : [ "Copyright 1997-2023, American Registry for Internet Numbers, Ltd." ] + } ], + "entitySearchResults" : [ { + "handle" : "ARINL", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Routing Security" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINL" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINL" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-01-12T15:02:07-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2021-08-17T07:05:54-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "American Registry for Internet Numbers" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "For abuse issues please see URL:\r", "https://www.arin.net/reference/materials/abuse/ \r", "The Registration Services Help Desk is open \r", "from 7 a.m. to 7 p.m., U.S. Eastern time to assist you.\r", "Phone Number: (703) 227-0660; Fax Number: (703) 997-8844." ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-03-02T14:54:51-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "1997-12-22T00:00:00-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MLICNA", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN" ], [ "adr", { + "label" : "3635 Concorde Pkwy\nChantilly\nVA\n20151\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MLICNA" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/MLICNA" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2012-02-16T15:05:27-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2002-09-19T14:13:47-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINOPS", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARIN Operations" ], [ "adr", { + "label" : "PO Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINOPS" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINOPS" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-22T17:52:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-09-07T18:41:45-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC-11", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARINC, INC." ], [ "adr", { + "label" : "9800 AIRPORT BLVD\nSAN ANTONIO\nTX\n78216\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC-11" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINC-11" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-10-09T23:19:36-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-10-09T23:19:36-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC-12", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARINC, INC." ], [ "adr", { + "label" : "9800 AIRPORT BLVD\nSAN ANTONIO\nTX\n78216\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC-12" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINC-12" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-10-10T04:09:43-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-10-10T04:09:43-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC-2", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARINC, Inc." ], [ "adr", { + "label" : "2551 Riva Road\nAnnapolis\nMD\n21401\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC-2" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINC-2" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2020-07-20T10:42:24-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "1992-07-16T00:00:00-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINCI", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARINC, Inc." ], [ "adr", { + "label" : "2551 Riva Road\nAnnapolis\nMD\n21401\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINCI" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINCI" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2011-09-24T08:30:57-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "1988-09-13T00:00:00-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINCI-1", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARINC, Inc." ], [ "adr", { + "label" : "Western Division\n 4055 Hancock Street\nSan Diego\nCA\n92110\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINCI-1" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINCI-1" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2011-09-24T08:30:57-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "1990-06-18T00:00:00-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINSO-1", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "ARINSO Canada Inc" ], [ "adr", { + "label" : "215, St-Jacques (coin de St-Francois Xavier) 220\nMontreal\nQC\nH2Y 1M6\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "kind", { }, "text", "org" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINSO-1" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/org/ARINSO-1" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2011-09-24T08:30:57-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2002-05-13T00:00:00-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ABUSE5754-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "8401 NW 53 Terr\nDoral\nFL\n33166\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN ABUSE" ], [ "n", { }, "text", [ "ABUSE", "ARIN", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "ARIN-ABUSE@CITYOFDORAL.COM" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-305-593-6604" ], [ "tel", { + "type" : [ "work", "fax" ] + }, "text", "+1-305-593-6622" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2018-11-29" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ABUSE5754-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ABUSE5754-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2017-11-29T07:01:04-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2016-11-29T11:25:46-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAD42-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "6701 Democracy Blvd. #350\nBethesda\nMD\n20817\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "n", { }, "text", [ "Admin", "Arin", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "arinadmin@brigh.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-866-350-6400" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAD42-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAD42-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2010-08-02T15:01:33-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2009-03-27T14:42:04-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ADMIN6110-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "57 Exchange Street\r\nSuite 302\nPortland\nME\n04101\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "n", { }, "text", [ "Admin", "ARIN", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "arin-admin@dirigodev.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-207-347-7360" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ADMIN6110-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ADMIN6110-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2016-02-04T13:04:08-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2016-02-04T13:04:08-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ADMIN6400-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "222 W College Ave, Ste 7\nAppleton\nWI\n54911\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "n", { }, "text", [ "Admin", "ARIN", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "TCARINAdmin@thedacare.org" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-920-738-6375" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ADMIN6400-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ADMIN6400-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-18T11:29:17-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2017-02-27T15:50:36-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ADMIN7187-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1500 West 4th Avenue\nSpokane\nWA\n99201\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "n", { }, "text", [ "Admin", "Arin", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "arin.admin@globalcu.org" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-509-455-4800" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ADMIN7187-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ADMIN7187-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-10-04T13:07:51-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-08-01T16:42:27-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ADMIN8136-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "201 Main Street, Suite 2700\nFort Worth\nTX\n76102\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "n", { }, "text", [ "Admin", "Arin", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "arin@basscompanies.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-817-390-8400" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ADMIN8136-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ADMIN8136-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-05-23T15:37:18-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-05-23T15:37:18-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAD8-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "City Center Building , 1St Circle , JamalAmman\nP.O.Box 941477 Amman 11194 Jordan\nAmman\n\n\nJordan" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Administrator" ], [ "n", { }, "text", [ "Administrator", "Arin", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "ripeadmin@go.com.jo" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+ 962 6 460 8888" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2010-12-16" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAD8-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAD8-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2004-01-29T09:38:54-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2004-01-29T09:38:54-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ADMIN6921-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "10740 Parkridge Blvd, Suite 200\nReston\nVA\n20191\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "n", { }, "text", [ "Administrator", "ARIN", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "netops@lookingglasscyber.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-351-1000" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "https://www.lookingglasscyber.com/", "Standard hours are 9:00 AM to 5:00 PM EST " ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ADMIN6921-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ADMIN6921-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-18T09:53:56-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2018-11-07T15:22:55-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN13-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "12700 Park Central Drive\r\n#2100\nDallas\nTX\n75251\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin" ], [ "org", { }, "text", "Arin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@augur.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-972-763-7947" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2011-12-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN13-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN13-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2010-12-14T13:57:56-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2010-12-14T13:57:56-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN15-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "4228 Royal Ave.\nMontreal\nQC\nH4A 2M5\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN" ], [ "org", { }, "text", "ARIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@sondo.net" ], [ "email", { }, "text", "arin@jablan.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-416-907-6079" ], [ "tel", { + "type" : [ "work", "cell" ] + }, "text", "+1-514-240-3909" ], [ "tel", { + "type" : [ "work", "cell" ] + }, "text", "+1-514-497-1411" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN15-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN15-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-09-19T14:18:40-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-03-22T00:33:41-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN21-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "10910 State Route 108\nEllicott City\nMD\n21043\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN" ], [ "org", { }, "text", "ARIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@hcpss.org" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-410-313-1262" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN21-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN21-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2014-03-25T12:14:42-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2014-03-25T12:14:42-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN22-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "6302 Constitution Dr\nFort Wayne\nIN\n46804\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "arin" ], [ "org", { }, "text", "arin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@mieweb.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-260-459-6270;ext314" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN22-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN22-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-08-23T20:11:48-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2014-09-09T13:31:00-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN24-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "7374 Las Positas Rd\nLivermore\nCA\n94551\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN" ], [ "org", { }, "text", "ARIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "domain@ansys.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-925-449-2500" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN24-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN24-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-01-11T16:40:02-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2016-02-01T12:03:02-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN28-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "777 W Main St Suite 900\nBoise\nID\n83702\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN" ], [ "org", { }, "text", "ARIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@clearwateranalytics.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-208-433-1200" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-03-18" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN28-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN28-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-03-19T12:13:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2017-03-17T12:54:28-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN29-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "300 Brannan\nSt# 101\nSan Fancisco\nCA\n94107\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "arin" ], [ "org", { }, "text", "arin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ops@teridion.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-415-940-7040" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN29-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN29-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-03-21T05:23:01-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2017-03-31T11:54:47-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN3-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "5100 California Ave Suite 104\nBakersfield\nCA\n93309\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN" ], [ "org", { }, "text", "ARIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ARIN@arrival.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-661-716-6000" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2010-06-25" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN3-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN3-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2003-12-31T14:25:02-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2003-12-18T13:12:25-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN30-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1300 S Dekalb St\nShelby\nNC\n28152\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN" ], [ "org", { }, "text", "ARIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "diego@bosstv.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-828-829-0388" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN30-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN30-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-11-04T15:08:59-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2017-05-01T13:46:08-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN31-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1 W Illinois St\nSt Charles\nIL\n60174\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN" ], [ "org", { }, "text", "ARIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@alesolutions.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-630-513-6434" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "http://www.alesolutions.com", "Standard NOC hours are 8:00 AM to 5:00 PM CST" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN31-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN31-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-08-04T19:37:01-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2017-08-03T16:41:01-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN32-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "200 S. Center\nMesa\nAZ\n85210\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN" ], [ "org", { }, "text", "ARIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@mesaaz.gov" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-480-644-2011" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN32-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN32-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-11-02T17:54:33-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2018-01-11T14:24:00-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN36-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "416 S Bell Ave\nAmes\nIA\n50010\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN" ], [ "org", { }, "text", "ARIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@regi.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-515-239-8000" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "www.regi.com" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN36-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN36-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-11T11:47:21-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-05-31T10:31:17-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN37-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "Anton Blvd\nCosta Mesa\nCA\n92656\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN" ], [ "org", { }, "text", "ARIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@tickets.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-714-327-5445" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN37-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN37-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-19T11:15:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-07-11T16:56:39-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN39-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "234 33rd St. Dr. SE\nCedar Rapids\nIA\n52403\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN" ], [ "org", { }, "text", "ARIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "admin@rfcablesexpress.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-800-388-3691" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN39-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN39-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-09-15T14:15:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-08-20T12:17:10-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "2533 N Carson St L241\nCarson City\nNV\n89706\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN" ], [ "org", { }, "text", "ARIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@livewireservicesinc.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-800-454-4565" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2018-12-26" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2017-12-26T09:21:06-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2005-12-01T14:11:10-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN43-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 6941\nLos Angeles\nCA\n90071\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN" ], [ "org", { }, "text", "ARIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@kamali.org" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-213-375-5522" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN43-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN43-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-05-15T18:28:08-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-21T10:37:30-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN44-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "455 Magnolia Ave\nFairhope, AL 36532\nFairhope\nAL\n36532\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN" ], [ "org", { }, "text", "ARIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@altaworx.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-569-8647" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN44-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN44-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-03-16T09:11:05-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-08-05T16:53:02-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN45-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "One Embarcadero Center\n19th Floor\nSan Francisco\nCA\n94111\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin" ], [ "org", { }, "text", "Arin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@onemedical.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-888-663-6331" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN45-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN45-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-01T12:32:12-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2021-03-25T18:26:31-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN46-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1750 West Front St., Suite 150\nBoise\nID\n83702\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin" ], [ "org", { }, "text", "Arin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@northwest-bank.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-208-332-0700" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN46-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN46-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-04-14T18:57:46-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2021-04-14T18:57:46-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN47-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "125 McCarty Street\nHouston\nTX\n77029\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN" ], [ "org", { }, "text", "ARIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@singerequities.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-605-978-3078" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-04-23" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN47-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN47-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-04-23T12:05:15-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2021-04-23T12:05:15-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN48-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1525 2nd st\nEscalon\nCA\n95320\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "arin" ], [ "org", { }, "text", "arin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@ezwave.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-209-490-0000" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-04-23" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN48-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN48-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-04-23T12:16:04-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2021-04-23T12:16:04-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN49-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "399 Park Ave\n27th Floor - First Manhattan\nNew York\nNY\n10022\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "arin" ], [ "org", { }, "text", "arin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@firstmanhattan.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-212-756-3300" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN49-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN49-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-01-03T18:38:38-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-01-03T12:37:16-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN50-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1475 E. Woodfield Rd., Ste 100\nSchaumburg\nIL\n60173\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN" ], [ "org", { }, "text", "ARIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "no-reply@acbandt.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-847-598-5700" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN50-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN50-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-03-24T18:03:17-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-03-24T18:03:17-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN51-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1440 N Artesian Avenue\nChicago\nIL\n60622-1703\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "arin" ], [ "org", { }, "text", "arin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@ewcs.io" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-872-241-0011" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN51-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN51-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-01T06:24:26-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-04-01T06:24:26-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN52-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "104 S Cascade Ave, Ste. 210-A\nColorado Springs\nCO\n80903\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN" ], [ "org", { }, "text", "ARIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@springstechnology.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-719-270-2270" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN52-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN52-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-05-27T15:37:17-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-05-27T15:37:17-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN53-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "6555 Sanger Road\nSuite 240\nOrlando\nFL\n32827\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN" ], [ "org", { }, "text", "ARIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@bdo.us" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-289-0734" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN53-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN53-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-08-15T14:37:16-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-08-15T14:37:16-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN54-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "2200 Ross Ave\nSte 2800\nDallas\nTX\n75201\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin" ], [ "org", { }, "text", "Arin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@lockelord.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-214-740-8000" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN54-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN54-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-01-05T12:54:15-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2023-01-05T12:54:15-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN55-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "411 Goulet ST.\nWinnipeg\nMB\nR2H 3C7\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin" ], [ "org", { }, "text", "Arin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "admin@canpay.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-204-987-8520;ext301" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN55-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN55-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-01-23T11:36:03-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2023-01-23T11:36:03-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN56-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "6351 W Montrose ave #139\nchicago\nIL\n60634\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "arin" ], [ "org", { }, "text", "arin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@cloudavity.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-312-600-5044" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN56-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN56-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-02-04T11:44:34-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2023-02-04T11:44:34-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1406 Peach Street\nErie\nPA\n16501\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin" ], [ "org", { }, "text", "Arin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@logisticsplus.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-814-461-7610" ], [ "tel", { + "type" : [ "work", "fax" ] + }, "text", "+1-814-461-7625" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-03-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-03-15T09:26:20-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2007-07-12T18:00:07-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN7-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "630 Allendale Road\nKing of Prussia\nPA\n19406\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN" ], [ "org", { }, "text", "ARIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@evolveip.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-610-964-8000" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN7-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN7-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-05-23T08:16:20-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2007-10-02T16:24:11-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARIN8-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1501 Mariposa Street, Suite 418\nSan Francisco\nCA\n94107\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN" ], [ "org", { }, "text", "ARIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@sotech.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-415-430-2200;ext230" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN8-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN8-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2020-11-10T11:34:10-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2008-07-07T14:18:06-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "BAA19-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "kom pong cham, phnom penh, cambodia.\nphnom penh\nkom pong cham\n03301\nCambodia" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Blue Apple ARIN" ], [ "n", { }, "text", [ "ARIN", "Blue", "Apple", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "id24325258327753@aol.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+855889917963;extwww.blueapplearin.net" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "i like ARIN, ARIN is very good." ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/BAA19-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/BAA19-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-07-23T01:31:16-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2021-07-23T01:31:16-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINI2-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "9300 Lee Highway\nFairfax\nVA\n22031\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ICF ARIN" ], [ "n", { }, "text", [ "ARIN", "ICF", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "arin@icfi.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-934-3000" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2021-11-04" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINI2-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINI2-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2020-11-04T07:44:11-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2015-11-04T12:02:17-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINI3-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "6100 Tennyson Pkwy, Ste 200\nPlano\nTX\n75056\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Infosys Arin" ], [ "n", { }, "text", [ "Arin", "Infosys ", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "ipv4-arin@infosys.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-469-229-9400" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINI3-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINI3-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-02-15T07:43:17-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2018-01-02T12:42:16-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "MAR177-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "4937 Spring Road\nVerona\nNY\n13478\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "MORIC Arin" ], [ "n", { }, "text", [ "Arin", "MORIC", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "moricarin@moric.org" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-866-986-6742" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/MAR177-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/MAR177-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-12-09T11:04:07-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2009-06-22T13:36:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINN27-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "Suite B-2, 2035 Sunset Lake Road\nNewark\nDE\n19702\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "NARORO ARIN" ], [ "n", { }, "text", [ "ARIN", "NARORO", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "admin@naroro.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-929-282-2213" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINN27-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINN27-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-12-29T12:22:06-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-10-27T05:19:48-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "NAR13-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "8 Richards Road\nPlymouth\nMA\n02360\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "NETWORK ARIN" ], [ "n", { }, "text", [ "ARIN", "NETWORK", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "NETWORK.ARIN@rocklandtrust.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-781-878-6100" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2010-11-02" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/NAR13-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/NAR13-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2006-07-12T11:18:08-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2006-07-12T11:18:08-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINS15-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "Suite-800-3rd-Ave.\n Calgary\nAB\nT2P 4L4\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Shawa Arin" ], [ "n", { }, "text", [ "Arin", "Shawa", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "internet.abuse@sjrb.ca" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-403-750-7420" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINS15-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINS15-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-01-21T19:42:33-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2023-01-21T19:42:33-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINS12-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "2341 Deerfield Drive\nFort Mill\nSC\n29715\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Sunbelt Arin" ], [ "n", { }, "text", [ "Arin", "Sunbelt", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "sunbelt.arin@sunbeltrentals.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-803-578-5431" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINS12-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINS12-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-05-19T08:42:34-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2017-04-10T10:35:21-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINW1-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "115 W 18TH ST\nNEW YORK\nNY\n10011-7006\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "wework Arin" ], [ "n", { }, "text", [ "Arin", "wework", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "wework-arin@wework.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-855-0865" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-05-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINW1-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINW1-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-05-15T23:25:05-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-05-15T23:25:05-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINW10-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "3001 BISHOP DRIVE\nSAN RAMON\nCA\n94583\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Wework Arin" ], [ "n", { }, "text", [ "Arin", "Wework", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "wework-arin@wework.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-855-0865" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-06-25" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINW10-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINW10-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-06-27T04:22:03-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-06-27T04:22:03-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINW11-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "625 ADAMS STREET WEST\nCHICAGO\nIL\n60661\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Wework Arin" ], [ "n", { }, "text", [ "Arin", "Wework", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "wework-arin@wework.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-855-0865" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-07-03" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINW11-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINW11-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-07-05T04:04:42-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-07-05T04:04:42-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINW12-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "6001 CASS AVENUE\nDETROIT\nMI\n48202\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Wework Arin" ], [ "n", { }, "text", [ "Arin", "Wework", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "wework-arin@wework.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-855-0865" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-07-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINW12-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINW12-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-07-16T04:16:45-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-07-16T04:16:45-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINW14-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "535 MISSION ST\nSAN FRANCISCO\nCA\n94015\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Wework Arin" ], [ "n", { }, "text", [ "Arin", "Wework", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "wework-arin@wework.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-855-0865" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-07-17" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINW14-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINW14-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-07-19T04:09:17-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-07-19T04:09:17-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINW16-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "9830 WILSHIRE BLVD\nBEVERLY HILLS\nCA\n90212-1804\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Wework Arin" ], [ "n", { }, "text", [ "Arin", "Wework", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "wework-arin@wework.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-855-0865" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-07-29" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINW16-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINW16-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-07-31T04:13:11-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-07-31T04:13:11-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINW17-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "115 W 18TH ST\nNEW YORK\nNY\n10011-7006\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "wework Arin" ], [ "n", { }, "text", [ "Arin", "wework", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "wework-arin@wework.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-855-0865" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-07-30" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINW17-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINW17-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-08-01T04:10:41-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-08-01T04:10:41-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINW19-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "625 ADAMS STREET WEST\nCHICAGO\nIL\n60661\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Wework Arin" ], [ "n", { }, "text", [ "Arin", "Wework", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "wework-arin@wework.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-855-0865" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-08-05" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINW19-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINW19-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-08-07T04:05:51-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-08-07T04:05:51-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINW21-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "3001 BISHOP DRIVE\nSAN RAMON\nCA\n94583\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Wework Arin" ], [ "n", { }, "text", [ "Arin", "Wework", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "wework-arin@wework.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-855-0865" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-08-06" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINW21-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINW21-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-08-08T04:20:37-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-08-08T04:20:37-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINW22-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "3001 BISHOP DRIVE\nSAN RAMON\nCA\n94583\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Wework Arin" ], [ "n", { }, "text", [ "Arin", "Wework", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "wework-arin@wework.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-855-0865" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-08-18" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINW22-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINW22-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-08-20T04:09:04-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-08-20T04:09:04-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINW23-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "3001 BISHOP DRIVE\nSAN RAMON\nCA\n94583\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Wework Arin" ], [ "n", { }, "text", [ "Arin", "Wework", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "wework-arin@wework.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-855-0865" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-09-23" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINW23-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINW23-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-09-25T04:12:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-09-25T04:12:14-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINW24-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "3001 BISHOP DRIVE\nSAN RAMON\nCA\n94583\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Wework Arin" ], [ "n", { }, "text", [ "Arin", "Wework", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "wework-arin@wework.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-855-0865" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-09-26" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINW24-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINW24-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-09-28T04:16:11-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-09-28T04:16:11-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINW25-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "3001 BISHOP DRIVE\nSAN RAMON\nCA\n94583\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Wework Arin" ], [ "n", { }, "text", [ "Arin", "Wework", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "wework-arin@wework.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-855-0865" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-09-29" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINW25-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINW25-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-10-01T04:07:01-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-10-01T04:07:01-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINW27-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "3001 BISHOP DRIVE\nSAN RAMON\nCA\n94583\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Wework Arin" ], [ "n", { }, "text", [ "Arin", "Wework", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "wework-arin@wework.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-855-0865" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-10-02" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINW27-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINW27-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-10-04T04:06:03-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-10-04T04:06:03-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINW30-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "3001 BISHOP DRIVE\nSAN RAMON\nCA\n94583\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Wework Arin" ], [ "n", { }, "text", [ "Arin", "Wework", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "wework-arin@wework.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-855-0865" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2021-02-09" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINW30-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINW30-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2020-02-11T03:12:44-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-02-11T03:12:44-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINW4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "115 W 18TH ST\nNEW YORK\nNY\n10011-7006\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "wework Arin" ], [ "n", { }, "text", [ "Arin", "wework", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "wework-arin@wework.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-855-0865" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-06-12" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINW4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINW4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-06-14T04:17:02-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-06-14T04:17:02-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINW5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "115 W 18TH ST\nNEW YORK\nNY\n10011-7006\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "wework Arin" ], [ "n", { }, "text", [ "Arin", "wework", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "wework-arin@wework.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-855-0865" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-06-12" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINW5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINW5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-06-14T04:17:13-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-06-14T04:17:13-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINW6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "391 San Antonio Rd\nMountain View\nCA\n94040\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Wework Arin" ], [ "n", { }, "text", [ "Arin", "Wework", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "wework-arin@wework.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-855-0865" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-06-18" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINW6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINW6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-06-20T04:06:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-06-20T04:06:31-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINW8-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "3001 BISHOP DRIVE\nSAN RAMON\nCA\n94583\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Wework Arin" ], [ "n", { }, "text", [ "Arin", "Wework", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "wework-arin@wework.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-855-0865" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-06-20" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINW8-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINW8-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-06-21T23:10:11-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-06-21T23:10:11-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINW9-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "3001 BISHOP DRIVE\nSAN RAMON\nCA\n94583\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Wework Arin" ], [ "n", { }, "text", [ "Arin", "Wework", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "wework-arin@wework.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-855-0865" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-06-20" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINW9-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINW9-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-06-22T04:09:17-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-06-22T04:09:17-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AWA159-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "391 SAN ANTONIO RD\nMOUNTAIN VIEW\nCA\n94040\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "WeWork Arin Arin" ], [ "n", { }, "text", [ "Arin", "WeWork Arin", "", "", "" ] ], [ "kind", { }, "text", "individual" ], [ "email", { }, "text", "wework-arin@wework.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-855-855-0865" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-09-19" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AWA159-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AWA159-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-09-21T04:03:40-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-09-21T04:03:40-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ABP4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "555 Bryant St. #233\nPalo Alto\nCA\n94301\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN - Baby Penguin" ], [ "org", { }, "text", "ARIN - Baby Penguin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@babypenguin.co" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-650-353-0546" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ABP4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ABP4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-15T15:25:03-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2018-05-11T01:31:02-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA182-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "187 Erie Street\nStratford\nON\nN5A 2V5\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Absue" ], [ "org", { }, "text", "ARIN Absue" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@rhyzome.ca" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-519-271-4633" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2017-11-28" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA182-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA182-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2016-11-28T12:25:51-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2014-10-20T09:21:35-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA101-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "115 Broadway\n14th floor\nNew York\nNY\n10006\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Abuse" ], [ "org", { }, "text", "ARIN Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "techopsadministrator@rts.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-646-291-6724" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-646-440-4081" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "www.recyclebank.com" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA101-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA101-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-09-30T15:42:59-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2010-11-19T17:04:40-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA157-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "100 Campus Circle\nOwings Mills\nMD\n21117\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Abuse" ], [ "org", { }, "text", "ARIN Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ARINabuse@stevenson.edu" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-443-334-3000" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA157-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA157-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-09-27T08:27:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-11-12T12:26:49-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA162-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "8000 Marina Blvd\nSuite 130\nBrisbane\nCA\n94005\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Abuse" ], [ "org", { }, "text", "ARIN Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ggilkinson@voxns.com" ], [ "email", { }, "text", "dsmith@voxns.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-650-989-2681" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-707-961-6237" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2021-01-30" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA162-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA162-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2020-01-31T08:30:41-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2014-04-11T18:50:45-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA165-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "2443 Fillmore St #380-4233\nSan Francisco\nCA\n94115\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Abuse" ], [ "org", { }, "text", "ARIN Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@iocoop.org" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-415-237-4183" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "https://iocoop.org/" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA165-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA165-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-18T10:12:27-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2014-04-30T17:48:12-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA173-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "7075 Flying Cloud Drive\nEden Prairie\nMN\n55344\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Abuse" ], [ "org", { }, "text", "ARIN Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ARIN-Abuse@bluestembrands.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-952-656-3700" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA173-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA173-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T07:43:02-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2014-06-24T10:24:52-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA187-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "303 Ravendale Dr.\nMountain View\nCA\n94043\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Abuse" ], [ "org", { }, "text", "Arin Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-abuse@tintri.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-650-810-8381" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2016-01-14" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA187-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA187-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2015-01-14T14:26:18-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2015-01-14T14:26:18-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA189-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "5 Mobile Infirmary Circle\nMobile\nAL\n36607\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Abuse" ], [ "org", { }, "text", "ARIN Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ARIN.Abuse@infirmaryhealth.org" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-251-435-2913" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-12-03" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA189-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA189-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-03T09:05:32-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2015-04-01T16:17:58-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA199-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "755 rue Berri\r\nSuite 200\nMontreal\nQC\nH2Y 3E5\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Abuse" ], [ "org", { }, "text", "Arin Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arinabuse@jestais.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-514-925-5100;ext4444" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2019-10-25" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA199-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA199-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2018-10-25T10:10:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2015-10-26T08:35:23-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA218-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1 MacArthur Pl.\r\nSuite 400\nSanta Ana\nCA\n92707\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Abuse" ], [ "org", { }, "text", "ARIN Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-abuse@trustcommerce.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-949-387-3747" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA218-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA218-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-02-24T13:42:55-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2017-01-25T16:29:03-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA264-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "292 Marshall Street\nMiddleton\nNS\nB0S 1P0\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Abuse" ], [ "org", { }, "text", "ARIN Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-abuse@mainlandtelecom.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-902-365-3132" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA264-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA264-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-01-30T14:42:19-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-10-04T12:34:46-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA266-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "27200 Riverview Center Blvd\nBonita Springs\nFL\n34134\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Abuse" ], [ "org", { }, "text", "ARIN Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "airtech@airtechfd.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-256-548-9067" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-11-03" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA266-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA266-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-11-04T11:30:12-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-11-04T11:30:12-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA316-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1704 West Pheasant Trail\nMount Prospect\nIL\n60056\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Abuse" ], [ "org", { }, "text", "ARIN Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-abuse@mjfventures.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-866-573-1653" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA316-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA316-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-10-05T17:04:34-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-10-05T17:04:34-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA322-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "700 Graham Rd\nCuyahoga Falls\nOH\n44221\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Abuse" ], [ "org", { }, "text", "ARIN Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-abuse@neonet.org" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-330-926-3900;ext" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA322-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA322-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-02-23T15:19:06-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2023-02-23T15:19:06-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA8-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "675 MCDONNELL BLVD\nHAZELWOOD\nMO\n63042\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Abuse" ], [ "org", { }, "text", "ARIN Abuse" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "NetOps@Mallinckrodt.com" ], [ "email", { }, "text", "John.Hamai@MNK.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-314-654-3901" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA8-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA8-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-08-12T16:43:33-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2003-06-05T12:10:37-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAATT-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "5101 Tennyson Parkway\nPlano\nTX\n75024\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Abuse at Tyler Tech" ], [ "org", { }, "text", "ARIN Abuse at Tyler Tech" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ARINAbuse@TylerTech.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-972-713-3700" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAATT-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAATT-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-11-30T09:08:10-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-09-24T12:16:19-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAC10-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "305 Vineyard Town Center, #251\nMorgan Hill\nCA\n95037\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Abuse Contact" ], [ "org", { }, "text", "ARIN Abuse Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@trinadex.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-408-683-4000" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2010-07-25" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAC10-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAC10-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2003-05-06T14:58:27-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2003-05-06T14:58:27-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAC31-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1620 W. Fountainhead Pkwy #400\nTEMPE\nAZ\n85282\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Abuse Contact" ], [ "org", { }, "text", "ARIN Abuse Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-contact@calence.com" ], [ "email", { }, "text", "itsecurityalerts@insight.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-480-889-9600" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAC31-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAC31-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-11-16T14:26:03-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2010-04-20T15:34:24-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAPTC-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "8 Vreeland Ave.\nFlorham Park\nNJ\n07932\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN ABUSE POC for Troy Corporation" ], [ "org", { }, "text", "ARIN ABUSE POC for Troy Corporation" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ARIN-POC-ABUSE@troycorp.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-973-443-4200;ext2539" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2017-12-02" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAPTC-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAPTC-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2016-12-02T09:22:13-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2015-12-03T15:39:59-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAR30-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "2500 Plaza 5\nJersey City\nNJ\n07311\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Abuse Role" ], [ "org", { }, "text", "ARIN Abuse Role" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@febox.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-908-517-5206" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2015-09-06" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAR30-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAR30-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2014-09-06T18:07:38-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2005-06-21T15:15:12-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAR31-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 611507\nSan Jose\nCA\n95161\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Abuse Role" ], [ "org", { }, "text", "ARIN Abuse Role" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@simpli.biz" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-877-574-6754" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAR31-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAR31-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2010-07-26T15:01:12-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2005-06-28T17:30:12-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AARA2-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "9535 Forest Lane, Ste 201\nDallas\nTX\n75243\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Abuse Role Account" ], [ "org", { }, "text", "ARIN Abuse Role Account" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@odysseyisp.net" ], [ "email", { }, "text", "arin.abuse.poc@odysseyisp.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-972-997-9052" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "In case abuse is suspected, please contact us immediately." ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AARA2-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AARA2-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-11-12T15:47:26-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2005-08-26T12:04:04-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAS50-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "6080 Young Street\nSuite 700\nHalifax\nNS\nB3K-5M3\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Abuse Support" ], [ "org", { }, "text", "ARIN Abuse Support" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-abuse@eastlink.ca" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-902-453-2800" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "EastLink ARIN Abuse Contact" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAS50-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAS50-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-05-12T14:49:11-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2007-03-14T10:54:11-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAS79-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "5251 Duke Street\n3rd Floor\nHalifax\nNS\nB3J 1P3\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Abuse support" ], [ "org", { }, "text", "ARIN Abuse support" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "HRMServiceDesk@halifax.ca" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-902-490-4444" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2021-08-05" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAS79-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAS79-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2020-08-05T10:39:07-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-05-16T11:12:14-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA204-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "180 N. LaSalle St.\nChicago\nIL\n60601\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Access" ], [ "org", { }, "text", "ARIN Access" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "tcraven@bylinebank.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-708-797-4080" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA204-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA204-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-02-03T09:32:02-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2016-02-04T15:07:30-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAA66-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "3036 Seaborg Ave, Suite A\nVentura\nCA\n93003\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Account Admin" ], [ "org", { }, "text", "ARIN Account Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@mjp.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-805-981-9511" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAA66-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAA66-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-08-11T12:03:25-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2017-03-02T18:24:33-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAR50-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "5742 West Harold Gatty Drive\nSalt Lake City\nUT\n84116\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Account Register" ], [ "org", { }, "text", "ARIN Account Register" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ARIN@basicresearch.org" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-801-517-7000" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2016-11-19" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAR50-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAR50-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2015-11-20T16:51:22-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2007-08-08T17:42:08-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AA415-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "4 Technology Drive\nWestborough\nMA\n01581\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN ADMIN" ], [ "org", { }, "text", "ARIN ADMIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin_admin@akibia.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-508-804-5152" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2011-05-24" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AA415-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AA415-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2010-05-24T15:12:57-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2001-01-16T14:22:25-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ADMIN1804-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "6720 Bright Ave\nWhittier\nCA\n90601\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "noc@globalit.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-562-698-2500;ext1" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ADMIN1804-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ADMIN1804-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2017-07-07T13:36:25-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2009-11-04T19:04:29-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA100-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "115 Broadway\n14th floor\nNew York\nNY\n10006\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "techopsadministrator@rts.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-646-291-6724" ], [ "tel", { + "type" : [ "work", "fax" ] + }, "text", "+1-646-440-4081" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "http://www.recyclebank.com" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA100-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA100-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-09-30T15:45:05-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2010-11-19T10:52:59-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA104-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1 Front Street\r\nSuite 2700\nSan Francisco\nCA\n94111\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@lookout.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-415-281-2820" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "https://www.lookout.com" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA104-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA104-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-17T13:38:40-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2010-12-15T14:07:46-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA105-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "2345 Grand Blvd\nSuite 2400\nKansas City\nMO\n64108\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin_admin@jackcooper.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-816-983-4056" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "Hours: 8am - 4pm M-F" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA105-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA105-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-09-13T16:39:18-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2011-01-04T18:55:55-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA106-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "119 N 600 W\nOgden\nUT\n84404\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-admin@GorillaServers.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-800-793-2609" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "Mon-Fri 9-5pm PST" ] + }, { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-12-31" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA106-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA106-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-31T15:12:40-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2011-01-16T00:52:10-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA113-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "2430 N Lincoln\nFremont\nNE\n68025\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "briang@omni-tech.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-402-753-9154" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA113-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA113-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-09-19T12:25:57-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2011-04-26T15:54:01-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA117-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "3471 N First Street\nSan Jose\nCA\n95134\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@wyse.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-408-473-1200" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "http://www.wyse.com" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA117-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA117-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2012-07-02T10:07:57-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2011-07-01T17:49:02-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA124-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "911 10th St.\nGolden\nCO\n80401\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "kdickson@cityofgolden.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-303-384-8149" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA124-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA124-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2011-10-20T09:23:49-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2011-10-20T09:23:49-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA130-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "25 Highland Park Village\r\n#100-419\nDallas\nTX\n75205\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "admin@cloudagy.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-214-736-8810" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2021-03-18" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA130-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA130-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2020-03-18T08:39:13-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-01-27T17:51:39-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA133-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1600 Grand Ave\nSaint Paul\nMN\n55105\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin.contact@macalester.edu" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-651-696-6000" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA133-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA133-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-21T11:28:54-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-03-05T10:48:29-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA134-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "3030 N Central Ave\nPhoenix\nAZ\n85012\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arinadmin@idiscoverglobal.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-602-648-3222" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA134-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA134-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-01T00:21:03-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-04-06T01:20:03-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA135-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "100 N El Portal Dr\nLA Habra\nCA\n90631\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@i9biz.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-562-623-9600" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "http://www.i9biz.com" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA135-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA135-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-09-08T13:00:40-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-04-09T15:12:01-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA139-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "130 Wells Dr\nSpringboro\nOH\n45066\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "justin.oeder@beyondholdingsllc.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-513-432-5152" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA139-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA139-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-03-07T05:52:55-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-06-18T12:50:06-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA14-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "5312 West Washington Center Rd\nFort Wayne\nIN\n46818\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@indigital.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-260-469-2010" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-12-13" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA14-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA14-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-13T07:01:06-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2004-03-08T20:15:53-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA141-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "300 Fibre Way\nLongview\nWA\n98632\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "org", { }, "text", "Arin Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "jshopkins@longfibre.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-360-575-5178" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2014-10-09" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA141-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA141-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2013-10-09T11:13:01-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-08-01T11:44:43-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA146-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "450 Lambert Ave\nPalo Alto\nCA\n94306\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arinadmin@instartlogic.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-650-919-8856" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2019-07-08" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA146-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA146-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2018-07-08T15:39:54-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-11-01T23:17:37-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA148-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "550 106th Ave NE\nSuite 207\nBellevue\nWA\n98004\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "Arin_Admin_DC@bungie.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-425-440-6900" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA148-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA148-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-30T14:12:51-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-12-17T16:25:03-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA156-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "7620 NW 25 Street, Unit 7\nMiami\nFL\n33122\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "org", { }, "text", "Arin Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@adistec.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-786-221-2316" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA156-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA156-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-08-24T09:58:58-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-08-21T13:35:28-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA161-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "8000 Marina Blvd.\nSuite 130\nBrisbane\nCA\n94005\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "dsmith@voxns.com" ], [ "email", { }, "text", "jcotter@voxns.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-650-989-1021" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-707-961-6237" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "http://www.voxns.com" ] + }, { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2021-01-30" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA161-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA161-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2020-01-31T08:30:58-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2014-04-11T18:04:15-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA164-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "2443 Fillmore St #380-4233\nSan Francisco\nCA\n94115\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@iocoop.org" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-415-237-4183" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "https://iocoop.org/" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA164-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA164-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-11T14:29:01-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2014-04-30T17:40:17-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA174-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "7475 Boul. Newman\nSuite# 510\nMontreal\nQC\nH8N 1X3\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "org", { }, "text", "Arin Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "nbiswas@ipvision.ca" ], [ "email", { }, "text", "shihab@ipvision.ca" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-514-368-9000" ], [ "tel", { + "type" : [ "work", "cell" ] + }, "text", "+1-514-715-6944" ], [ "tel", { + "type" : [ "work", "fax" ] + }, "text", "+1-514-221-3055" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2018-07-07" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA174-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA174-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2017-07-07T10:33:16-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2014-07-29T13:44:15-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA183-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "640 Belle Terre Rd\nBuilding H\nPort Jefferson\nNY\n11777\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arinadmin@fusionnetworks.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-631-331-3000" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "http://www.fusionnetworks.net", "", "Standard Hours are M-F 9 AM to 5:30 PM EST" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA183-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA183-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-09-09T11:56:01-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2014-10-20T17:22:39-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA188-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1241 W Mineral Ave suite #200\nLittleton\nCO\n80120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ArinAdmin@Alcoholmonitoring.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-303-785-7842" ], [ "tel", { + "type" : [ "work", "cell" ] + }, "text", "+1-303-785-7842" ], [ "tel", { + "type" : [ "work", "cell" ] + }, "text", "+1-303-989-8900" ], [ "tel", { + "type" : [ "work", "fax" ] + }, "text", "+1-303-791-4262" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA188-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA188-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2015-03-31T13:24:19-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2015-03-31T13:24:19-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA193-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "60 E. Rio Salado Parkway, Suite 400\nTempe\nAZ\n85281\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ipmgmt@lifelock.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-480-457-2000" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA193-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA193-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2017-05-17T14:58:32-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2015-05-15T13:05:50-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA202-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "777 Post Oak Blvd\r\nSuite 800\nHouston\nTX\n77056\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "DL-IT@vantagedrilling.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-281-404-4700;ext4719" ], [ "tel", { + "type" : [ "work", "cell" ] + }, "text", "+1-713-515-5208" ], [ "tel", { + "type" : [ "work", "cell" ] + }, "text", "+1-970-903-1381" ], [ "tel", { + "type" : [ "work", "fax" ] + }, "text", "+1-281-404-4749" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA202-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA202-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2015-12-08T03:43:21-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2015-12-08T03:43:21-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA203-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "11400 SE 6TH St Suite 125\nBellevue\nWA\n98004\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-admin@savers.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-425-462-1515" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA203-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA203-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-02-09T13:59:55-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2016-01-15T19:09:07-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA210-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "70 Lincoln Center Plz\nNew York\nNY\n10023\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "org", { }, "text", "Arin Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "itsupport@lincolncenter.org" ], [ "email", { }, "text", "lbecker@lincolncenter.org" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-212-875-5295" ], [ "tel", { + "type" : [ "work", "cell" ] + }, "text", "+1-646-533-0267" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA210-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA210-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-12-07T12:20:01-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2016-04-28T09:29:56-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA212-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "2355 Hwy 36 W., STE 100\nRoseville\nMN\n55113\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "licensing@midwestradiology.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-651-292-2100" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-07-01" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA212-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA212-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-07-01T07:02:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2016-07-13T11:10:14-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA219-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "951 DRAKE RD, \r\nGLENVIEW, IL \r\n60025\nGLENVIEW\nIL\n60025\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "admin@hovanet.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-909-345-0468" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "Standard NOC hours are 9:00 AM to 6:00 PM EST" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA219-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA219-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2017-01-26T22:04:21-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2017-01-26T22:04:21-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA226-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1740 1 Avenue NW\nCalgary\nAB\nT2N 0B1\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arinadmin@yycnetlab.org" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-403-681-4172" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA226-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA226-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-20T10:58:24-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2017-04-13T11:53:05-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA230-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "11030 O St\nOmaha\nNE\n68137\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "org", { }, "text", "Arin Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin_admin@omahasteaks.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-402-597-3000" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA230-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA230-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-27T09:52:57-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2017-05-03T09:15:10-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA233-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "111 Westwood Place\nSuite 100\nBrentwood\nTN\n37027\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN ADMIN" ], [ "org", { }, "text", "ARIN ADMIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arinadmin@brookdale.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-615-221-2250" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA233-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA233-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-20T09:31:37-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2017-08-03T13:49:10-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA240-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "3300 West Olive Avenue\nBurbank \nCA\n91505\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "org", { }, "text", "Arin Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-admin@warnerbros.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-818-977-1935" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA240-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA240-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-01-09T08:50:24-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2018-01-09T17:28:29-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA241-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "4730 South Fort Apache Road Suite 300\nLas Vegas\nNV\n89147\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "org", { }, "text", "Arin Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@fc2.us" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-310-414-9626" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA241-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA241-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-03-25T11:04:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2018-03-22T17:58:58-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA243-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "381 E Evelyn Ave\nMountain View\nCA\n94041\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "org", { }, "text", "Arin Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "rwildgrube@coursera.org" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-650-430-2228" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA243-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA243-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-05-02T14:43:01-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2018-04-28T03:40:13-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA250-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P O Box 3729\nPagosa Springs\nCO\n81147\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "org", { }, "text", "Arin Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "accountspayagble@parelli.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-970-731-6241" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-09-25" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA250-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA250-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-09-26T17:58:48-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2018-09-26T15:35:48-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA263-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "292 Marshall Street\nMiddleton\nNS\nB0S 1P0\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-admin@mainlandtelecom.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-902-365-3132" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA263-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA263-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-01-30T14:42:35-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-10-04T11:45:09-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA270-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "2050 Lexington Rd\nVersailles\nKY\n40383\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "org", { }, "text", "Arin Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arinadmin@frontier.edu" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-859-251-4545" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA270-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA270-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-02-04T22:44:07-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-03-13T14:31:56-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA274-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "3543 Simpson Ferry Road\nCamp Hill\nPA\n17011\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-admin@footlocker.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-717-763-5200" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA274-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA274-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-05-10T10:30:02-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-05-04T11:38:10-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA278-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "111 West Jackson\nSuite 400\nChicago\nIL\n60604\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "org", { }, "text", "Arin Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@harriscollect.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-866-781-4538" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA278-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA278-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-08-15T08:12:23-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-08-13T17:07:20-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA283-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "105-111 Euston Street\nLondon\n\nNW1 2EW\nUnited Kingdom" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "org", { }, "text", "Arin Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@dengage.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+44 20 3731 9198" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA283-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA283-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-01-20T02:07:05-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2021-01-20T02:06:24-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA285-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1 Yonge Street\nSuite 1801\nToronto\nON\nM5E 1W7\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-admin@finchtech.ca" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-416-834-5960" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA285-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA285-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-01-22T11:17:17-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2021-01-22T11:17:17-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA286-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "700 Westpark Drive\nPeachtree City\nGA\n30269\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ARIN_Admin@dodea.edu" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-470-460-2047" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA286-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA286-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-03-03T07:38:05-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2021-02-04T10:59:41-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA287-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "300 North Coit Road\nSuite 1400\nRichardson\nTX\n75080\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "admin@vistacollege.edu" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-972-733-3431" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA287-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA287-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-03-13T12:54:40-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2021-03-13T12:54:40-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA288-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "580 Wald\nIrvine\nCA\n92618\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@nextmill.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-949-252-8772;ext222" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA288-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA288-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-03-23T12:33:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2021-03-23T13:36:47-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA290-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "200 W. 4th St.\nMadera \nCA\n93637\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN ADMIN" ], [ "org", { }, "text", "ARIN ADMIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "brian.rohde@maderacounty.com" ], [ "email", { }, "text", "aaron.ortiz@maderacounty.com" ], [ "email", { }, "text", "chou.yang@maderacounty.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-559-661-5267" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA290-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA290-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-07T17:00:58-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2021-04-21T18:00:13-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA291-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "550 California Ave Suite 100\nPalo Alto\nCA\n94306\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "org", { }, "text", "Arin Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "alvaro@def.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-650-862-4759" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA291-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA291-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-06-05T17:53:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2021-06-05T17:53:47-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA292-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 4070\nCastaic\nCA\n91310-4070\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "dns@lfcu.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-818-565-2444" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "https://www.lfcu.com" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA292-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA292-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-08-10T18:53:54-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2021-07-22T16:05:01-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA293-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "2443 N. Greenfield Road \nSuite 116\nMesa\nAZ\n85215\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "joesterle@magnifytelecom.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-619-800-5646" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA293-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA293-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-01-05T13:14:16-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2021-07-27T14:58:03-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA294-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "400 Rivers Edge Drive\nMedford\nMA\n02155\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin_admin@agero.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-781-306-3397" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA294-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA294-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-09-13T13:16:24-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2021-09-13T13:16:24-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA299-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "2105 Vista Oeste NW, #E-3280\nAlbuquerque\nNM\n87120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "org", { }, "text", "Arin Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-admin@dynanode.io" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-505-484-2884" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA299-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA299-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-02-01T15:39:55-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-02-01T15:39:55-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA3-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "101 N Cherry St.\nWINSTON SALEM\nNC\n27101\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "arin admin" ], [ "org", { }, "text", "arin admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin_admin@rmic.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-800-999-7642;ext4046" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA3-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA3-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-09-08T07:32:21-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2003-01-17T17:14:07-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA304-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1200 Corporate Dr #400\nBirmingham\nAL\n35242\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN ADMIN" ], [ "org", { }, "text", "ARIN ADMIN" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "IT-NetOps@urpt.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-205-991-7488" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA304-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA304-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-03-11T10:04:01-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-03-11T10:04:01-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA305-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1920 McKinney Ave, Dallas\nDallas\nTX\n75201\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "org", { }, "text", "Arin Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@copeland.tech" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-903-530-2755" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA305-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA305-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-03-16T14:56:28-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-03-16T14:56:28-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA307-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "4780 NW 41st St. STE 500\nRiverside\nMO\n64150\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arinadmin@velociti.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-913-551-0121" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA307-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA307-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-03-28T17:40:06-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-03-28T17:40:06-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA315-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1704 West Pheasant Trail\nMount Prospect\nIL\n60056\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-admin@mjfventures.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-866-573-1653" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA315-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA315-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-10-05T17:02:40-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-10-05T17:02:40-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA317-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "PO Box 5035\n300 Dufferin Avenue\nLondon\nON\nN6A 4L9\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "dns@london.ca" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-519-661-2489;ext7513" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA317-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA317-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-10-25T15:06:34-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-10-25T15:06:34-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA318-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "20200 Eastway Village Drive\nHumble\nTX\n77338\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arinadmin@humbleisd.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-281-641-1000" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA318-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA318-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-11-17T14:12:14-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-11-17T14:12:14-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA321-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "72 Park Terrace West\n#E16\nNew York\nNY\n10034\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "org", { }, "text", "Arin Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin_admin@gardensnyc.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-212-567-6376" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA321-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA321-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-01-27T15:47:52-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2023-01-27T15:47:52-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA46-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1825 South Grant Street\nSuite 400\nSan Mateo\nCA\n94402\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "peering@talkplus.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-650-926-9711" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2010-07-29" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA46-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA46-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2006-08-03T16:40:44-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2006-08-03T16:40:44-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA51-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "4243C Dundas Street West, Suite 405\nEtobicoke\nCanada\nToronto\nON\nM8X 1Y3\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@easydns.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-416-535-8672" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA51-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA51-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-02-28T17:43:02-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2007-02-16T09:12:03-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA52-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "15th and L streets NW\nWashington\nDC\n20016\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ARIN_Admin@fanniemae.com" ], [ "email", { }, "text", "hes-net-group-hobbs@fanniemae.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-800-232-6643" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA52-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA52-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-09T17:10:29-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2007-05-08T10:45:27-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA63-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "Business Affairs - Levermore 301\n08. Address 1 South Ave\nGarden City\nNY\n11530\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "org", { }, "text", "Arin Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arinadmin@adelphi.edu" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-516-877-3240" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA63-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA63-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-11-10T08:49:54-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2008-05-23T10:48:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA67-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "100 Union Square Drive\nNew Hope\nPA\n18938\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-admin@meetme.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-215-862-1162" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA67-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA67-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-10-20T07:14:30-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2008-10-27T09:58:07-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA68-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "89 Genesee Street\nRochester\nNY\n14611\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin.admin@rochesterregional.org" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-585-723-7000" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2021-10-20" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA68-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA68-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2020-10-20T09:54:18-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2008-10-29T12:24:18-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA87-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "7900 Westpark Drive\nSuite A-100\nMclean\nVA\n22102\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin.admin@ntiva.com" ], [ "email", { }, "text", "noc@ntiva.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-738-2981" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "https://www.ntiva.com" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA87-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA87-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-07T06:37:28-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2010-04-30T13:00:37-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA91-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "40 Fulton, 8th Floor\nNew York\nNY\n10038\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "npapaseraphim@pubdata.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-212-673-3210" ], [ "tel", { + "type" : [ "work", "cell" ] + }, "text", "+1-212-673-3210" ], [ "tel", { + "type" : [ "work", "fax" ] + }, "text", "+1-212-673-5261" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA91-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA91-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-10T11:08:02-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2010-05-18T12:59:44-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA96-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "696 Route 46 West\nTeterboro\nNJ\n07608\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "org", { }, "text", "Arin Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-admin@cdillc.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-973-455-0606" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA96-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA96-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-06T07:32:27-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2010-09-14T14:20:51-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA97-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1277 Treat Blvd., Suite 700\nWalnut Creek\nCA\n94597\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Admin" ], [ "org", { }, "text", "Arin Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ip-admin@paradigmcorp.com" ], [ "email", { }, "text", "arin@paradigmcorp.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-925-676-2300" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "www.paradigmcorp.com" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA97-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA97-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-03-03T13:25:15-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2010-10-13T12:50:23-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA99-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "100 SW Main Street\r\nSuite 1500\nPortland\nOR\n97204\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin" ], [ "org", { }, "text", "ARIN Admin" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arinadmin@peci.org" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-503-248-4636" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA99-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA99-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2014-11-11T13:20:19-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2010-11-15T15:03:02-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAAAM-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "One Dauch Dr\nDetroit\nMI\n48211\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin - American Axle Manufacturing" ], [ "org", { }, "text", "ARIN Admin - American Axle Manufacturing" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arinadmin@aam.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-313-758-4582" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2019-12-09" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAAAM-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAAAM-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2018-12-09T21:43:52-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2017-12-08T13:18:22-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAC29-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "Information Security Technology Governance\n2090 Parkway Office Circle\nBirmingham\nAL\n35244\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin Contact" ], [ "org", { }, "text", "ARIN Admin Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "deric.reaves@regions.com" ], [ "email", { }, "text", "tom.symons@regions.com" ], [ "email", { }, "text", "ivana.cojbasic@regions.com" ], [ "email", { }, "text", "jay.patty@regions.com" ], [ "email", { }, "text", "eric.twitchell@regions.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-205-914-4551" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAC29-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAC29-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-30T08:46:20-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2009-05-05T11:24:03-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAC9-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "305 Vineyard Town Center, #251\nMorgan Hill\nCA\n95037\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin Contact" ], [ "org", { }, "text", "ARIN Admin Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "admin@trinadex.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-408-683-4000" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2010-07-25" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAC9-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAC9-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2003-05-06T14:58:27-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2003-05-06T14:58:27-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAG19-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "514 Daniels Street\nSuite 335\nRaleigh\nNC\n27605\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin Group" ], [ "org", { }, "text", "ARIN Admin Group" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@solidterrace.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-919-283-4005" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2018-12-20" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAG19-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAG19-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2017-12-20T12:54:38-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2017-12-20T12:54:38-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAR88-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "660 Alabama St\nSan Francisco\nCA\n94110\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN admin role" ], [ "org", { }, "text", "ARIN admin role" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "icann@burningman.org" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-415-865-3800" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAR88-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAR88-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-28T18:05:53-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-02-14T17:03:38-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AARA1-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "9535 Forest Lane Suite 201\nDallas\nTX\n75243\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin Role Account" ], [ "org", { }, "text", "ARIN Admin Role Account" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin.admin.poc@odysseyisp.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-972-997-9052" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "In case abuse is suspected,please contact us", "immediately." ] + }, { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-10-02" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AARA1-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AARA1-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-10-02T10:13:03-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2005-08-26T11:00:09-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AARA3-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "9535 Forest Lane\nSuite 201\nDallas\nTX\n75243\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARiN Admin Role Account" ], [ "org", { }, "text", "ARiN Admin Role Account" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin.admin.poc@odysseyisp.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-972-997-9052" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AARA3-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AARA3-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-10-02T10:02:48-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2021-10-02T10:02:48-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAS49-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "6080 Young Street\nSuite 700\nHalifax\nNS\nB3K-5M3\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin Support" ], [ "org", { }, "text", "ARIN Admin Support" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-admin@eastlink.ca" ], [ "email", { }, "text", "peering@eastlink.ca" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-902-453-2800" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "EastLink ARIN Technical Support Contact" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAS49-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAS49-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-10-21T14:46:44-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2007-03-13T12:54:11-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAS80-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "5251 Duke Street\n3rd Floor\nHalifax\nNS\nB3J 1P3\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admin Support" ], [ "org", { }, "text", "ARIN Admin Support" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "HRMServiceDesk@halifax.ca" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-902-490-4444" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2021-08-05" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAS80-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAS80-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2020-08-05T10:38:39-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-05-17T09:19:49-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA217-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1 MacArthur Pl.\r\nSuite 400\nSanta Ana\nCA\n92707\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administration" ], [ "org", { }, "text", "ARIN Administration" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-admin@trustcommerce.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-949-387-3747" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA217-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA217-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-02-24T13:43:06-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2017-01-25T16:18:46-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA235-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "8806 Main Street\nFlorenceville-Bristol\nNB\nE7L 3G2\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administration" ], [ "org", { }, "text", "ARIN Administration" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "cscommsteam@mccain.ca" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-416-955-1700" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA235-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA235-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-09-30T07:54:35-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2017-09-14T10:26:57-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA280-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "Suite 1700\n440 - 2nd Ave SW\nCalgary\nAB\nT2P 5E9\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administration" ], [ "org", { }, "text", "ARIN Administration" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "is.billing@gibsonenergy.com" ], [ "email", { }, "text", "janice.proctor@gibsonenergy.com" ], [ "email", { }, "text", "mohamed.borhot@gibsonenergy.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-587-316-2603" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA280-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA280-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-12-07T16:17:45-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-12-17T10:57:47-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA302-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1313 N. Market St\n6th Floor\nWilmington\nDE\n19801\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Administration" ], [ "org", { }, "text", "Arin Administration" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "smartin@potteranderson.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-302-984-6000;ext6150" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA302-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA302-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-02-16T09:09:54-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-02-16T09:09:54-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA75-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "14015 Minuteman Dr\nDraper\nUT\n84020\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Administration" ], [ "org", { }, "text", "Arin Administration" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@utahbroadband.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-801-717-2000" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA75-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA75-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-08-01T15:57:09-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2009-03-23T14:18:07-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAG-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1660 - 1050 West Pender St.\nVancouver\nBC\nV6E 4T3\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administration Group" ], [ "org", { }, "text", "ARIN Administration Group" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-admin@radiant.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-604-257-0500" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAG-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAG-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-01-23T13:37:48-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2002-09-04T12:45:24-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA136-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 1778\nErie\nPA\n16512\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrative" ], [ "org", { }, "text", "ARIN Administrative" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-admin@velocity.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-814-833-9111" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "Standard Hours are M-F 8a-5p Eastern" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA136-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA136-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-12-20T08:03:24-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-04-13T10:26:35-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAA22-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1055 Parsippany Blvd.\nSuite 301\nParsippany\nNJ\n07054\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrative Alias" ], [ "org", { }, "text", "ARIN Administrative Alias" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "matt.sprague@cdillc.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-201-426-9346" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAA22-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAA22-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-05-24T07:59:10-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2004-06-22T10:03:39-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAC11-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "307 W. 7th Street\r\nSuite 1600\nFort Worth\nTX\n76102\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrative Contact" ], [ "org", { }, "text", "ARIN Administrative Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin.admin@transcomus.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-817-338-3700" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2014-11-06" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAC11-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAC11-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2013-11-06T08:09:42-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2003-07-09T17:16:41-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAC18-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "100 Plaza Drive\nSecaucus\nNJ\n07094\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrative Contact" ], [ "org", { }, "text", "ARIN Administrative Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-admin@nba.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-201-974-6166" ], [ "tel", { + "type" : [ "work", "fax" ] + }, "text", "+1-201-590-3590" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAC18-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAC18-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-01-30T07:51:19-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2006-04-09T03:42:04-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAC21-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "501 West Grand Blvd\nOklahoma City\nOK\n73118\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrative Contact" ], [ "org", { }, "text", "ARIN Administrative Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-admin@midfirst.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-405-840-7600" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2010-07-25" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAC21-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAC21-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2007-02-28T10:12:09-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2007-02-28T10:12:09-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAC38-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1430 West Peachtree Street NW\nSuite 425\nAtlanta\nGA\n30309\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Administrative Contact" ], [ "org", { }, "text", "Arin Administrative Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@ivision.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-678-999-3002" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "https://ivision.com" ] + }, { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-10-08" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAC38-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAC38-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-10-08T07:06:31-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2018-10-08T16:50:23-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAC4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "2800 Bridge Parkway\nRedwood City\nCA\n94065\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrative Contact" ], [ "org", { }, "text", "ARIN Administrative Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-admin@shutterfly.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-650-610-5200" ], [ "tel", { + "type" : [ "work", "fax" ] + }, "text", "+1-650-654-1299" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2019-11-08" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAC4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAC4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2018-11-08T11:56:21-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2002-08-28T16:50:24-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAC8-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "4141 Yonge Street\r\nSuite 204\nToronto\nON\nM2P-2A8\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrative Contact" ], [ "org", { }, "text", "ARIN Administrative Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin_admin@registrypro.pro" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-416-646-3304" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAC8-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAC8-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2016-04-09T09:44:02-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2003-04-28T14:22:38-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAR29-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "2500 Plaza 5\nJersey City\nNJ\n07311\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrative Role" ], [ "org", { }, "text", "ARIN Administrative Role" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@febox.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-908-517-5206" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2015-09-06" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAR29-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAR29-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2014-09-06T18:07:43-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2005-06-21T15:15:06-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA102-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "555 Gateway Drive\nNapa\nCA\n94558\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "DL.IT.NetworkServices@tweglobal.com" ], [ "email", { }, "text", "Internet.Admin@tweglobal.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-707-259-4500" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA102-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA102-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-05-20T15:40:51-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2010-12-08T18:45:35-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA108-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "320 Wakara Way\nSalt Lake City\nUT\n84108\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arinadministrator@myriad.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-801-584-3600" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "www.myriad.com" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA108-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA108-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-11-05T11:44:01-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2011-02-15T16:45:15-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA11-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "4210 Coronado Avenue\nStockton\nCA\n95204\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arinadmin@pacwest.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-800-511-9048" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2017-12-29" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA11-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA11-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2016-12-29T13:45:38-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2003-08-11T15:17:03-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA131-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "240 Greenwich Avenue\nGreenwich\nCT\n06830\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@townsquaremedia.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-203-413-1475" ], [ "tel", { + "type" : [ "work", "cell" ] + }, "text", "+1-513-478-9987" ], [ "tel", { + "type" : [ "work", "fax" ] + }, "text", "+1-800-301-6408" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "http://townsquaremedia.com" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA131-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA131-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2015-11-10T08:27:56-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-02-22T12:21:03-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA149-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "5130 Commercial Circle\nConcord\nCA\n94520\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ARIN@bayalarm.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-800-610-1000" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA149-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA149-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-11-29T20:18:52-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-01-08T20:57:19-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA150-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "27877 Clemens Rd\nWestlake\nOH\n44145\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "richard.ferchen@momentumtelecom.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-440-808-4834" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA150-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA150-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-02T11:24:17-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-02-11T10:13:18-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA158-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "501 2nd Avenue SE\nCedar Rapids\nIA\n52401\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-admin@thegazettecompany.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-319-398-5820" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "http://thegazette.com/\r", "Standard NOC hours are 8am to 5pm M-F CST" ] + }, { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-03-04" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA158-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA158-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2015-06-23T18:19:23-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2014-01-03T15:10:30-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA159-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "140 Broadway\n46th Floor\nNew York\nNY\n10005\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "platform-engineering@oanda.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-416-593-6767" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-05-26" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA159-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA159-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-05-26T16:11:40-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2014-03-27T15:35:32-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA172-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "7075 Flying Cloud Drive\nEden Prairie\nMN\n55344\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ARIN-Admin@bluestembrands.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-952-656-3700" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA172-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA172-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-14T07:42:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2014-06-24T10:18:54-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA190-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1434 Air Rail Avenue\nVirginia Beach\nVA\n23455\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "tharris@hrsd.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-757-460-7274" ], [ "tel", { + "type" : [ "work", "cell" ] + }, "text", "+1-757-282-1973" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA190-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA190-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-12-20T10:20:52-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2015-04-17T12:58:02-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA208-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "301 E. Montgomery Ave\nArdmore\nPA\n19003\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arinadministrator@lmsd.org" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-610-645-1800" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2021-04-02" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA208-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA208-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2020-04-02T07:22:21-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2016-04-01T08:43:25-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA213-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "12980 Metcalf Avenue, Suite 470\nOverland Park\nKS\n66213\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@mixonhill.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-913-239-8400" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA213-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA213-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-28T13:46:52-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2016-09-30T10:06:51-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA232-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "907 Dowling Road Suite 27\nAnchorage\nAK\n99518\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "horst@borealisbroadband.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-907-563-3278" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA232-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA232-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2017-05-31T17:47:41-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2017-05-31T17:47:41-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA252-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "2 Ravinia Drive, STE 605\nAtlanta\nGA\n30346\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin.admin@nucleuscentral.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-404-233-6446" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "https://www.nucleus-global.com/" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA252-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA252-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-11-08T10:33:11-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2018-11-07T08:57:01-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA268-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "Box 112\nBurton\nBC\nV0G 1E0\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@kootenaybroadband.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-403-560-6502" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA268-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA268-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-05-14T10:17:30-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-02-26T19:19:16-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA272-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1065 Rainer Dr.\nAltamonte Springs\nFL\n32714\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "registrations@zradio.org" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-407-869-8000" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA272-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA272-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-06-01T15:33:00-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-04-29T18:56:55-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA275-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "5701 Lake Wright Drive\nNorfolk\nVA\n23502\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "cma-cgm.arin@usa.cma-cgm.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-757-763-6922" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA275-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA275-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2020-06-23T12:31:22-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2020-06-23T12:31:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA295-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "Box 33\nBurton\nBC\nV0G 1E0\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@burtoninternetsociety.ca" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-403-560-6502" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA295-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA295-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-10-01T17:59:13-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2021-10-01T17:59:13-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA30-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "8951 Cypress Waters Blvd.,\nSuite 160\nDallas\nTX\n75019\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "tom.erwin@kwe.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-214-775-1430" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA30-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA30-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-10-28T09:51:15-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2005-05-18T16:10:22-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA300-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "877 Cedar Street #150\nSanta Cruz\nCA\n95060\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Administrator" ], [ "org", { }, "text", "Arin Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@cruzio.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-831-459-6301" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA300-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA300-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-02-03T14:37:39-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-02-03T18:29:57-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA308-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "12228 White Cedar Dr\nCedar Springs\nMI\n49319\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-admin@rcsol.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-616-275-0556" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA308-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA308-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-07T11:12:32-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-04-07T11:12:32-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA310-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "277 Park Ave\nNew York\nNY\n10172\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "vasant.patel@smbc-cm.com" ], [ "email", { }, "text", "syam@smbc-cm.com" ], [ "email", { }, "text", "krol@smbc-cm.com" ], [ "email", { }, "text", "NYSysInfraNetMgt@smbc-cm.com" ], [ "email", { }, "text", "cucinella@smbc-cm.com" ], [ "email", { }, "text", "arana@smbc-cm.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-212-224-4928" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA310-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA310-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-05-19T12:02:59-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-05-19T12:02:59-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA312-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "2535 Augustine Dr.\nSanta Clara\nCA\n95054\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arinpoc@hitachivantara.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-408-970-7417" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA312-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA312-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-07-13T12:36:47-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-07-13T12:36:47-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA320-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "8201 N FM 620., Suite 100\nAustin\nTX\n78726\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN administrator" ], [ "org", { }, "text", "ARIN administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ARIN@iaawg.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-605-550-0070" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA320-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA320-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-01-14T20:48:10-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2023-01-14T20:48:10-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA53-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "8601 Georgia Avenue\nSuite 800\nSilver Spring\nMD\n20910\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN ADMINISTRATOR" ], [ "org", { }, "text", "ARIN ADMINISTRATOR" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "bblake@chfinternational.org" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-301-587-4700;ext1855" ], [ "tel", { + "type" : [ "work", "cell" ] + }, "text", "+1-301-755-3291" ], [ "tel", { + "type" : [ "work", "fax" ] + }, "text", "+1-301-587-7315" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2017-11-10" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA53-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA53-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2016-11-10T15:09:22-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2007-05-14T11:30:10-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA56-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "2404 Townsgate Road\nWestlake Village\nCA\n91361\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin.admin@pleasant.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-818-991-3390;ext6552" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-12-20" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA56-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA56-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-12-20T09:40:51-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2007-06-19T12:06:07-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA57-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "5401 South Solberg Ave\nSioux Falls\nSD\n57108\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "support@datawareservices.com" ], [ "email", { }, "text", "jlarson@datawareservices.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-605-336-0820" ], [ "tel", { + "type" : [ "work", "fax" ] + }, "text", "+1-605-336-0228" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "http://www.datawareservices.com" ] + }, { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2016-06-22" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA57-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA57-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2015-06-23T15:44:36-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2007-07-17T16:00:28-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA58-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "120 E. Clark St\nFreeport\nIL\n61032\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@aeroinc.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-815-233-3387" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2013-11-09" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA58-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA58-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2011-11-09T17:40:40-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2007-09-01T00:54:46-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA66-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "7795 West Flagler Street\nSte 35\nMiami\nFL\n33144\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "dnsadmin@pcm.com" ], [ "email", { }, "text", "ARINAdmin@tigerdirect.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-305-415-2991;ext2991" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA66-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA66-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2018-07-23T10:22:55-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2008-09-19T13:59:06-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA74-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "135 Bluxome Street\nSan Francisco\nCA\n94107\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "rir-admin@cisco.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-408-525-0560" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA74-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA74-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-09-13T09:08:51-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2009-02-09T12:00:20-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA81-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1500 Seaport Blvd.\nRedwood City\nCA\n94063\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrator" ], [ "org", { }, "text", "ARIN Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ARIN-Admin@facetbiotech.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-650-454-1000" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2010-10-12" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA81-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA81-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2009-10-12T02:06:02-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2009-10-12T02:06:02-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA98-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "11 Broadway\r\nSuite 640\nNew York\nNY\n10004\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Administrator" ], [ "org", { }, "text", "Arin Administrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-admin@thorn.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-212-480-3680" ], [ "tel", { + "type" : [ "work", "fax" ] + }, "text", "+1-212-480-3868" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2019-03-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA98-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA98-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2018-03-15T10:23:39-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2010-11-11T11:48:38-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA259-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "433 Main Street\nGreen bay \nWI\n54301\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrators" ], [ "org", { }, "text", "ARIN Administrators" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ARINadmins@associatedbank.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-920-405-5103" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-09-18" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA259-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA259-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-09-19T14:36:05-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2019-09-19T14:36:05-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA306-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "1325 Boylston Street\nBoston\nMA\n02215\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Administrators" ], [ "org", { }, "text", "ARIN Administrators" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@pathai.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-617-500-8457" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA306-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA306-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-03-17T22:02:11-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-03-17T22:02:11-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA142-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "16027 Ventura Blvd.\r\nSuite 400\nEncino\nCA\n91436\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admins" ], [ "org", { }, "text", "ARIN Admins" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "POC-ARIN@lifealert.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-818-700-7000;ext1108" ], [ "tel", { + "type" : [ "work", "cell" ] + }, "text", "+1-818-388-7671" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA142-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA142-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-02-04T17:11:32-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2012-08-01T12:14:40-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA225-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "2500 Wilcrest Dr\nHouston\nTX\n77042\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Admins" ], [ "org", { }, "text", "ARIN Admins" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "bsanders@nextfinancial.com" ], [ "email", { }, "text", "cmueller@nextfinancial.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-877-876-6398;ext2629" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "Standard NOC hours are 8 AM to 5 PM CST" ] + }, { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-09-16" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA225-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA225-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-09-16T10:45:01-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2017-04-11T12:55:15-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA251-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "11559 Rock Island Court\nMaryland Heights\nMO\n63043\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN admins" ], [ "org", { }, "text", "ARIN admins" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@matthewsbooks.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-314-432-1400" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA251-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA251-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-02-22T10:29:10-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2018-11-05T15:40:26-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINA197-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "2200 Second Street\nFort Myers\nFL\n33901\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Adminstrator" ], [ "org", { }, "text", "ARIN Adminstrator" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arinadministrator@cityftmyers.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-239-321-7111" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-09-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINA197-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINA197-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-09-15T11:28:26-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2015-08-21T10:27:51-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAG10-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "210 Route 4 East\nParamus\nNJ\n07652\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN AS Group" ], [ "org", { }, "text", "ARIN AS Group" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "_APPVRTARINASGroup@vno.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-201-587-1000" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2019-12-20" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAG10-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAG10-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2018-12-20T10:26:40-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2007-12-05T09:36:13-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "AAF6-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "19111 Dallas Parkway\r\nSuite 370\nDallas\nTX\n75287\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN at FEAD" ], [ "org", { }, "text", "ARIN at FEAD" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@lastcallopco.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-214-214-4204" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/AAF6-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/AAF6-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2016-12-19T16:43:31-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2016-12-19T16:43:31-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINB3-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "411 SW 9th St\nRedmond\nOR\n97756\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Billing" ], [ "org", { }, "text", "ARIN Billing" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "itpayables@redmondoregon.gov" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-541-504-3000" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINB3-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINB3-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-12-21T12:03:45-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-12-21T12:03:45-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINB4-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "411 SW 9th St.\nRedmond\nOR\n97756\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Billing" ], [ "org", { }, "text", "ARIN Billing" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "itpayables@redmondoregon.gov" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-541-504-3000" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINB4-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINB4-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-12-21T12:36:36-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2022-12-21T12:36:36-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC11-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "10501 10th Street\nWaconia\nMN\n55387\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Contact" ], [ "org", { }, "text", "ARIN Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "helpdesk@medallioncabinetry.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-952-442-5171;ext1111" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2015-01-03" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC11-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC11-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2014-01-03T11:16:42-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2004-12-22T10:28:37-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC12-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "10501 10th Street\nWaconia\nMN\n55387\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Contact" ], [ "org", { }, "text", "ARIN Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arincontact@medallioncabinetry.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-952-442-5171;ext1111" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2010-07-29" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC12-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC12-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2004-12-22T10:28:37-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2004-12-22T10:28:37-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC17-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "6820 S Harl Ave\nTEMPE\nAZ\n85283\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Contact" ], [ "org", { }, "text", "ARIN Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "itsecurityalerts@insight.com" ], [ "email", { }, "text", "arin-contacts@calence.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-480-889-9600" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC17-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC17-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-03-09T11:33:52-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2005-11-21T21:11:14-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC18-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "23 Main St\nHolmdel\nNJ\n07733\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Contact" ], [ "org", { }, "text", "ARIN Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-contact@vonage.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-877-662-2001" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC18-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC18-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-12-12T08:21:44-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2006-04-19T10:34:46-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC19-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "7218 McNeil Drive\nSuite 300\nAustin\nTX\n78729\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Contact" ], [ "org", { }, "text", "ARIN Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@corenap.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-512-685-0003" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2016-11-06" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC19-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC19-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2015-11-07T07:02:31-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2006-06-30T17:04:05-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC24-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "5454 Beethoven Street\nSuite 200\nLos Angeles\nCA\n90066\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Contact" ], [ "org", { }, "text", "Arin Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-contact@broadcore.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-310-360-1000;ext228" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2022-10-26" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC24-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC24-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-10-26T18:56:50-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2007-01-09T12:30:29-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC27-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "333 W Evelyn Ave\nMountain View\nCA\n94041\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Contact" ], [ "org", { }, "text", "ARIN Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "abuse@friendfeed.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-650-966-1351;extask for ARIN Contact" ], [ "tel", { + "type" : [ "work", "cell" ] + }, "text", "+1-650-534-6445" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2010-07-29" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC27-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC27-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2008-03-12T16:36:13-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2008-03-12T16:36:13-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC29-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "11900 RR 620 N\nAustin\nTX\n78750\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Contact" ], [ "org", { }, "text", "Arin Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-contact@newhometech.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-512-371-3800" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC29-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC29-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-10-04T14:12:43-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2008-05-21T18:00:14-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC3-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "Welsh & McKean Rd\nSpring House\nPA\n19477\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Contact" ], [ "org", { }, "text", "ARIN Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ksanders@advanta.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-215-385-3855" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2010-06-25" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC3-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC3-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2009-04-17T15:32:52-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2002-09-25T10:45:07-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC30-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "4750 Patrick Henry Dr.\nSanta Clara\nCA\n95054\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Contact" ], [ "org", { }, "text", "ARIN Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@ooyala.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-650-961-3400" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "http://www.ooyala.com\r", "Standard NOC hours are 9am to 5pm PST" ] + }, { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-01-22" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC30-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC30-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-01-22T12:06:18-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2008-12-18T20:18:08-05:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC31-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "2709 Sunset Blvd\nSteubenville\nOH\n43952\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Contact" ], [ "org", { }, "text", "ARIN Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@ridgefieldgroup.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-740-264-2641" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "http://www.ridgefieldgroup.com" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC31-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC31-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-07-13T12:26:50-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2009-01-06T12:24:12-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC32-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "4690 Millennium Drive\nBelcamp\nMD\n21017\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Contact" ], [ "org", { }, "text", "ARIN Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "systemsadministration-americas@thalesgroup.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-443-327-1303" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC32-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC32-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2021-04-09T11:50:26-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2009-10-13T01:33:01-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC40-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "4600 Silicon Dr\nDurham\nNC\n27703\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Contact" ], [ "org", { }, "text", "ARIN Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "It_communications@cree.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-910-587-3267" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-919-313-5300;ext5656" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC40-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC40-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-08-31T10:28:04-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-07-09T08:50:45-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC42-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "100 Washington Ave South, 20th Floor\nMinneapolis\nMN\n55401\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Contact" ], [ "org", { }, "text", "ARIN Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-contact@code42.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-844-333-4242" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC42-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC42-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-08T09:10:21-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2013-11-13T12:15:32-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC46-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "2800 Wells Branch Parkway\nAustin\nTX\n78728\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Contact" ], [ "org", { }, "text", "ARIN Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ARIN.Contact@hartehanks.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-888-444-2457" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC46-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC46-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-12-13T10:30:56-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2014-10-14T13:04:44-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC47-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "655 New York Ave, NW\nWashington\nDC\n20001\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Contact" ], [ "org", { }, "text", "ARIN Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin-contact@advisory.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-202-266-5600" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC47-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC47-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-15T07:52:10-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2015-11-25T09:43:28-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC48-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "4000 Legato Road Suite 600\nFairfax\nVA\n22033\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Contact" ], [ "org", { }, "text", "ARIN Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@salientcrgt.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-326-1901" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC48-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC48-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2016-03-21T11:21:53-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2016-03-21T11:21:53-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC5-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "404 E Sunrise Blvd\nFort Lauderdale\nFL\n33304\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Contact" ], [ "org", { }, "text", "ARIN Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@uniquecr.org" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-954-765-4466" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2010-07-15" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC5-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC5-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2003-04-18T17:15:02-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2003-04-18T17:15:02-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC50-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "c/o The Weather Company, an IBM Company\n1001 Summit Blvd\nAtlanta\nGA\n30319\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Contact" ], [ "org", { }, "text", "ARIN Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "chouston@us.ibm.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-770-691-2925" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC50-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC50-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2018-07-17T13:42:53-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2016-07-22T09:01:37-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC52-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "129 Portland St.\r\n5th Floor\nBoston\nMA\n02114\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Arin Contact" ], [ "org", { }, "text", "Arin Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "arin@crunchtime.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-617-567-5228" ] ] ], + "remarks" : [ { + "title" : "Registration Comments", + "description" : [ "http://www.crunchtime.com/" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC52-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC52-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-04-07T07:13:53-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2016-12-28T14:37:43-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC58-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "#500 10220 103 Avenue NW\nEdmonton\nAB\nT5J 0K4\nCanada" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Contact" ], [ "org", { }, "text", "ARIN Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "ArinContact@stantec.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-866-782-6832" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC58-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC58-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2023-02-23T15:25:56-05:00" + }, { + "eventAction" : "registration", + "eventDate" : "2018-02-09T17:04:54-05:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + }, { + "handle" : "ARINC60-ARIN", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "107 N 7th Street\nTonkawa\nOK\n74653\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "ARIN Contact" ], [ "org", { }, "text", "ARIN Contact" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "carichardson@emcompassok.com" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-800-450-1395" ] ] ], + "remarks" : [ { + "title" : "Unvalidated POC", + "description" : [ "ARIN has attempted to validate the data for this POC, but has received no response from the POC since 2020-08-06" ] + } ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARINC60-ARIN" + }, { + "value" : "https://rdap.arin.net/registry/entities?fn=arin*", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARINC60-ARIN" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2019-08-07T10:17:14-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2018-05-02T15:21:37-04:00" + } ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" + } ] +} \ No newline at end of file diff --git a/icann-rdap-common/src/response/test_files/entity_arin_hostmaster.json b/icann-rdap-common/src/response/test_files/entity_arin_hostmaster.json new file mode 100644 index 0000000..b091c63 --- /dev/null +++ b/icann-rdap-common/src/response/test_files/entity_arin_hostmaster.json @@ -0,0 +1,52 @@ +{ + "rdapConformance" : [ "nro_rdap_profile_0", "rdap_level_0" ], + "notices" : [ { + "title" : "Terms of Service", + "description" : [ "By using the ARIN RDAP/Whois service, you are agreeing to the RDAP/Whois Terms of Use" ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entity/arin-hostmaster", + "rel" : "terms-of-service", + "type" : "text/html", + "href" : "https://www.arin.net/resources/registry/whois/tou/" + } ] + }, { + "title" : "Whois Inaccuracy Reporting", + "description" : [ "If you see inaccuracies in the results, please visit: " ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entity/arin-hostmaster", + "rel" : "inaccuracy-report", + "type" : "text/html", + "href" : "https://www.arin.net/resources/registry/whois/inaccuracy_reporting/" + } ] + }, { + "title" : "Copyright Notice", + "description" : [ "Copyright 1997-2023, American Registry for Internet Numbers, Ltd." ] + } ], + "handle" : "ARIN-HOSTMASTER", + "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "adr", { + "label" : "P.O. Box 232290\nCentreville\nVA\n20120\nUnited States" + }, "text", [ "", "", "", "", "", "", "" ] ], [ "fn", { }, "text", "Registration Services Department" ], [ "org", { }, "text", "Registration Services Department" ], [ "kind", { }, "text", "group" ], [ "email", { }, "text", "hostmaster@arin.net" ], [ "tel", { + "type" : [ "work", "voice" ] + }, "text", "+1-703-227-0660" ] ] ], + "links" : [ { + "value" : "https://rdap.arin.net/registry/entity/arin-hostmaster", + "rel" : "self", + "type" : "application/rdap+json", + "href" : "https://rdap.arin.net/registry/entity/ARIN-HOSTMASTER" + }, { + "value" : "https://rdap.arin.net/registry/entity/arin-hostmaster", + "rel" : "alternate", + "type" : "application/xml", + "href" : "https://whois.arin.net/rest/poc/ARIN-HOSTMASTER" + } ], + "events" : [ { + "eventAction" : "last changed", + "eventDate" : "2022-08-02T15:17:32-04:00" + }, { + "eventAction" : "registration", + "eventDate" : "2003-04-30T12:32:56-04:00" + } ], + "status" : [ "validated" ], + "port43" : "whois.arin.net", + "objectClassName" : "entity" +} \ No newline at end of file diff --git a/icann-rdap-common/src/response/test_files/error_ripe_net.json b/icann-rdap-common/src/response/test_files/error_ripe_net.json new file mode 100644 index 0000000..82c1ddc --- /dev/null +++ b/icann-rdap-common/src/response/test_files/error_ripe_net.json @@ -0,0 +1,35 @@ +{ + "description" : [ + "Nameserver not supported" + ], + "errorCode" : 501, + "links" : [ + { + "href" : "http://www.ripe.net/data-tools/support/documentation/terms", + "rel" : "copyright", + "value" : "http://www.ripe.net/data-tools/support/documentation/terms" + } + ], + "notices" : [ + { + "description" : [ + "This is the RIPE Database query service. The objects are in RDAP format." + ], + "links" : [ + { + "href" : "http://www.ripe.net/db/support/db-terms-conditions.pdf", + "rel" : "terms-of-service", + "type" : "application/pdf" + } + ], + "title" : "Terms and Conditions" + } + ], + "port43" : "whois.ripe.net", + "rdapConformance" : [ + "cidr0", + "rdap_level_0", + "nro_rdap_profile_0" + ], + "title" : "501 Not Implemented" +} diff --git a/icann-rdap-common/src/response/test_files/help_nic_fr.json b/icann-rdap-common/src/response/test_files/help_nic_fr.json new file mode 100644 index 0000000..889a9b4 --- /dev/null +++ b/icann-rdap-common/src/response/test_files/help_nic_fr.json @@ -0,0 +1,52 @@ +{ + "notices" : [ + { + "description" : [ + "domain/XXXX", + "domains?name=XXXX", + "entity/XXXX", + "entities?fn=XXXX", + "nameserver/XXXX", + "nameservers?ip=XXXX", + "help" + ], + "links" : [ + { + "href" : "https://rdap.nic.fr/help", + "media" : "application/json", + "rel" : "self", + "value" : "https://rdap.nic.fr/help" + } + ], + "title" : "RDAP queries can be made on the following types" + }, + { + "description" : [ + "(1) DOMAIN : Domain queries are made based only on the domain name criterion.", + " Example: domain/example.tld", + "(2) ENTITY : Registrar queries are made based only on the registrar gurid criterion (IANA ID).", + " Example: entity/9995", + " Registrar searches are made based only on the registrar name criterion.", + " Example: entities?fn=Registry%20Testing%201 for the registrar with name 'Registry Testing 1'", + "(3) NAMESERVER : Nameserver queries are made based only on the nameserver name criterion.", + " Example: nameserver/ns1.example.tld", + " Nameserver searches are made based only on the IP address criterion.", + " Example: nameservers?ip=198.51.100.0" + ], + "links" : [ + { + "href" : "https://rdap.nic.fr/help", + "media" : "application/json", + "rel" : "self", + "value" : "https://rdap.nic.fr/help" + } + ], + "title" : "USE" + } + ], + "rdapConformance" : [ + "rdap_level_0", + "icann_rdap_technical_implementation_guide_0", + "icann_rdap_response_profile_0" + ] +} diff --git a/icann-rdap-common/src/response/test_files/lookup_with_redaction.json b/icann-rdap-common/src/response/test_files/lookup_with_redaction.json new file mode 100644 index 0000000..2408415 --- /dev/null +++ b/icann-rdap-common/src/response/test_files/lookup_with_redaction.json @@ -0,0 +1,393 @@ +{ + "rdapConformance": [ + "rdap_level_0", + "redacted" + ], + "objectClassName": "domain", + "ldhName": "example.com", + "secureDNS": { + "delegationSigned": false + }, + "notices": [ + { + "title": "Terms of Use", + "description": [ + "Service subject to Terms of Use." + ], + "links": [ + { + "rel": "self", + "href": "https://www.example.com/terms-of-use", + "type": "text/html", + "value": "https://www.example.com/terms-of-use" + } + ] + } + ], + "nameservers": [ + { + "objectClassName": "nameserver", + "ldhName": "ns1.example.com" + }, + { + "objectClassName": "nameserver", + "ldhName": "ns2.example.com" + } + ], + "entities": [ + { + "objectClassName": "entity", + "handle": "123", + "roles": [ + "registrar" + ], + "publicIds": [ + { + "type": "IANA Registrar ID", + "identifier": "1" + } + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Example Registrar Inc." + ], + [ + "adr", + {}, + "text", + [ + "", + "Suite 100", + "123 Example Dr.", + "Dulles", + "VA", + "20166-6503", + "US" + ] + ], + [ + "email", + {}, + "text", + "contact@organization.example" + ], + [ + "tel", + { + "type": "voice" + }, + "uri", + "tel:+1.7035555555" + ], + [ + "tel", + { + "type": "fax" + }, + "uri", + "tel:+1.7035555556" + ] + ] + ], + "entities": [ + { + "objectClassName": "entity", + "roles": [ + "abuse" + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Abuse Contact" + ], + [ + "email", + {}, + "text", + "abuse@organization.example" + ], + [ + "tel", + { + "type": "voice" + }, + "uri", + "tel:+1.7035555555" + ] + ] + ] + } + ] + }, + { + "objectClassName": "entity", + "handle": "XXXX", + "roles": [ + "registrant" + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + "", + "", + "QC", + "", + "Canada" + ] + ] + ] + ] + }, + { + "objectClassName": "entity", + "handle": "YYYY", + "roles": [ + "technical" + ], + "vcardArray": [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "" + ], + [ + "org", + {}, + "text", + "Example Inc." + ], + [ + "adr", + {}, + "text", + [ + "", + "Suite 1234", + "4321 Rue Somewhere", + "Quebec", + "QC", + "G1V 2M2", + "Canada" + ] + ] + ] + ] + } + ], + "events": [ + { + "eventAction": "registration", + "eventDate": "1997-06-03T00:00:00Z" + }, + { + "eventAction": "last changed", + "eventDate": "2020-05-28T01:35:00Z" + }, + { + "eventAction": "expiration", + "eventDate": "2021-06-03T04:00:00Z" + } + ], + "status": [ + "server delete prohibited", + "server update prohibited", + "server transfer prohibited", + "client transfer prohibited" + ], + "redacted": [ + { + "name": { + "description": "Registry Domain ID" + }, + "prePath": "$.handle", + "pathLang": "jsonpath", + "method": "removal", + "reason": { + "description": "Server policy" + } + }, + { + "name": { + "description": "Registrant Name" + }, + "postPath": "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='fn')][3]", + "pathLang": "jsonpath", + "method": "emptyValue", + "reason": { + "description": "Server policy" + } + }, + { + "name": { + "description": "Registrant Organization" + }, + "prePath": "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='org')]", + "pathLang": "jsonpath", + "method": "removal", + "reason": { + "description": "Server policy" + } + }, + { + "name": { + "description": "Registrant Street" + }, + "postPath": "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='adr')][3][:3]", + "pathLang": "jsonpath", + "method": "emptyValue", + "reason": { + "description": "Server policy" + } + }, + { + "name": { + "description": "Registrant City" + }, + "postPath": "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='adr')][3][3]", + "pathLang": "jsonpath", + "method": "emptyValue", + "reason": { + "description": "Server policy" + } + }, + { + "name": { + "description": "Registrant Postal Code" + }, + "postPath": "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='adr')][3][5]", + "pathLang": "jsonpath", + "method": "emptyValue", + "reason": { + "description": "Server policy" + } + }, + { + "name": { + "description": "Registrant Email" + }, + "prePath": "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='email')]", + "method": "removal", + "reason": { + "description": "Server policy" + } + }, + { + "name": { + "description": "Registrant Phone" + }, + "prePath": "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[1].type=='voice')]", + "method": "removal", + "reason": { + "description": "Server policy" + } + }, + { + "name": { + "description": "Technical Name" + }, + "postPath": "$.entities[?(@.roles[0]=='technical')].vcardArray[1][?(@[0]=='fn')][3]", + "method": "emptyValue", + "reason": { + "description": "Server policy" + } + }, + { + "name": { + "description": "Technical Email" + }, + "prePath": "$.entities[?(@.roles[0]=='technical')].vcardArray[1][?(@[0]=='email')]", + "method": "removal", + "reason": { + "description": "Server policy" + } + }, + { + "name": { + "description": "Technical Phone" + }, + "prePath": "$.entities[?(@.roles[0]=='technical')].vcardArray[1][?(@[1].type=='voice')]", + "method": "removal", + "reason": { + "description": "Server policy" + } + }, + { + "name": { + "description": "Technical Fax" + }, + "prePath": "$.entities[?(@.roles[0]=='technical')].vcardArray[1][?(@[1].type=='fax')]", + "reason": { + "description": "Client request" + } + }, + { + "name": { + "description": "Administrative Contact" + }, + "prePath": "$.entities[?(@.roles[0]=='administrative')]", + "method": "removal", + "reason": { + "description": "Refer to the technical contact" + } + }, + { + "name": { + "description": "Billing Contact" + }, + "prePath": "$.entities[?(@.roles[0]=='billing')]", + "method": "removal", + "reason": { + "description": "Refer to the registrant contact" + } + } + ] +} \ No newline at end of file diff --git a/icann-rdap-common/src/response/test_files/nameserver_ns1_nic_fr.json b/icann-rdap-common/src/response/test_files/nameserver_ns1_nic_fr.json new file mode 100644 index 0000000..7301e8e --- /dev/null +++ b/icann-rdap-common/src/response/test_files/nameserver_ns1_nic_fr.json @@ -0,0 +1,146 @@ +{ + "entities" : [ + { + "events" : [ + { + "eventAction" : "registration", + "eventDate" : "2014-07-29T15:35:33Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "2022-10-12T18:54:01.785057Z" + } + ], + "handle" : "RAR939-FRNIC", + "links" : [ + { + "href" : "https://rdap.nic.fr/entity/RAR939-FRNIC", + "rel" : "self", + "value" : "https://rdap.nic.fr/entity/RAR939-FRNIC" + } + ], + "objectClassName" : "entity", + "port43" : "whois.nic.fr", + "publicIds" : [ + { + "identifier" : "9999", + "type" : "IANA Registrar ID" + } + ], + "remarks" : [ + { + "description" : [ + "No" + ], + "type" : "registrar restricted publication" + } + ], + "roles" : [ + "sponsor", + "registrar" + ], + "status" : [ + "active" + ], + "vcardArray" : [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Registry Operations" + ], + [ + "tel", + {}, + "text", + "+33.139308300" + ], + [ + "tel", + { + "type" : "fax" + }, + "text", + "+33.139308301" + ], + [ + "email", + {}, + "text", + "support@afnic.fr" + ], + [ + "url", + {}, + "uri", + "https://www.afnic.fr" + ], + [ + "adr", + {}, + "text", + [ + "", + "", + [ + "AFNIC", + "immeuble le Stephenson", + "1, rue Stephenson" + ], + "Montigny-Le-Bretonneux", + "", + "78180", + "FR" + ] + ] + ] + ] + } + ], + "handle" : "HOST05-FRNIC", + "ipAddresses" : { + "v4" : [ + "192.134.4.1" + ], + "v6" : [ + "2001:67c:2218:2::4:1" + ] + }, + "ldhName" : "ns1.nic.fr", + "links" : [ + { + "href" : "https://rdap.nic.fr/nameserver/ns1.nic.fr", + "rel" : "self", + "value" : "https://rdap.nic.fr/nameserver/ns1.nic.fr" + } + ], + "objectClassName" : "nameserver", + "port43" : "whois.nic.fr", + "rdapConformance" : [ + "rdap_level_0", + "icann_rdap_technical_implementation_guide_0", + "icann_rdap_response_profile_0" + ], + "remarks" : [ + { + "description" : [ + "The list of results does not contain all results due to lack of authorization.", + "This may indicate to some clients that proper authorization will yield a longer result set." + ], + "title" : "result set truncated due to authorization" + } + ], + "status" : [ + "server update prohibited", + "server delete prohibited", + "associated" + ] +} diff --git a/icann-rdap-common/src/response/test_files/network_192_198_0_0.json b/icann-rdap-common/src/response/test_files/network_192_198_0_0.json new file mode 100644 index 0000000..03ebb92 --- /dev/null +++ b/icann-rdap-common/src/response/test_files/network_192_198_0_0.json @@ -0,0 +1,384 @@ +{ + "arin_originas0_originautnums" : [ + 53301 + ], + "cidr0_cidrs" : [ + { + "length" : 22, + "v4prefix" : "192.198.0.0" + } + ], + "endAddress" : "192.198.3.255", + "entities" : [ + { + "entities" : [ + { + "events" : [ + { + "eventAction" : "last changed", + "eventDate" : "2022-02-07T09:20:07-05:00" + }, + { + "eventAction" : "registration", + "eventDate" : "2011-10-29T11:02:59-04:00" + } + ], + "handle" : "PETSI-ARIN", + "links" : [ + { + "href" : "https://rdap.arin.net/registry/entity/PETSI-ARIN", + "rel" : "self", + "type" : "application/rdap+json", + "value" : "https://rdap.arin.net/registry/ip/192.198.0.0" + }, + { + "href" : "https://whois.arin.net/rest/poc/PETSI-ARIN", + "rel" : "alternate", + "type" : "application/xml", + "value" : "https://rdap.arin.net/registry/ip/192.198.0.0" + } + ], + "objectClassName" : "entity", + "port43" : "whois.arin.net", + "roles" : [ + "noc", + "technical", + "administrative", + "abuse" + ], + "status" : [ + "validated" + ], + "vcardArray" : [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "adr", + { + "label" : "8162 Sw 81st ST\nEllendale\nMN\n56026\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "fn", + {}, + "text", + "Daniel Petsinger" + ], + [ + "n", + {}, + "text", + [ + "Petsinger", + "Daniel", + "", + "", + "" + ] + ], + [ + "kind", + {}, + "text", + "individual" + ], + [ + "email", + {}, + "text", + "daniel.petsinger@radiolinkinternet.com" + ], + [ + "tel", + { + "type" : [ + "work", + "voice" + ] + }, + "text", + "+1-507-417-4176" + ] + ] + ] + } + ], + "events" : [ + { + "eventAction" : "last changed", + "eventDate" : "2017-01-28T08:32:29-05:00" + }, + { + "eventAction" : "registration", + "eventDate" : "2011-11-02T07:36:05-04:00" + } + ], + "handle" : "DP-41", + "links" : [ + { + "href" : "https://rdap.arin.net/registry/entity/DP-41", + "rel" : "self", + "type" : "application/rdap+json", + "value" : "https://rdap.arin.net/registry/ip/192.198.0.0" + }, + { + "href" : "https://whois.arin.net/rest/org/DP-41", + "rel" : "alternate", + "type" : "application/xml", + "value" : "https://rdap.arin.net/registry/ip/192.198.0.0" + } + ], + "objectClassName" : "entity", + "port43" : "whois.arin.net", + "remarks" : [ + { + "description" : [ + "http://www.radiolinkinternet.com", + "Standard NOC hours are 9am to 5pm CST" + ], + "title" : "Registration Comments" + } + ], + "roles" : [ + "registrant" + ], + "vcardArray" : [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "fn", + {}, + "text", + "Radio Link Internet" + ], + [ + "adr", + { + "label" : "8162 SW 81st St\nEllendale\nMN\n56026\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "kind", + {}, + "text", + "org" + ] + ] + ] + }, + { + "events" : [ + { + "eventAction" : "last changed", + "eventDate" : "2022-02-07T09:20:07-05:00" + }, + { + "eventAction" : "registration", + "eventDate" : "2011-10-29T11:02:59-04:00" + } + ], + "handle" : "PETSI-ARIN", + "links" : [ + { + "href" : "https://rdap.arin.net/registry/entity/PETSI-ARIN", + "rel" : "self", + "type" : "application/rdap+json", + "value" : "https://rdap.arin.net/registry/ip/192.198.0.0" + }, + { + "href" : "https://whois.arin.net/rest/poc/PETSI-ARIN", + "rel" : "alternate", + "type" : "application/xml", + "value" : "https://rdap.arin.net/registry/ip/192.198.0.0" + } + ], + "objectClassName" : "entity", + "port43" : "whois.arin.net", + "roles" : [ + "noc", + "abuse", + "technical" + ], + "status" : [ + "validated" + ], + "vcardArray" : [ + "vcard", + [ + [ + "version", + {}, + "text", + "4.0" + ], + [ + "adr", + { + "label" : "8162 Sw 81st ST\nEllendale\nMN\n56026\nUnited States" + }, + "text", + [ + "", + "", + "", + "", + "", + "", + "" + ] + ], + [ + "fn", + {}, + "text", + "Daniel Petsinger" + ], + [ + "n", + {}, + "text", + [ + "Petsinger", + "Daniel", + "", + "", + "" + ] + ], + [ + "kind", + {}, + "text", + "individual" + ], + [ + "email", + {}, + "text", + "daniel.petsinger@radiolinkinternet.com" + ], + [ + "tel", + { + "type" : [ + "work", + "voice" + ] + }, + "text", + "+1-507-417-4176" + ] + ] + ] + } + ], + "events" : [ + { + "eventAction" : "last changed", + "eventDate" : "2013-03-19T09:14:03-04:00" + }, + { + "eventAction" : "registration", + "eventDate" : "2013-01-31T18:11:17-05:00" + } + ], + "handle" : "NET-192-198-0-0-1", + "ipVersion" : "v4", + "links" : [ + { + "href" : "https://rdap.arin.net/registry/ip/192.198.0.0", + "rel" : "self", + "type" : "application/rdap+json", + "value" : "https://rdap.arin.net/registry/ip/192.198.0.0" + }, + { + "href" : "https://whois.arin.net/rest/net/NET-192-198-0-0-1", + "rel" : "alternate", + "type" : "application/xml", + "value" : "https://rdap.arin.net/registry/ip/192.198.0.0" + } + ], + "name" : "RADIOLINK-ARIN-1", + "notices" : [ + { + "description" : [ + "By using the ARIN RDAP/Whois service, you are agreeing to the RDAP/Whois Terms of Use" + ], + "links" : [ + { + "href" : "https://www.arin.net/resources/registry/whois/tou/", + "rel" : "terms-of-service", + "type" : "text/html", + "value" : "https://rdap.arin.net/registry/ip/192.198.0.0" + } + ], + "title" : "Terms of Service" + }, + { + "description" : [ + "If you see inaccuracies in the results, please visit: " + ], + "links" : [ + { + "href" : "https://www.arin.net/resources/registry/whois/inaccuracy_reporting/", + "rel" : "inaccuracy-report", + "type" : "text/html", + "value" : "https://rdap.arin.net/registry/ip/192.198.0.0" + } + ], + "title" : "Whois Inaccuracy Reporting" + }, + { + "description" : [ + "Copyright 1997-2023, American Registry for Internet Numbers, Ltd." + ], + "title" : "Copyright Notice" + } + ], + "objectClassName" : "ip network", + "parentHandle" : "NET-192-0-0-0-0", + "port43" : "whois.arin.net", + "rdapConformance" : [ + "nro_rdap_profile_0", + "rdap_level_0", + "cidr0", + "arin_originas0" + ], + "startAddress" : "192.198.0.0", + "status" : [ + "active" + ], + "type" : "DIRECT ALLOCATION" +} diff --git a/icann-rdap-common/src/response/types.rs b/icann-rdap-common/src/response/types.rs new file mode 100644 index 0000000..c8adf11 --- /dev/null +++ b/icann-rdap-common/src/response/types.rs @@ -0,0 +1,1022 @@ +//! Common data structures, etc... +use { + serde::{Deserialize, Serialize}, + strum_macros::{AsRefStr, Display, EnumString}, +}; + +use super::lenient::VectorStringish; + +/// Represents an RDAP extension identifier. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Extension(pub String); + +impl From<&str> for Extension { + fn from(value: &str) -> Self { + Self(value.to_string()) + } +} + +impl std::ops::Deref for Extension { + type Target = String; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// The RDAP conformance array. +/// +/// This is a vec of [Extension] specifically to be able to handle one or more +/// unknown extension ids. Known extension identifiers are enumerated by [ExtensionId]. +pub type RdapConformance = Vec; + +/// Extension Identifiers +/// +/// This enum uses [EnumString] and [AsRefStr] to allow serialization +/// and deserialization of the variant to the matching name in the IANA registry. +/// +/// To get the variant from a string: +/// +/// ```rust +/// use std::str::FromStr; +/// use icann_rdap_common::prelude::*; +/// +/// let cidr0 = ExtensionId::from_str("cidr0").unwrap(); +/// assert_eq!(cidr0, ExtensionId::Cidr0); +/// println!("{}", cidr0.to_string()); +/// ``` +/// +/// To get the variants as a string: +/// +/// ```rust +/// use icann_rdap_common::prelude::*; +/// +/// let s = ExtensionId::Cidr0.to_string(); +/// ``` +/// +/// To get the variants as a &str: +/// +/// ```rust +/// use icann_rdap_common::prelude::*; +/// +/// let s = ExtensionId::Cidr0.as_ref(); +/// ``` +#[derive(Serialize, Deserialize, EnumString, Display, Debug, PartialEq, Eq, AsRefStr)] +pub enum ExtensionId { + #[strum(serialize = "rdap_level_0")] + RdapLevel0, + #[strum(serialize = "arin_originas0")] + ArinOriginAs0, + #[strum(serialize = "artRecord")] + ArtRecord, + #[strum(serialize = "cidr0")] + Cidr0, + #[strum(serialize = "farv1")] + Farv1, + #[strum(serialize = "fred")] + Fred, + #[strum(serialize = "icann_rdap_response_profile_0")] + IcannRdapResponseProfile0, + #[strum(serialize = "icann_rdap_response_profile_1")] + IcannRdapResponseProfile1, + #[strum(serialize = "icann_rdap_technical_implementation_guide_0")] + IcannRdapTechnicalImplementationGuide0, + #[strum(serialize = "icann_rdap_technical_implementation_guide_1")] + IcannRdapTechnicalImplementationGuide1, + #[strum(serialize = "nro_rdap_profile_0")] + NroRdapProfile0, + #[strum(serialize = "nro_rdap_profile_asn_flat_0")] + NroRdapProfileAsnFlat0, + #[strum(serialize = "nro_rdap_profile_asn_hierarchical_0")] + NroRdapProfileAsnHierarchical0, + #[strum(serialize = "paging")] + Paging, + #[strum(serialize = "platformNS")] + PlatformNs, + #[strum(serialize = "rdap_objectTag")] + RdapObjectTag, + #[strum(serialize = "redacted")] + Redacted, + #[strum(serialize = "redirect_with_content")] + RedirectWithContent, + #[strum(serialize = "regType")] + RegType, + #[strum(serialize = "reverse_search")] + ReverseSearch, + #[strum(serialize = "sorting")] + Sorting, + #[strum(serialize = "subsetting")] + Subsetting, +} + +impl ExtensionId { + /// Gets an [Extension] from an Extension ID. + pub fn to_extension(&self) -> Extension { + Extension(self.to_string()) + } +} + +/// HrefLang, either a string or an array of strings. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(untagged)] +pub enum HrefLang { + Langs(Vec), + Lang(String), +} + +/// An array of RDAP link structures. +pub type Links = Vec; + +/// Represents and RDAP link structure. +/// +/// This structure allows `value`, `rel`, and `href` to be +/// optional to be tolerant of misbehaving servers, +/// but those are fields required by RFC 9083. +/// +/// To create an RFC valid structure, use the builder +/// which will not allow omision of required fields. +/// +/// ```rust +/// use icann_rdap_common::prelude::*; +/// +/// let link = Link::builder() +/// .value("https://example.com/domains?domain=foo.*") +/// .rel("related") +/// .href("https://example.com/domain/foo.example") +/// .hreflang("ch") +/// .title("Related Object") +/// .media("print") +/// .media_type("application/rdap+json") +/// .build(); +/// ``` +/// +/// Note also that this structure allows for `hreflang` to +/// be either a single string or an array of strings. However, +/// the builder will always construct an array of strings. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Link { + /// Represents the value part of a link in an RDAP response. + /// According to RFC 9083, this field is required + /// but many servers do not return it as it was + /// optional in RFC 7483. + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option, + + /// Represents the relationship of a link in an RDAP response. + /// According to RFC 9083, this field is required + /// but many servers do not return it as it was + /// optional in RFC 7483. + #[serde(skip_serializing_if = "Option::is_none")] + pub rel: Option, + + /// This is required by RDAP, both RFC 7043 and 9083, + /// but is optional because some servers do the wrong thing. + #[serde(skip_serializing_if = "Option::is_none")] + pub href: Option, + + /// This can either be a string or an array of strings. + #[serde(skip_serializing_if = "Option::is_none")] + pub hreflang: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub media: Option, + + #[serde(rename = "type")] + #[serde(skip_serializing_if = "Option::is_none")] + pub media_type: Option, +} + +#[buildstructor::buildstructor] +impl Link { + /// True if the link `rel` property is equal to the given value. + pub fn is_relation(&self, rel: &str) -> bool { + let Some(link_rel) = &self.rel else { + return false; + }; + link_rel == rel + } + + /// Builds an RDAP link. + #[builder(visibility = "pub")] + fn new( + value: String, + href: String, + rel: String, + hreflang: Option, + title: Option, + media: Option, + media_type: Option, + ) -> Self { + let hreflang = hreflang.map(HrefLang::Lang); + Self { + value: Some(value), + rel: Some(rel), + href: Some(href), + hreflang, + title, + media, + media_type, + } + } + + /// Builds a potentially illegal RDAP link. + #[builder(entry = "illegal", visibility = "pub(crate)")] + #[allow(dead_code)] + fn new_illegal( + value: Option, + href: Option, + rel: Option, + hreflang: Option, + title: Option, + media: Option, + media_type: Option, + ) -> Self { + let hreflang = hreflang.map(HrefLang::Lang); + Link { + value, + rel, + href, + hreflang, + title, + media, + media_type, + } + } + + /// Returns the value of the link. + pub fn value(&self) -> Option<&str> { + self.value.as_deref() + } + + /// Returns the relationship of the link. + pub fn rel(&self) -> Option<&str> { + self.rel.as_deref() + } + + /// Returns the target URL of the link. + pub fn href(&self) -> Option<&str> { + self.href.as_deref() + } + + /// Returns the language(s) of the linked resource. + pub fn hreflang(&self) -> Option<&HrefLang> { + self.hreflang.as_ref() + } + + /// Returns the title of the link. + pub fn title(&self) -> Option<&str> { + self.title.as_deref() + } + + /// Returns the media type for which the link is designed. + pub fn media(&self) -> Option<&str> { + self.media.as_deref() + } + + /// Returns the media type of the linked resource. + pub fn media_type(&self) -> Option<&str> { + self.media_type.as_deref() + } +} + +/// An array of notices. +pub type Notices = Vec; + +/// Represents an RDAP Notice. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Notice(pub NoticeOrRemark); + +impl std::ops::Deref for Notice { + type Target = NoticeOrRemark; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// An array of remarks. +pub type Remarks = Vec; + +/// Represents an RDAP Remark. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Remark(pub NoticeOrRemark); + +impl std::ops::Deref for Remark { + type Target = NoticeOrRemark; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// Represents an RDAP Notice or Remark (they are the same thing in RDAP). +/// +/// RFC 9083 requires that `description` be required, but some servers +/// do not follow this rule. Therefore, this structure allows `description` +/// to be optional. It is recommended to use builder to construct an RFC valid +/// structure. +/// +/// ```rust +/// use icann_rdap_common::prelude::*; +/// +/// let link = Link::builder() +/// .value("https://example.com/domains/foo.example") +/// .rel("about") +/// .href("https://example.com/tou.html") +/// .hreflang("en") +/// .title("ToU Link") +/// .media_type("text/html") +/// .build(); +/// +/// let nr = NoticeOrRemark::builder() +/// .title("Terms of Use") +/// .description_entry("Please read our terms of use.") +/// .description_entry("TOS can be found in the link.") +/// .links(vec![link]) +/// .build(); +/// ``` +/// +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct NoticeOrRemark { + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub links: Option, + + /// Description `type` as is found in the IANA registry. + #[serde(rename = "type")] + #[serde(skip_serializing_if = "Option::is_none")] + pub nr_type: Option, +} + +#[buildstructor::buildstructor] +impl NoticeOrRemark { + /// Builds an RDAP notice/remark. + #[builder(visibility = "pub")] + fn new( + title: Option, + description: Vec, + links: Vec, + nr_type: Option, + ) -> Self { + NoticeOrRemark { + title, + description: Some(VectorStringish::from(description)), + links: (!links.is_empty()).then_some(links), + nr_type, + } + } + + /// Builds an illegal RDAP notice/remark. + #[builder(entry = "illegal", visibility = "pub(crate)")] + #[allow(dead_code)] + fn new_illegal( + title: Option, + description: Option>, + links: Option>, + nr_type: Option, + ) -> Self { + let d = description + .is_some() + .then_some(VectorStringish::from(description.unwrap())); + NoticeOrRemark { + title, + description: d, + links, + nr_type, + } + } + + /// Converts to a [Notice]. + pub fn notice(self) -> Notice { + Notice(self) + } + + /// Converts to a [Remark]. + pub fn remark(self) -> Remark { + Remark(self) + } + + /// Returns the title of the notice/remark. + pub fn title(&self) -> Option<&str> { + self.title.as_deref() + } + + /// Returns the description of the notice/remark. + pub fn description(&self) -> Option<&VectorStringish> { + self.description.as_ref() + } + + /// Returns the links associated with the notice/remark. + pub fn links(&self) -> Option<&Links> { + self.links.as_ref() + } + + /// Returns the `type` of the notice or remark. + /// + /// These values are suppose to come from the IANA RDAP registry. + pub fn nr_type(&self) -> Option<&str> { + self.nr_type.as_deref() + } +} + +/// Conversion for collection of notices. +pub trait ToNotices { + /// Convert to a collection of notices. + fn to_notices(self) -> Vec; + /// Convert to a collection if some, otherwise none. + fn to_opt_notices(self) -> Option>; +} + +impl ToNotices for &[NoticeOrRemark] { + fn to_notices(self) -> Vec { + self.iter().map(|n| Notice(n.clone())).collect::() + } + + fn to_opt_notices(self) -> Option> { + let notices = self.to_notices(); + (!notices.is_empty()).then_some(notices) + } +} + +impl ToNotices for Vec { + fn to_notices(self) -> Vec { + self.into_iter().map(Notice).collect::() + } + + fn to_opt_notices(self) -> Option> { + let notices = self.to_notices(); + (!notices.is_empty()).then_some(notices) + } +} + +/// Conversion for collection of remarks. +pub trait ToRemarks { + /// Convert to a collection of remarks. + fn to_remarks(self) -> Vec; + /// Convert to a collection if some, otherwise none. + fn to_opt_remarks(self) -> Option>; +} + +impl ToRemarks for &[NoticeOrRemark] { + fn to_remarks(self) -> Vec { + self.iter().map(|n| Remark(n.clone())).collect::() + } + + fn to_opt_remarks(self) -> Option> { + let remarks = self.to_remarks(); + (!remarks.is_empty()).then_some(remarks) + } +} + +impl ToRemarks for Vec { + fn to_remarks(self) -> Vec { + self.into_iter().map(Remark).collect::() + } + + fn to_opt_remarks(self) -> Option> { + let remarks = self.to_remarks(); + (!remarks.is_empty()).then_some(remarks) + } +} + +/// An array of events. +pub type Events = Vec; + +/// Represents an RDAP event. +/// +/// RFC 9083 requires `eventAction` (event_action) and `eventDate` (event_date), but +/// this structure allows those to be optional to be able to parse responses from +/// servers that do not strictly obey the RFC. +/// +/// Use of the builder to contruct an RFC valid structure is recommended. +/// +/// ```rust +/// use icann_rdap_common::prelude::*; +/// +/// let link = Link::builder() +/// .value("https://example.com/domains/foo.example") +/// .rel("about") +/// .href("https://example.com/registration-duration.html") +/// .hreflang("en") +/// .title("Domain Validity Period") +/// .media_type("text/html") +/// .build(); +/// +/// let nr = Event::builder() +/// .event_action("expiration") +/// .event_date("1990-12-31T23:59:59Z") +/// .links(vec![link]) +/// .build(); +/// ``` +/// +/// NOTE: `event_date` is to be an RFC 3339 valid date and time. +/// The builder does not enforce RFC 3339 validity. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Event { + /// This value is required by RFC 9083 (and 7483), + /// however some servers don't include it. Therefore + /// it is optional here to be compatible with these + /// types of non-compliant servers. + #[serde(rename = "eventAction")] + #[serde(skip_serializing_if = "Option::is_none")] + pub event_action: Option, + + #[serde(rename = "eventActor")] + #[serde(skip_serializing_if = "Option::is_none")] + pub event_actor: Option, + + /// This value is required by RFC 9083 (and 7483), + /// however some servers don't include it. Therefore + /// it is optional here to be compatible with these + /// types of non-compliant servers. + #[serde(rename = "eventDate")] + #[serde(skip_serializing_if = "Option::is_none")] + pub event_date: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub links: Option, +} + +#[buildstructor::buildstructor] +impl Event { + /// Builds an Event. + #[builder(visibility = "pub")] + fn new( + event_action: String, + event_date: String, + event_actor: Option, + links: Option, + ) -> Self { + Self { + event_action: Some(event_action), + event_actor, + event_date: Some(event_date), + links, + } + } + + #[builder(entry = "illegal", visibility = "pub(crate)")] + #[allow(dead_code)] + fn new_illegal( + event_action: Option, + event_date: Option, + event_actor: Option, + links: Option, + ) -> Self { + Event { + event_action, + event_actor, + event_date, + links, + } + } + + /// Returns the action associated with the event. + pub fn event_action(&self) -> Option<&str> { + self.event_action.as_deref() + } + + /// Returns the actor associated with the event. + pub fn event_actor(&self) -> Option<&str> { + self.event_actor.as_deref() + } + + /// Returns the date and time of the event. + pub fn event_date(&self) -> Option<&str> { + self.event_date.as_deref() + } + + /// Returns the links associated with the event. + pub fn links(&self) -> Option<&Links> { + self.links.as_ref() + } +} + +/// An RDAP port53 type. +pub type Port43 = String; + +/// An array of RDAP public IDs. +pub type PublicIds = Vec; + +/// An RDAP Public ID. +/// +/// RFC 9083 requires `type` (id_type) and `identifier`, but +/// this structure allows those to be optional to be able to parse responses from +/// servers that do not strictly obey the RFC. +/// +/// Use of the builder to contruct an RFC valid structure is recommended. +/// +/// ```rust +/// use icann_rdap_common::prelude::*; +/// +/// let public_id = PublicId::builder() +/// .id_type("IANA Registrar ID") +/// .identifier("1990") +/// .build(); +/// ``` +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct PublicId { + /// This are manditory per RFC 9083. + #[serde(rename = "type")] + #[serde(skip_serializing_if = "Option::is_none")] + pub id_type: Option, + + /// This are manditory per RFC 9083. + #[serde(skip_serializing_if = "Option::is_none")] + pub identifier: Option, +} + +#[buildstructor::buildstructor] +impl PublicId { + /// Builds a public ID. + #[builder(visibility = "pub")] + fn new(id_type: String, identifier: String) -> Self { + PublicId { + id_type: Some(id_type), + identifier: Some(identifier), + } + } + + /// Builds an illegal public ID. + #[builder(entry = "illegal", visibility = "pub(crate)")] + #[allow(dead_code)] + fn new_illegal(id_type: Option, identifier: Option) -> Self { + PublicId { + id_type, + identifier, + } + } + + /// Returns the type of the public ID. + pub fn id_type(&self) -> Option<&str> { + self.id_type.as_deref() + } + + /// Returns the identifier of the public ID. + pub fn identifier(&self) -> Option<&str> { + self.identifier.as_deref() + } +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use crate::{ + prelude::ObjectCommon, + response::types::{Extension, Notice, Notices, RdapConformance, Remark, Remarks}, + }; + + use super::{Event, Link, Links, NoticeOrRemark, PublicId}; + + #[test] + fn GIVEN_rdap_conformance_WHEN_serialize_THEN_array_of_strings() { + // GIVEN + let rdap_conformance: RdapConformance = + vec![Extension("foo".to_string()), Extension("bar".to_string())]; + + // WHEN + let actual = serde_json::to_string(&rdap_conformance).unwrap(); + + // THEN + let expected = r#"["foo","bar"]"#; + assert_eq!(actual, expected); + } + + #[test] + fn GIVEN_an_array_of_links_WHEN_deserialize_THEN_success() { + // GIVEN + let expected = r#" + [ + { + "value" : "https://1.example.com/context_uri", + "rel" : "self", + "href" : "https://1.example.com/target_uri", + "hreflang" : [ "en", "ch" ], + "title" : "title1", + "media" : "screen", + "type" : "application/json" + }, + { + "value" : "https://2.example.com/context_uri", + "rel" : "self", + "href" : "https://2.example.com/target_uri", + "hreflang" : [ "en", "ch" ], + "title" : "title2", + "media" : "screen", + "type" : "application/json" + } + ] + "#; + + // WHEN + let links = serde_json::from_str::(expected); + + // THEN + let actual = links.unwrap(); + assert_eq!(actual.len(), 2); + let actual_1 = actual.first().unwrap(); + let actual_2 = actual.last().unwrap(); + assert_eq!( + actual_1.value.as_ref().unwrap(), + "https://1.example.com/context_uri" + ); + assert_eq!( + actual_2.value.as_ref().unwrap(), + "https://2.example.com/context_uri" + ); + assert_eq!( + actual_1.href.as_ref().unwrap(), + "https://1.example.com/target_uri" + ); + assert_eq!( + actual_2.href.as_ref().unwrap(), + "https://2.example.com/target_uri" + ); + assert_eq!(actual_1.title.as_ref().unwrap(), "title1"); + assert_eq!(actual_2.title.as_ref().unwrap(), "title2"); + assert_eq!(actual_1.media_type.as_ref().unwrap(), "application/json"); + assert_eq!(actual_2.media_type.as_ref().unwrap(), "application/json"); + } + + #[test] + fn GIVEN_an_array_of_links_with_one_lang_WHEN_deserialize_THEN_success() { + // GIVEN + let expected = r#" + [ + { + "value" : "https://1.example.com/context_uri", + "rel" : "self", + "href" : "https://1.example.com/target_uri", + "hreflang" : "en", + "title" : "title1", + "media" : "screen", + "type" : "application/json" + }, + { + "value" : "https://2.example.com/context_uri", + "rel" : "self", + "href" : "https://2.example.com/target_uri", + "hreflang" : "ch", + "title" : "title2", + "media" : "screen", + "type" : "application/json" + } + ] + "#; + + // WHEN + let links = serde_json::from_str::(expected); + + // THEN + let actual = links.unwrap(); + assert_eq!(actual.len(), 2); + let actual_1 = actual.first().unwrap(); + let actual_2 = actual.last().unwrap(); + assert_eq!( + actual_1.value.as_ref().unwrap(), + "https://1.example.com/context_uri" + ); + assert_eq!( + actual_2.value.as_ref().unwrap(), + "https://2.example.com/context_uri" + ); + assert_eq!( + actual_1.href.as_ref().unwrap(), + "https://1.example.com/target_uri" + ); + assert_eq!( + actual_2.href.as_ref().unwrap(), + "https://2.example.com/target_uri" + ); + assert_eq!(actual_1.title.as_ref().unwrap(), "title1"); + assert_eq!(actual_2.title.as_ref().unwrap(), "title2"); + assert_eq!(actual_1.media_type.as_ref().unwrap(), "application/json"); + assert_eq!(actual_2.media_type.as_ref().unwrap(), "application/json"); + } + + #[test] + fn GIVEN_a_notice_or_remark_WHEN_deserialize_THEN_success() { + // GIVEN + let expected = r#" + { + "title" : "Terms of Use", + "description" : + [ + "Service subject to The Registry of the Moon's TOS.", + "Copyright (c) 2020 LunarNIC" + ], + "links" : + [ + { + "value" : "https://example.net/entity/XXXX", + "rel" : "alternate", + "type" : "text/html", + "href" : "https://www.example.com/terms_of_use.html" + } + ] + } + "#; + + // WHEN + let actual = serde_json::from_str::(expected); + + // THEN + let actual = actual.unwrap(); + actual.title.as_ref().unwrap(); + let description: Vec = actual.description.expect("must have description").into(); + assert_eq!(description.len(), 2); + actual.links.unwrap(); + } + + #[test] + fn GIVEN_notices_WHEN_serialize_THEN_array_of_notice_structs() { + // GIVEN + let notices: Notices = vec![ + Notice( + NoticeOrRemark::builder() + .description(vec!["foo".to_string()]) + .build(), + ), + Notice( + NoticeOrRemark::builder() + .description(vec!["bar".to_string()]) + .build(), + ), + ]; + + // WHEN + let actual = serde_json::to_string(¬ices).unwrap(); + + // THEN + let expected = r#"[{"description":["foo"]},{"description":["bar"]}]"#; + assert_eq!(actual, expected); + } + + #[test] + fn GIVEN_remarks_WHEN_serialize_THEN_array_of_remark_structs() { + // GIVEN + let remarks: Remarks = vec![ + Remark( + NoticeOrRemark::builder() + .description(vec!["foo".to_string()]) + .build(), + ), + Remark( + NoticeOrRemark::builder() + .description(vec!["bar".to_string()]) + .build(), + ), + ]; + + // WHEN + let actual = serde_json::to_string(&remarks).unwrap(); + + // THEN + let expected = r#"[{"description":["foo"]},{"description":["bar"]}]"#; + assert_eq!(actual, expected); + } + + #[test] + fn GIVEN_an_event_WHEN_deserialize_THEN_success() { + // GIVEN + let expected = r#" + { + "eventAction" : "last changed", + "eventActor" : "OTHERID-LUNARNIC", + "eventDate" : "1991-12-31T23:59:59Z" + } + "#; + + // WHEN + let actual = serde_json::from_str::(expected); + + // THEN + let actual = actual.unwrap(); + actual.event_actor.as_ref().unwrap(); + } + + #[test] + fn GIVEN_a_public_id_WHEN_deserialize_THEN_success() { + // GIVEN + let expected = r#" + { + "type":"IANA Registrar ID", + "identifier":"1" + } + "#; + + // WHEN + let actual = serde_json::from_str::(expected); + + // THEN + let _actual = actual.unwrap(); + } + + #[test] + fn GIVEN_no_self_links_WHEN_set_self_link_THEN_link_is_only_one() { + // GIVEN + let mut oc = ObjectCommon::domain() + .links(vec![Link::builder() + .href("http://bar.example") + .value("http://bar.example") + .rel("unknown") + .build()]) + .build(); + + // WHEN + oc = oc.set_self_link( + Link::builder() + .href("http://foo.example") + .value("http://foo.example") + .rel("unknown") + .build(), + ); + + // THEN + assert_eq!( + oc.links + .expect("links are empty") + .iter() + .filter(|link| link.is_relation("self")) + .count(), + 1 + ); + } + + #[test] + fn GIVEN_no_links_WHEN_set_self_link_THEN_link_is_only_one() { + // GIVEN + let mut oc = ObjectCommon::domain().build(); + + // WHEN + oc = oc.set_self_link( + Link::builder() + .href("http://foo.example") + .value("http://foo.example") + .rel("unknown") + .build(), + ); + + // THEN + assert_eq!( + oc.links + .expect("links are empty") + .iter() + .filter(|link| link.is_relation("self")) + .count(), + 1 + ); + } + + #[test] + fn GIVEN_one_self_link_WHEN_set_self_link_THEN_link_is_only_one() { + // GIVEN + let mut oc = ObjectCommon::domain() + .links(vec![Link::builder() + .href("http://bar.example") + .value("http://bar.example") + .rel("self") + .build()]) + .build(); + + // WHEN + oc = oc.set_self_link( + Link::builder() + .href("http://foo.example") + .value("http://foo.example") + .rel("unknown") + .build(), + ); + + // THEN + // new link is in + assert_eq!( + oc.links + .as_ref() + .expect("links are empty") + .iter() + .filter(|link| link.is_relation("self") + && link.href.as_ref().unwrap() == "http://foo.example") + .count(), + 1 + ); + // all self links count == 1 + assert_eq!( + oc.links + .as_ref() + .expect("links are empty") + .iter() + .filter(|link| link.is_relation("self")) + .count(), + 1 + ); + } +} diff --git a/icann-rdap-srv/Cargo.toml b/icann-rdap-srv/Cargo.toml new file mode 100644 index 0000000..0a52304 --- /dev/null +++ b/icann-rdap-srv/Cargo.toml @@ -0,0 +1,59 @@ +[package] +name = "icann-rdap-srv" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +description = """ +An RDAP Server. +""" + +[dependencies] + +icann-rdap-client = { version = "0.0.22", path = "../icann-rdap-client" } +icann-rdap-common = { version = "0.0.22", path = "../icann-rdap-common" } + +ab-radix-trie.workspace = true +async-trait.workspace = true +axum.workspace = true +axum-extra.workspace = true +axum-macros.workspace = true +axum-client-ip.workspace = true +btree-range-map.workspace = true +buildstructor.workspace = true +chrono.workspace = true +cidr.workspace = true +clap.workspace = true +dotenv.workspace = true +envmnt.workspace = true +idna.workspace = true +ipnet.workspace = true +headers.workspace = true +http.workspace = true +hyper.workspace = true +pct-str.workspace = true +prefix-trie.workspace = true +regex.workspace = true +reqwest.workspace = true +serde.workspace = true +serde_json.workspace = true +strum.workspace = true +strum_macros.workspace = true +sqlx.workspace = true +thiserror.workspace = true +tokio.workspace = true +tower.workspace = true +tower-http.workspace = true +tracing.workspace = true +tracing-subscriber.workspace = true + +[dev-dependencies] + +# cli assertions +assert_cmd = "2.0.11" + +# fixture testings +rstest = "0.17.0" + +# test directories +test_dir = "0.2.0" diff --git a/icann-rdap-srv/README.md b/icann-rdap-srv/README.md new file mode 100644 index 0000000..a4110dc --- /dev/null +++ b/icann-rdap-srv/README.md @@ -0,0 +1,26 @@ +ICANN RDAP Server +================= + +This is an RDAP server that stores information in-memory and is +subsequently very fast. + +Installation and Usage +---------------------- + +See the [project wiki](https://github.com/icann/icann-rdap/wiki) for information on installation +and usage of this software. + +License +------- + +Licensed under either of +* Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) +* MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT) at your option. + +Contribution +------------ + +Unless you explicitly state otherwise, any contribution, as defined in the Apache-2.0 license, +intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, +shall be dual licensed pursuant to the Apache License, Version 2.0 or the MIT License referenced +as above, at ICANN’s option, without any additional terms or conditions. diff --git a/icann-rdap-srv/resources/drill.yml b/icann-rdap-srv/resources/drill.yml new file mode 100644 index 0000000..fc9e45d --- /dev/null +++ b/icann-rdap-srv/resources/drill.yml @@ -0,0 +1,65 @@ +# This a YAML file to use with Drill (https://github.com/fcsonline/drill), +# an HTTP load testing tool. When used with rdap-srv-test-data, Drill +# can be used to load test rdap-srv. + +--- +base: 'http://localhost:3000/rdap' +iterations: 1000 +concurrency: 8 + +plan: + - name: "Domain" + request: + url: /domain/test-domain-{{ item }}.example + with_items_range: + start: 1 + step: 1 + stop: 5000 + + - name: "Entity" + request: + url: /entity/test-entity-{{ item }} + with_items_range: + start: 1 + step: 1 + stop: 5000 + + - name: "Nameserver" + request: + url: /nameserver/ns.test-nameserver-{{ item }}.example + with_items_range: + start: 1 + step: 1 + stop: 5000 + + - name: "Autnum" + request: + url: /autnum/{{ item }} + with_items_range: + start: 1 + step: 1 + stop: 5000 + + - name: "IpV4 Octet 4" + request: + url: /ip/1.0.0.{{ item }} + with_items_range: + start: 1 + step: 1 + stop: 254 + + - name: "IpV4 Octet 3" + request: + url: /ip/1.0.{{ item }}.0 + with_items_range: + start: 1 + step: 1 + stop: 254 + + - name: "IpV6" + request: + url: "/ip/2000:0:0:0::{{ item }}" + with_items_range: + start: 1 + step: 1 + stop: 5000 diff --git a/icann-rdap-srv/resources/test-data-files/0_2_192_in-addr_arpa.json b/icann-rdap-srv/resources/test-data-files/0_2_192_in-addr_arpa.json new file mode 100644 index 0000000..e995ccb --- /dev/null +++ b/icann-rdap-srv/resources/test-data-files/0_2_192_in-addr_arpa.json @@ -0,0 +1,154 @@ +{ + "objectClassName" : "domain", + "handle" : "XXXX", + "ldhName" : "0.2.192.in-addr.arpa", + "nameservers" : + [ + { + "objectClassName" : "nameserver", + "ldhName" : "ns1.rir.example" + }, + { + "objectClassName" : "nameserver", + "ldhName" : "ns2.rir.example" + } + ], + "secureDNS": + { + "delegationSigned": true, + "dsData": + [ + { + "keyTag": 25345, + "algorithm": 8, + "digestType": 2, + "digest": "2788970E18EA14...C890C85B8205B94" + } + ] + }, + "remarks" : + [ + { + "description" : + [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links" : + [ + { + "value": "https://example.net/domain/0.2.192.in-addr.arpa", + "rel" : "self", + "href" : "https://example.net/domain/0.2.192.in-addr.arpa", + "type" : "application/rdap+json" + + } + ], + "events" : + [ + { + "eventAction" : "registration", + "eventDate" : "1990-12-31T23:59:59Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "1991-12-31T23:59:59Z", + "eventActor" : "joe@example.com" + } + ], + "entities" : + [ + { + "objectClassName" : "entity", + "handle" : "XXXX", + "vcardArray":[ + "vcard", + [ + ["version", {}, "text", "4.0"], + ["fn", {}, "text", "Joe User"], + ["kind", {}, "text", "individual"], + ["lang", { + "pref":"1" + }, "language-tag", "fr"], + ["lang", { + "pref":"2" + }, "language-tag", "en"], + ["org", { + "type":"work" + }, "text", "Example"], + ["title", {}, "text", "Research Scientist"], + ["role", {}, "text", "Project Lead"], + ["adr", + { "type":"work" }, + "text", + [ + "", + "Suite 1234", + "4321 Rue Somewhere", + "Quebec", + "QC", + "G1V 2M2", + "Canada" + ] + + ], + ["tel", + { "type":["work", "voice"], "pref":"1" }, + "uri", "tel:+1-555-555-1234;ext=102" + ], + ["email", + { "type":"work" }, + "text", "joe.user@example.com" + ] + ] + ], + "roles" : [ "registrant" ], + "remarks" : + [ + { + "description" : + [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links" : + [ + { + "value": "https://example.net/entity/XXXX", + "rel" : "self", + "href" : "https://example.net/entity/XXXX", + "type" : "application/rdap+json" + } + ], + "events" : + [ + { + "eventAction" : "registration", + "eventDate" : "1990-12-31T23:59:59Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "1991-12-31T23:59:59Z", + "eventActor" : "joe@example.com" + } + ] + } + ], + "network" : + { + "objectClassName" : "ip network", + "handle" : "XXXX-RIR", + "startAddress" : "192.0.2.0", + "endAddress" : "192.0.2.255", + "ipVersion" : "v4", + "name": "NET-RTR-1", + "type" : "DIRECT ALLOCATION", + "country" : "AU", + "parentHandle" : "YYYY-RIR", + "status" : [ "active" ] + } +} diff --git a/icann-rdap-srv/resources/test-data-files/2001_db8__.json b/icann-rdap-srv/resources/test-data-files/2001_db8__.json new file mode 100644 index 0000000..c4586b0 --- /dev/null +++ b/icann-rdap-srv/resources/test-data-files/2001_db8__.json @@ -0,0 +1,127 @@ +{ + "objectClassName" : "ip network", + "handle" : "XXXX-RIR", + "startAddress" : "2001:db8::", + "endAddress" : "2001:db8:0:ffff:ffff:ffff:ffff:ffff", + "ipVersion" : "v6", + "name": "NET-RTR-1", + "type" : "DIRECT ALLOCATION", + "country" : "AU", + "parentHandle" : "YYYY-RIR", + "status" : [ "active" ], + "remarks" : + [ + { + "description" : + [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links" : + [ + { + "value" : "https://example.net/ip/2001:db8::/48", + "rel" : "self", + "href" : "https://example.net/ip/2001:db8::/48", + "type" : "application/rdap+json" + }, + { + "value" : "https://example.net/ip/2001:db8::/48", + "rel" : "up", + "href" : "https://example.net/ip/2001:db8::/32", + "type" : "application/rdap+json" + } + ], + "events" : + [ + { + "eventAction" : "registration", + "eventDate" : "1990-12-31T23:59:59Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "1991-12-31T23:59:59Z" + } + ], + "entities" : + [ + { + "objectClassName" : "entity", + "handle" : "XXXX", + "vcardArray":[ + "vcard", + [ + ["version", {}, "text", "4.0"], + ["fn", {}, "text", "Joe User"], + ["kind", {}, "text", "individual"], + ["lang", { + "pref":"1" + }, "language-tag", "fr"], + ["lang", { + "pref":"2" + }, "language-tag", "en"], + ["org", { + "type":"work" + }, "text", "Example"], + ["title", {}, "text", "Research Scientist"], + ["role", {}, "text", "Project Lead"], + ["adr", + { "type":"work" }, + "text", + [ + "", + "Suite 1234", + "4321 Rue Somewhere", + "Quebec", + "QC", + "G1V 2M2", + "Canada" + ] + ], + ["tel", + { "type":["work", "voice"], "pref":"1" }, + "uri", "tel:+1-555-555-1234;ext=102" + ], + ["email", + { "type":"work" }, + "text", "joe.user@example.com" + ] + ] + ], + "roles" : [ "registrant" ], + "remarks" : + [ + { + "description" : + [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links" : + [ + { + "value" : "https://example.net/entity/xxxx", + "rel" : "self", + "href" : "https://example.net/entity/xxxx", + "type" : "application/rdap+json" + } + ], + "events" : + [ + { + "eventAction" : "registration", + "eventDate" : "1990-12-31T23:59:59Z" + + }, + { + "eventAction" : "last changed", + "eventDate" : "1991-12-31T23:59:59Z" + } + ] + } + ] +} \ No newline at end of file diff --git a/icann-rdap-srv/resources/test-data-files/as65541.json b/icann-rdap-srv/resources/test-data-files/as65541.json new file mode 100644 index 0000000..c131135 --- /dev/null +++ b/icann-rdap-srv/resources/test-data-files/as65541.json @@ -0,0 +1,119 @@ +{ + "objectClassName" : "autnum", + "handle" : "XXXX-RIR", + "startAutnum" : 65536, + "endAutnum" : 65541, + "name": "AS-RTR-1", + "type" : "DIRECT ALLOCATION", + "status" : [ "active" ], + "country": "AU", + "remarks" : + [ + { + "description" : + [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links" : + [ + { + "value" : "https://example.net/autnum/65537", + "rel" : "self", + "href" : "https://example.net/autnum/65537", + "type" : "application/rdap+json" + } + ], + "events" : + + [ + { + "eventAction" : "registration", + "eventDate" : "1990-12-31T23:59:59Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "1991-12-31T23:59:59Z" + } + ], + "entities" : + [ + { + "objectClassName" : "entity", + "handle" : "XXXX", + "vcardArray":[ + "vcard", + [ + ["version", {}, "text", "4.0"], + ["fn", {}, "text", "Joe User"], + ["kind", {}, "text", "individual"], + ["lang", { + "pref":"1" + }, "language-tag", "fr"], + ["lang", { + "pref":"2" + }, "language-tag", "en"], + ["org", { + "type":"work" + }, "text", "Example"], + ["title", {}, "text", "Research Scientist"], + ["role", {}, "text", "Project Lead"], + ["adr", + { "type":"work" }, + "text", + [ + "", + "Suite 1234", + "4321 Rue Somewhere", + "Quebec", + "QC", + "G1V 2M2", + "Canada" + ] + ], + ["tel", + { "type":["work", "voice"], "pref":"1" }, + "uri", "tel:+1-555-555-1234;ext=102" + ], + ["email", + { "type":"work" }, + "text", "joe.user@example.com" + ] + ] + ], + "roles" : [ "registrant" ], + "remarks" : + [ + { + "description" : + [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links" : + [ + { + "value" : "https://example.net/entity/XXXX", + "rel" : "self", + "href" : "https://example.net/entity/XXXX", + "type" : "application/rdap+json" + } + ], + "events" : + [ + { + "eventAction" : "registration", + "eventDate" : "1990-12-31T23:59:59Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "1991-12-31T23:59:59Z" + } + ] + } + ] +} \ No newline at end of file diff --git a/icann-rdap-srv/resources/test-data-files/entity-rfc9083-fig15.json b/icann-rdap-srv/resources/test-data-files/entity-rfc9083-fig15.json new file mode 100644 index 0000000..679fb6a --- /dev/null +++ b/icann-rdap-srv/resources/test-data-files/entity-rfc9083-fig15.json @@ -0,0 +1,115 @@ +{ + "objectClassName" : "entity", + "handle":"fig15", + "vcardArray":[ + "vcard", + [ + ["version", {}, "text", "4.0"], + ["fn", {}, "text", "Joe User"], + ["n", {}, "text", + ["User", "Joe", "", "", ["ing. jr", "M.Sc."]] + ], + ["kind", {}, "text", "individual"], + ["lang", { + "pref":"1" + }, "language-tag", "fr"], + ["lang", { + "pref":"2" + }, "language-tag", "en"], + ["org", { + "type":"work" + }, "text", "Example"], + ["title", {}, "text", "Research Scientist"], + ["role", {}, "text", "Project Lead"], + ["adr", + { "type":"work" }, + "text", + [ + "", + "Suite 1234", + "4321 Rue Somewhere", + "Quebec", + "QC", + "G1V 2M2", + "Canada" + ] + ], + ["adr", + { + "type":"home", + "label":"123 Maple Ave\nSuite 90001\nVancouver\nBC\n1239\n" + }, + "text", + [ + "", "", "", "", "", "", "" + ] + ], + ["tel", + { + "type":["work", "voice"], + "pref":"1" + }, + "uri", + "tel:+1-555-555-1234;ext=102" + ], + ["tel", + { "type":["work", "cell", "voice", "video", "text"] }, + "uri", + "tel:+1-555-555-4321" + ], + ["email", + { "type":"work" }, + "text", + "joe.user@example.com" + ], + ["geo", { + "type":"work" + }, "uri", "geo:46.772673,-71.282945"], + ["key", + { "type":"work" }, + "uri", + "https://www.example.com/joe.user/joe.asc" + ], + ["tz", {}, + "utc-offset", "-05:00"], + ["url", { "type":"home" }, + "uri", "https://example.org"] + ] + ], + "roles":[ "registrar" ], + "publicIds":[ + { + "type":"IANA Registrar ID", + "identifier":"1" + } + ], + "remarks":[ + { + "description":[ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links":[ + { + "value":"https://example.com/entity/XXXX", + "rel":"self", + "href":"https://example.com/entity/XXXX", + "type" : "application/rdap+json" + } + ], + "events":[ + { + "eventAction":"registration", + "eventDate":"1990-12-31T23:59:59Z" + } + ], + "asEventActor":[ + + { + "eventAction":"last changed", + "eventDate":"1991-12-31T23:59:59Z" + } + ] +} \ No newline at end of file diff --git a/icann-rdap-srv/resources/test-data-files/entity-rfc9083-fig17.json b/icann-rdap-srv/resources/test-data-files/entity-rfc9083-fig17.json new file mode 100644 index 0000000..956a222 --- /dev/null +++ b/icann-rdap-srv/resources/test-data-files/entity-rfc9083-fig17.json @@ -0,0 +1,73 @@ +{ + "objectClassName" : "entity", + "handle":"fig17", + "vcardArray":[ + "vcard", + [ + ["version", {}, "text", "4.0"], + ["fn", {}, "text", "Joe User"], + ["kind", {}, "text", "individual"], + ["lang", { + "pref":"1" + }, "language-tag", "fr"], + ["lang", { + "pref":"2" + }, "language-tag", "en"], + ["org", { + "type":"work" + }, "text", "Example"], + ["title", {}, "text", "Research Scientist"], + ["role", {}, "text", "Project Lead"], + ["adr", + { "type":"work" }, + "text", + [ + "", + "Suite 1234", + "4321 Rue Somewhere", + "Quebec", + "QC", + "G1V 2M2", + "Canada" + ] + ], + ["tel", + { "type":["work", "voice"], "pref":"1" }, + "uri", "tel:+1-555-555-1234;ext=102" + ], + ["email", + { "type":"work" }, + "text", "joe.user@example.com" + ] + ] + ], + "status":[ "validated", "locked" ], + "remarks":[ + { + "description":[ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links":[ + { + "value":"https://example.com/entity/XXXX", + "rel":"self", + "href":"https://example.com/entity/XXXX", + "type":"application/rdap+json" + } + ], + "port43":"whois.example.net", + "events":[ + { + "eventAction":"registration", + "eventDate":"1990-12-31T23:59:59Z" + }, + { + "eventAction":"last changed", + "eventDate":"1991-12-31T23:59:59Z", + "eventActor":"joe@example.com" + } + ] +} \ No newline at end of file diff --git a/icann-rdap-srv/resources/test-data-files/ns1_example_com.json b/icann-rdap-srv/resources/test-data-files/ns1_example_com.json new file mode 100644 index 0000000..960005e --- /dev/null +++ b/icann-rdap-srv/resources/test-data-files/ns1_example_com.json @@ -0,0 +1,4 @@ +{ + "objectClassName" : "nameserver", + "ldhName" : "ns1.example.com" +} \ No newline at end of file diff --git a/icann-rdap-srv/resources/test-data-files/ns1_xn--fo-5ja_example.json b/icann-rdap-srv/resources/test-data-files/ns1_xn--fo-5ja_example.json new file mode 100644 index 0000000..58f7b63 --- /dev/null +++ b/icann-rdap-srv/resources/test-data-files/ns1_xn--fo-5ja_example.json @@ -0,0 +1,44 @@ +{ + "objectClassName" : "nameserver", + "handle" : "XXXX", + "ldhName" : "ns1.xn--fo-5ja.example", + "unicodeName" : "ns.fóo.example", + "status" : [ "active" ], + "ipAddresses" : + { + "v4": [ "192.0.2.1", "192.0.2.2" ], + "v6": [ "2001:db8::123" ] + }, + "remarks" : + [ + { + "description" : + [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links" : + [ + { + "value" : "https://example.net/nameserver/ns1.xn--fo-5ja.example", + "rel" : "self", + "href" : "https://example.net/nameserver/ns1.xn--fo-5ja.example", + "type" : "application/rdap+json" + } + ], + "port43" : "whois.example.net", + "events" : + [ + { + "eventAction" : "registration", + "eventDate" : "1990-12-31T23:59:59Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "1991-12-31T23:59:59Z", + "eventActor" : "joe@example.com" + } + ] +} \ No newline at end of file diff --git a/icann-rdap-srv/resources/test-data-files/ns2_example_com.json b/icann-rdap-srv/resources/test-data-files/ns2_example_com.json new file mode 100644 index 0000000..9f7517e --- /dev/null +++ b/icann-rdap-srv/resources/test-data-files/ns2_example_com.json @@ -0,0 +1,5 @@ +{ + "objectClassName" : "nameserver", + "ldhName" : "ns2.example.com", + "ipAddresses" : { "v6" : [ "2001:db8::123", "2001:db8::124" ] } +} diff --git a/icann-rdap-srv/resources/test-data-files/xn--fo-5ja_example.json b/icann-rdap-srv/resources/test-data-files/xn--fo-5ja_example.json new file mode 100644 index 0000000..a1c3409 --- /dev/null +++ b/icann-rdap-srv/resources/test-data-files/xn--fo-5ja_example.json @@ -0,0 +1,272 @@ +{ + "objectClassName" : "domain", + "handle" : "XXXX", + "ldhName" : "xn--fo-5ja.example", + "unicodeName" : "fóo.example", + "variants" : + [ + { + "relation" : [ "registered", "conjoined" ], + "variantNames" : + [ + { + "ldhName" : "xn--fo-cka.example", + "unicodeName" : "fõo.example" + }, + { + "ldhName" : "xn--fo-fka.example", + "unicodeName" : "föo.example" + } + ] + }, + { + "relation" : [ "unregistered", "registration restricted" ], + "idnTable": ".EXAMPLE Swedish", + "variantNames" : + [ + { + "ldhName": "xn--fo-8ja.example", + "unicodeName" : "fôo.example" + } + ] + + } + ], + "status" : [ "locked", "transfer prohibited" ], + "publicIds":[ + { + "type":"ENS_Auth ID", + "identifier":"1234567890" + } + ], + "nameservers" : + [ + { + "objectClassName" : "nameserver", + "handle" : "XXXX", + "ldhName" : "ns1.example.com", + "status" : [ "active" ], + "ipAddresses" : + { + "v6": [ "2001:db8::123", "2001:db8::124" ], + "v4": [ "192.0.2.1", "192.0.2.2" ] + }, + "remarks" : + [ + { + "description" : + [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links" : + [ + { + "value" : "https://example.net/nameserver/ns1.example.com", + "rel" : "self", + "href" : "https://example.net/nameserver/ns1.example.com", + "type" : "application/rdap+json" + } + ], + "events" : + [ + { + "eventAction" : "registration", + "eventDate" : "1990-12-31T23:59:59Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "1991-12-31T23:59:59Z" + } + ] + }, + { + "objectClassName" : "nameserver", + "handle" : "XXXX", + "ldhName" : "ns2.example.com", + "status" : [ "active" ], + "ipAddresses" : + { + "v6" : [ "2001:db8::125", "2001:db8::126" ], + "v4" : [ "192.0.2.3", "192.0.2.4" ] + }, + "remarks" : + [ + { + "description" : + [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links" : + [ + { + "value" : "https://example.net/nameserver/ns2.example.com", + "rel" : "self", + "href" : "https://example.net/nameserver/ns2.example.com", + "type" : "application/rdap+json" + } + ], + "events" : + [ + { + "eventAction" : "registration", + "eventDate" : "1990-12-31T23:59:59Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "1991-12-31T23:59:59Z" + } + ] + } + ], + "secureDNS": + { + + "zoneSigned": true, + "delegationSigned": true, + "maxSigLife": 604800, + "keyData": + [ + { + "flags": 257, + "protocol": 3, + "algorithm": 8, + "publicKey": "AwEAAa6eDzronzjEDbT...Jg1M5N rBSPkuXpdFE=", + "events": + [ + { + "eventAction": "last changed", + "eventDate": "2012-07-23T05:15:47Z" + } + ] + } + ] + }, + "remarks" : + [ + { + "description" : + [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links" : + [ + { + "value": "https://example.net/domain/xn--fo-5ja.example", + "rel" : "self", + "href" : "https://example.net/domain/xn--fo-5ja.example", + "type" : "application/rdap+json" + } + ], + "port43" : "whois.example.net", + "events" : + [ + { + "eventAction" : "registration", + "eventDate" : "1990-12-31T23:59:59Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "1991-12-31T23:59:59Z", + "eventActor" : "joe@example.com" + }, + { + "eventAction" : "transfer", + "eventDate" : "1991-12-31T23:59:59Z", + "eventActor" : "joe@example.com" + }, + { + "eventAction" : "expiration", + "eventDate" : "2016-12-31T23:59:59Z", + "eventActor" : "joe@example.com" + } + ], + "entities" : + [ + { + "objectClassName" : "entity", + "handle" : "XXXX", + "vcardArray":[ + "vcard", + [ + ["version", {}, "text", "4.0"], + ["fn", {}, "text", "Joe User"], + ["kind", {}, "text", "individual"], + ["lang", { + "pref":"1" + }, "language-tag", "fr"], + ["lang", { + "pref":"2" + }, "language-tag", "en"], + ["org", { + "type":"work" + }, "text", "Example"], + ["title", {}, "text", "Research Scientist"], + ["role", {}, "text", "Project Lead"], + ["adr", + { "type":"work" }, + "text", + [ + "", + "Suite 1234", + "4321 Rue Somewhere", + "Quebec", + "QC", + "G1V 2M2", + "Canada" + ] + + ], + ["tel", + { "type":["work", "voice"], "pref":"1" }, + "uri", "tel:+1-555-555-1234;ext=102" + ], + ["email", + { "type":"work" }, + "text", "joe.user@example.com" + ] + ] + ], + "status" : [ "validated", "locked" ], + "roles" : [ "registrant" ], + "remarks" : + [ + { + "description" : + [ + "She sells sea shells down by the sea shore.", + "Originally written by Terry Sullivan." + ] + } + ], + "links" : + [ + { + "value" : "https://example.net/entity/XXXX", + "rel" : "self", + "href" : "https://example.net/entity/XXXX", + "type" : "application/rdap+json" + } + ], + "events" : + [ + { + "eventAction" : "registration", + "eventDate" : "1990-12-31T23:59:59Z" + }, + { + "eventAction" : "last changed", + "eventDate" : "1991-12-31T23:59:59Z" + } + ] + } + ] +} diff --git a/icann-rdap-srv/src/bin/rdap-srv-data.rs b/icann-rdap-srv/src/bin/rdap-srv-data.rs new file mode 100644 index 0000000..e3634cc --- /dev/null +++ b/icann-rdap-srv/src/bin/rdap-srv-data.rs @@ -0,0 +1,1174 @@ +use { + chrono::{DateTime, FixedOffset, Utc}, + cidr::{IpCidr, IpInet}, + clap::{Args, Parser, Subcommand}, + icann_rdap_client::rdap::QueryType, + icann_rdap_common::{ + contact::{Contact, PostalAddress}, + media_types::RDAP_MEDIA_TYPE, + prelude::{RdapResponse, ToNotices, ToRemarks, ToResponse, VectorStringish}, + response::{ + Autnum, Domain, DsDatum, Entity, Event, Events, Help, Link, Links, Nameserver, Network, + Notice, NoticeOrRemark, Rfc9083Error, SecureDns, ToChild, + }, + VERSION, + }, + icann_rdap_srv::{ + config::{debug_config_vars, ServiceConfig, LOG}, + error::RdapServerError, + storage::{ + data::{ + load_data, AutnumId, AutnumOrError, DomainId, DomainOrError, EntityId, + EntityOrError, NameserverId, NameserverOrError, NetworkId, NetworkOrError, + Template, + }, + mem::{config::MemConfig, ops::Mem}, + CommonConfig, StoreOps, + }, + util::bin::check::{check_rdap, to_check_classes, CheckArgs}, + }, + pct_str::{PctString, URIReserved}, + regex::Regex, + std::{fs, path::PathBuf, str::FromStr}, + tracing::{error, info}, + tracing_subscriber::{ + fmt, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, EnvFilter, + }, +}; + +#[derive(Parser, Debug)] +#[command(author, version = VERSION, about, long_about)] +/// This program creates RDAP objects. +struct Cli { + #[clap(flatten)] + check_args: CheckArgs, + + /// Specifies the directory where data will be written. + #[arg(long, env = "RDAP_SRV_DATA_DIR")] + data_dir: String, + + /// Output data as a redirect. + /// + /// When specified, the data will create a redirect template file to the given URL. + /// This cannot be used with --template. + #[arg(long, conflicts_with = "template")] + redirect: Option, + + /// Output data as a template. + /// + /// When specified, the data will be output as a template file. + /// This cannot be used with --redirect. + #[arg(long, conflicts_with = "redirect")] + template: bool, + + #[command(subcommand)] + command: Commands, +} + +#[derive(Debug, Args)] +struct ObjectArgs { + /// Base URL of the server where the object is to be served. + #[arg(short = 'B', long, env = "RDAP_BASE_URL")] + base_url: String, + + /// Status of the object (e.g. "active"). + /// + /// This argument may be specified multiple times. + #[arg(long)] + status: Vec, + + /// Created date and time. + /// + /// This argument should be in RFC3339 format. + /// If not specified, the current date and time will be used. + #[arg(long, value_parser = parse_datetime)] + created: Option>, + + /// Updated date and time. + /// + /// This argument should be in RFC3339 format. + /// If not specified, the current date and time will be used. + #[arg(long, value_parser = parse_datetime)] + updated: Option>, + + /// Adds a server notice. + /// + /// Takes the form of "\[LINK\] description" where the optional \[LINK\] takes + /// the form of "(REL;TYPE)\[HREF\]". This argument maybe specified multiple times. + #[arg(long, value_parser = parse_notice_or_remark)] + notice: Vec, + + /// Adds an object remark. + /// + /// Takes the form of "\[LINK\] description" where the optional \[LINK\] takes + /// the form of "(REL;TYPE)\[HREF\]". This argument maybe specified multiple times. + #[arg(long, value_parser = parse_notice_or_remark)] + remark: Vec, + + /// Registrant entity handle. + #[arg(long)] + registrant: Option, + + /// Administrative entity handle. + #[arg(long)] + administrative: Option, + + /// Technical entity handle. + #[arg(long)] + technical: Option, + + /// Abuse entity handle. + #[arg(long)] + abuse: Option, + + /// Billing entity handle. + #[arg(long)] + billing: Option, + + /// Registrar entity handle. + #[arg(long)] + registrar: Option, + + /// NOC entity handle. + #[arg(long)] + noc: Option, +} + +fn parse_datetime(arg: &str) -> Result, chrono::format::ParseError> { + let dt = DateTime::parse_from_rfc3339(arg)?; + Ok(dt) +} + +fn parse_notice_or_remark(arg: &str) -> Result { + let re = Regex::new(r"^(?P\(\S+\)\[\S+\])?\s*(?P.+)$") + .expect("creating notice/remark argument regex"); + let Some(cap) = re.captures(arg) else { + return Err(RdapServerError::ArgParse( + "Unable to parse Notice/Remark argumnet.".to_string(), + )); + }; + let Some(description) = cap.name("t") else { + return Err(RdapServerError::ArgParse( + "Unable to parse Notice/Remark description".to_string(), + )); + }; + let mut links = vec![]; + if let Some(link_data) = cap.name("l") { + let link_re = + Regex::new(r"^\((?P\w+);(?P\S+)\)\[(?P\S+)\]$").expect("creating link regex"); + let Some(link_cap) = link_re.captures(link_data.as_str()) else { + return Err(RdapServerError::ArgParse( + "Unable to parse link in Notice/Remark".to_string(), + )); + }; + let Some(link_rel) = link_cap.name("r") else { + return Err(RdapServerError::ArgParse( + "unable to parse link rel in Notice/Remark".to_string(), + )); + }; + let Some(link_type) = link_cap.name("t") else { + return Err(RdapServerError::ArgParse( + "unable to parse link type in Notice/Remark".to_string(), + )); + }; + let Some(link_href) = link_cap.name("h") else { + return Err(RdapServerError::ArgParse( + "unable to parse link href in Notice/Remark".to_string(), + )); + }; + links = vec![Link::builder() + .media_type(link_type.as_str().to_string()) + .href(link_href.as_str().to_string()) + .value(link_href.as_str().to_string()) + .rel(link_rel.as_str().to_string()) + .build()]; + } + let not_rem = NoticeOrRemark::builder() + .description(vec![description.as_str().to_string()]) + .links(links) + .build(); + Ok(not_rem) +} + +#[derive(Debug, Subcommand)] +enum Commands { + /// Creates an RDAP entity. + Entity(Box), + + /// Create a nameserver. + Nameserver(Box), + + /// Create a domain. + Domain(Box), + + /// Create an autnum. + Autnum(Box), + + /// Create an IP network. + Network(Box), + + /// Creates a Help response. + SrvHelp(SrvHelpArgs), +} + +#[derive(Debug, Args)] +struct EntityArgs { + #[clap(flatten)] + object_args: ObjectArgs, + + /// Entity handle. + #[arg(long)] + handle: String, + + /// Full name of contact. + /// + /// If not specified, an org-name will be used. + #[arg(long)] + full_name: Option, + + /// Title. + /// + /// This argument may be specified multiple times. + #[arg(long)] + title: Vec, + + /// Organization name. + /// + /// This argument may be specified multiple times. + #[arg(long)] + org_name: Vec, + + /// Email. + /// + /// Specifies the email for the contact of the entity. + /// This argument may be specified multiple times. + #[arg(long)] + email: Vec, + + /// Voice phone. + /// + /// Specifies the voice phone for the contact of the entity. + /// This argument may be specified multiple times. + #[arg(long)] + voice: Vec, + + /// Fax phone. + /// + /// Specifies the fax phone for the contact of the entity. + /// This argument may be specified multiple times. + #[arg(long)] + fax: Vec, + + /// Street address line. + /// + /// Specifies a line in the "street" part of an address. + /// Street lines are parts of an address that are more + /// specific than a locality or city, and are not necessarily + /// a street address. That is, it maybe a post office box. + /// This argument may be specified multiple times. + #[arg(long)] + street: Vec, + + /// Locality (e.g. city). + #[arg(long)] + locality: Option, + + /// Region name (e.g. province or state). + #[arg(long)] + region_name: Option, + + /// Region code. + /// + /// This should be the 2 letter code for the region. + #[arg(long)] + region_code: Option, + + /// Country name. + #[arg(long)] + country_name: Option, + + /// Country code. + /// + /// This should be the 2 letter code for the country. + #[arg(long)] + country_code: Option, + + /// Postal code (e.g. zip code). + #[arg(long)] + postal_code: Option, +} + +#[derive(Debug, Args)] +struct NameserverArgs { + #[clap(flatten)] + object_args: ObjectArgs, + + /// Entity handle. + #[arg(long)] + handle: Option, + + /// Letters-Digits-Hyphen name. + #[arg(long)] + ldh: String, + + /// Ipv4 Address. + /// + /// This argument may be given multiple times. + #[arg(long)] + v4: Vec, + + /// Ipv6 Address. + /// + /// This argument may be given multiple times. + #[arg(long)] + v6: Vec, +} + +#[derive(Debug, Args)] +struct DomainArgs { + #[clap(flatten)] + object_args: ObjectArgs, + + /// Domain handle. + #[arg(long)] + handle: Option, + + /// Letters-Digits-Hyphen name. + #[arg(long)] + ldh: Option, + + /// IDN U-Label name. + #[arg(long)] + idn: Option, + + /// Zone is signed. + #[arg(long)] + zone_signed: Option, + + /// Delegation is signed. + #[arg(long)] + delegation_signed: Option, + + /// Maximum Signature Life. + /// + /// This value is specified in seconds. + #[arg(long)] + max_sig_life: Option, + + /// Adds DS Information. + /// + /// Takes the form of "KEYTAG ALGORITHM DIGEST_TYPE DIGEST". + /// This argument maybe specified multiple times. + #[arg(long, value_parser = parse_ds_datum)] + ds: Vec, + + /// Nameserver LDH. + /// + /// The DNS LDH (letters, digits, hyphens) name of the name server. + /// This argument may be given multiple times. + #[arg(long)] + ns: Vec, +} + +fn parse_ds_datum(arg: &str) -> Result { + let strings = arg.split_whitespace().collect::>(); + if strings.len() != 4 { + return Err(RdapServerError::InvalidArg( + "not enough DS data".to_string(), + )); + } + let key_tag: u32 = strings[0] + .parse() + .map_err(|_e| RdapServerError::InvalidArg("cannot parse keyTag".to_string()))?; + let algorithm: u8 = strings[1] + .parse() + .map_err(|_e| RdapServerError::InvalidArg("cannot parse algorithm".to_string()))?; + let digest_type: u8 = strings[2] + .parse() + .map_err(|_e| RdapServerError::InvalidArg("cannot parse digestType".to_string()))?; + let ds_datum = DsDatum::builder() + .key_tag(key_tag) + .algorithm(algorithm) + .digest_type(digest_type) + .digest(strings[3].to_owned()) + .build(); + Ok(ds_datum) +} + +#[derive(Debug, Args)] +struct AutnumArgs { + #[clap(flatten)] + object_args: ObjectArgs, + + /// Start Autnum + #[arg(long)] + start_autnum: u32, + + /// End Autnum + /// + /// If not given, start_autnum will be used. + #[arg(long)] + end_autnum: Option, + + /// Autnum handle. + #[arg(long)] + handle: Option, + + /// Autnum type. + #[arg(long)] + autnum_type: Option, + + /// Country. + #[arg(long)] + country: Option, + + /// Name. + #[arg(long)] + name: Option, +} + +#[derive(Debug, Args)] +struct NetworkArgs { + #[clap(flatten)] + object_args: ObjectArgs, + + /// IP CIDR. + /// + /// The RDAP start and end address and IP type will be derived from this. + #[arg(long, value_parser = parse_cidr)] + cidr: IpCidr, + + /// Network handle. + #[arg(long)] + handle: Option, + + /// Parent network handle. + #[arg(long)] + parent_handle: Option, + + /// Network type. + #[arg(long)] + network_type: Option, + + /// Country. + #[arg(long)] + country: Option, + + /// Name. + #[arg(long)] + name: Option, +} + +#[derive(Debug, Args)] +struct SrvHelpArgs { + /// Host. + /// + /// The host name for which help is given. If not given, then the default host is assumed. + #[arg(long)] + host: Option, + + /// Adds a server notice. + /// + /// Takes the form of "\[LINK\] description" where the optional \[LINK\] takes + /// the form of "(REL;TYPE)\[HREF\]". This argument maybe specified multiple times. + #[arg(long, value_parser = parse_notice_or_remark)] + notice: Vec, +} + +fn parse_cidr(arg: &str) -> Result { + let ip_inet = IpInet::from_str(arg).map_err(|e| RdapServerError::InvalidArg(e.to_string()))?; + Ok(ip_inet.network()) +} + +#[tokio::main(flavor = "multi_thread")] +async fn main() -> Result<(), RdapServerError> { + dotenv::dotenv().ok(); + let cli = Cli::parse(); + tracing_subscriber::registry() + .with(fmt::layer()) + .with(EnvFilter::from_env(LOG)) + .init(); + + debug_config_vars(); + + let data_dir = cli.data_dir.clone(); + let config = ServiceConfig::non_server().data_dir(&data_dir).build()?; + let storage = Mem::new( + MemConfig::builder() + .common_config(CommonConfig::default()) + .build(), + ); + storage.init().await?; + load_data(&config, &storage, false).await?; + + let work = do_the_work(cli, &storage, &data_dir).await; + match work { + Ok(_) => Ok(()), + Err(err) => { + error!("Error: {err}"); + Err(err) + } + } +} + +async fn do_the_work( + cli: Cli, + storage: &dyn StoreOps, + data_dir: &str, +) -> Result<(), RdapServerError> { + let output = match cli.command { + Commands::Entity(args) => make_entity(args, storage).await?, + Commands::Nameserver(args) => make_nameserver(args, storage).await?, + Commands::Domain(args) => { + if args.ldh.is_none() && args.idn.is_none() { + return Err(RdapServerError::InvalidArg( + "domain must specify either LDH or U-Label (idn) options".to_string(), + )); + } + make_domain(args, storage).await? + } + Commands::Autnum(args) => make_autnum(args, storage).await?, + Commands::Network(args) => make_network(args, storage).await?, + Commands::SrvHelp(args) => { + if cli.template || cli.redirect.is_some() { + return Err(RdapServerError::InvalidArg( + "help cannot use --redirect or --template options".to_string(), + )); + } + make_help(args)? + } + }; + + let check_types = to_check_classes(&cli.check_args); + let checks_found = check_rdap(output.rdap.clone(), &check_types); + if checks_found { + return Err(RdapServerError::ErrorOnChecks); + } else { + info!("Checks conducted and no issues were found."); + } + + if let RdapId::Help = output.id { + create_help_file(data_dir, &output.self_href, output.rdap)?; + } else if cli.template { + create_template_file(data_dir, &output.self_href, &output.id, &output.rdap)?; + } else if let Some(redirect_url) = cli.redirect { + create_redirect_file(data_dir, &output.self_href, &output.id, &redirect_url)?; + } else { + create_json_file(data_dir, &output.self_href, output.rdap)?; + } + + Ok(()) +} + +fn create_file_name(self_href: &str, extension: &str) -> String { + let file_name = self_href + .trim_start_matches("https://") + .trim_start_matches("http://") + .replace(['.', '/', ':'], "_"); + format!( + "{}.{extension}", + PctString::encode(file_name.chars(), URIReserved) + ) +} + +fn create_json_file( + data_dir: &str, + self_href: &str, + rdap: RdapResponse, +) -> Result<(), RdapServerError> { + let file_name = create_file_name(self_href, "json"); + let mut path = PathBuf::from(data_dir); + path.push(file_name); + let content = serde_json::to_string_pretty(&rdap)?; + fs::write(&path, content)?; + info!("JSON data written to {}.", path.to_string_lossy()); + Ok(()) +} + +fn create_help_file( + data_dir: &str, + self_href: &str, + rdap: RdapResponse, +) -> Result<(), RdapServerError> { + let file_name = create_file_name(self_href, "help"); + let mut path = PathBuf::from(data_dir); + path.push(file_name); + let content = serde_json::to_string_pretty(&rdap)?; + fs::write(&path, content)?; + info!("HELP data written to {}.", path.to_string_lossy()); + Ok(()) +} + +fn create_redirect_file( + data_dir: &str, + self_href: &str, + id: &RdapId, + url: &str, +) -> Result<(), RdapServerError> { + let file_name = create_file_name(self_href, "template"); + let mut path = PathBuf::from(data_dir); + path.push(file_name); + let error = Rfc9083Error::builder() + .error_code(307) + .notice(Notice( + NoticeOrRemark::builder() + .title("Temporary Redirect") + .links(vec![Link::builder() + .href(url) + .value(self_href) + .media_type(RDAP_MEDIA_TYPE) + .rel("related") + .build()]) + .build(), + )) + .build(); + let template = match id { + RdapId::Entity(id) => Template::Entity { + entity: EntityOrError::ErrorResponse(error), + ids: vec![id.clone()], + }, + RdapId::Domain(id) => Template::Domain { + domain: DomainOrError::ErrorResponse(error), + ids: vec![id.clone()], + }, + RdapId::Nameserver(id) => Template::Nameserver { + nameserver: NameserverOrError::ErrorResponse(error), + ids: vec![id.clone()], + }, + RdapId::Autnum(id) => Template::Autnum { + autnum: AutnumOrError::ErrorResponse(error), + ids: vec![id.clone()], + }, + RdapId::Netowrk(id) => Template::Network { + network: NetworkOrError::ErrorResponse(error), + ids: vec![id.clone()], + }, + RdapId::Help => panic!("cannot create help redirect file"), + }; + let content = serde_json::to_string_pretty(&template)?; + fs::write(&path, content)?; + info!("Redirect data written to {}.", path.to_string_lossy()); + Ok(()) +} + +fn create_template_file( + data_dir: &str, + self_href: &str, + id: &RdapId, + rdap: &RdapResponse, +) -> Result<(), RdapServerError> { + let file_name = create_file_name(self_href, "template"); + let mut path = PathBuf::from(data_dir); + path.push(file_name); + let template = match id { + RdapId::Entity(id) => { + let RdapResponse::Entity(entity) = rdap else { + panic!("non entity created with entity id") + }; + Template::Entity { + entity: EntityOrError::EntityObject(Box::new(*entity.clone())), + ids: vec![id.clone()], + } + } + RdapId::Domain(id) => { + let RdapResponse::Domain(domain) = rdap else { + panic!("non domain created with domain id") + }; + Template::Domain { + domain: DomainOrError::DomainObject(Box::new(*domain.clone())), + ids: vec![id.clone()], + } + } + RdapId::Nameserver(id) => { + let RdapResponse::Nameserver(nameserver) = rdap else { + panic!("non nameserver created with nameserver id") + }; + Template::Nameserver { + nameserver: NameserverOrError::NameserverObject(Box::new(*nameserver.clone())), + ids: vec![id.clone()], + } + } + RdapId::Autnum(id) => { + let RdapResponse::Autnum(autnum) = rdap else { + panic!("non autnum created with autnum id") + }; + Template::Autnum { + autnum: AutnumOrError::AutnumObject(Box::new(*autnum.clone())), + ids: vec![id.clone()], + } + } + RdapId::Netowrk(id) => { + let RdapResponse::Network(network) = rdap else { + panic!("non network created with network id") + }; + Template::Network { + network: NetworkOrError::NetworkObject(Box::new(*network.clone())), + ids: vec![id.clone()], + } + } + RdapId::Help => panic!("cannot create help template file"), + }; + let content = serde_json::to_string_pretty(&template)?; + fs::write(&path, content)?; + info!("Template data written to {}.", path.to_string_lossy()); + Ok(()) +} + +enum RdapId { + Entity(EntityId), + Domain(DomainId), + Nameserver(NameserverId), + Autnum(AutnumId), + Netowrk(NetworkId), + Help, +} + +struct Output { + pub rdap: RdapResponse, + pub id: RdapId, + pub self_href: String, +} + +async fn entities(store: &dyn StoreOps, args: &ObjectArgs) -> Result, RdapServerError> { + let mut entities: Vec = Vec::new(); + if let Some(handle) = &args.registrant { + entities.push(get_entity(store, handle, "registrant".to_string()).await?); + } + if let Some(handle) = &args.administrative { + entities.push(get_entity(store, handle, "administrative".to_string()).await?); + } + if let Some(handle) = &args.technical { + entities.push(get_entity(store, handle, "technical".to_string()).await?); + } + if let Some(handle) = &args.abuse { + entities.push(get_entity(store, handle, "abuse".to_string()).await?); + } + if let Some(handle) = &args.billing { + entities.push(get_entity(store, handle, "billing".to_string()).await?); + } + if let Some(handle) = &args.registrar { + entities.push(get_entity(store, handle, "registrar".to_string()).await?); + } + if let Some(handle) = &args.noc { + entities.push(get_entity(store, handle, "noc".to_string()).await?); + } + Ok(entities) +} + +async fn get_entity( + store: &dyn StoreOps, + handle: &str, + role: String, +) -> Result { + let e = store.get_entity_by_handle(handle).await?; + if let RdapResponse::Entity(mut e) = e { + e.roles = Some(VectorStringish::from(role)); + Ok(e.to_child()) + } else { + Err(RdapServerError::InvalidArg(handle.to_string())) + } +} + +async fn nameservers( + store: &dyn StoreOps, + ns_names: Vec, +) -> Result, RdapServerError> { + let mut nameservers: Vec = Vec::new(); + for ns in ns_names { + let ns = get_ns(store, &ns).await?; + nameservers.push(ns); + } + Ok(nameservers) +} + +async fn get_ns(store: &dyn StoreOps, ldh: &str) -> Result { + let n = store.get_nameserver_by_ldh(ldh).await?; + if let RdapResponse::Nameserver(n) = n { + Ok(n.to_child()) + } else { + Err(RdapServerError::InvalidArg(ldh.to_string())) + } +} + +fn events(args: &ObjectArgs) -> Option { + let mut events: Events = vec![]; + let created_at = if let Some(dt) = args.created { + dt + } else { + Utc::now().into() + }; + let created = Event::builder() + .event_date(created_at.to_rfc3339()) + .event_action("registration".to_string()) + .build(); + events.push(created); + let updated_at = if let Some(dt) = args.created { + dt + } else { + Utc::now().into() + }; + let updated = Event::builder() + .event_date(updated_at.to_rfc3339()) + .event_action("last changed".to_string()) + .build(); + events.push(updated); + (!events.is_empty()).then_some(events) +} + +fn links(self_href: &str) -> Option { + let mut links: Links = vec![]; + let self_link = Link::builder() + .value(self_href.to_owned()) + .href(self_href.to_owned()) + .rel("self".to_string()) + .media_type(RDAP_MEDIA_TYPE.to_string()) + .build(); + links.push(self_link); + (!links.is_empty()).then_some(links) +} + +async fn make_entity( + args: Box, + store: &dyn StoreOps, +) -> Result { + let self_href = QueryType::Entity(args.handle.to_owned()) + .query_url(&args.object_args.base_url) + .expect("entity self href"); + let full_name = if let Some(full_name) = args.full_name { + full_name + } else if let Some(first_org) = args.org_name.first() { + first_org.clone() + } else { + return Err(RdapServerError::InvalidArg( + "a full name or org name is required".to_string(), + )); + }; + let mut contact = Contact::builder() + .full_name(full_name) + .organization_names( + (!args.org_name.is_empty()) + .then_some(args.org_name) + .unwrap_or_default(), + ) + .titles( + (!args.title.is_empty()) + .then_some(args.title) + .unwrap_or_default(), + ) + .build(); + contact = contact.set_emails(&args.email); + contact = contact.add_voice_phones(&args.voice); + contact = contact.add_fax_phones(&args.fax); + let postal_address = PostalAddress::builder() + .street_parts(args.street.clone()) + .and_locality(args.locality) + .and_region_name(args.region_name) + .and_region_code(args.region_code) + .and_country_name(args.country_name) + .and_country_code(args.country_code) + .and_postal_code(args.postal_code) + .build(); + contact = contact.set_postal_address(postal_address); + let entity = Entity::builder() + .contact(contact) + .notices(args.object_args.notice.clone().to_notices()) + .remarks(args.object_args.remark.clone().to_remarks()) + .entities(entities(store, &args.object_args).await?) + .statuses(args.object_args.status.clone()) + .events(events(&args.object_args).unwrap_or_default()) + .links(links(&self_href).unwrap_or_default()) + .handle(args.handle); + let entity = entity.build(); + let id = RdapId::Entity(EntityId { + handle: entity + .object_common + .handle + .clone() + .expect("entity created without a handle"), + }); + let output = Output { + rdap: entity.to_response(), + id, + self_href, + }; + Ok(output) +} + +async fn make_nameserver( + args: Box, + store: &dyn StoreOps, +) -> Result { + let self_href = QueryType::ns(&args.ldh)? + .query_url(&args.object_args.base_url) + .expect("nameserver self href"); + let mut addrs: Vec = args.v4.clone(); + addrs.append(&mut args.v6.clone()); + let ns = Nameserver::builder() + .ldh_name(args.ldh) + .addresses(addrs) + .notices(args.object_args.notice.clone().to_notices()) + .remarks(args.object_args.remark.clone().to_remarks()) + .entities(entities(store, &args.object_args).await?) + .statuses(args.object_args.status.clone()) + .events(events(&args.object_args).unwrap_or_default()) + .links(links(&self_href).unwrap_or_default()) + .and_handle(args.handle); + let ns = ns.build()?; + let id = RdapId::Nameserver(NameserverId { + ldh_name: ns + .ldh_name + .clone() + .expect("nameserver created without ldhName"), + unicode_name: ns.unicode_name.clone(), + }); + let output = Output { + rdap: ns.to_response(), + id, + self_href, + }; + Ok(output) +} + +async fn make_domain( + args: Box, + store: &dyn StoreOps, +) -> Result { + // get ldh from idn u-label if ldh is not given + let ldh = if let Some(ldh_arg) = args.ldh.as_ref() { + ldh_arg.to_owned() + } else if let Some(idn_arg) = args.idn.as_ref() { + idna::domain_to_ascii(idn_arg) + .map_err(|_| RdapServerError::InvalidArg("Invalid IDN U-Lable".to_string()))? + } else { + panic!("neither ldh or idn specified. this should have been caught in arg parsing.") + } + + // get unicodeName (idn) from ldh if idn is not given + ; + let unicode_name = if let Some(idn_arg) = args.idn { + idn_arg + } else { + idna::domain_to_unicode(&ldh).0 + }; + + let self_href = QueryType::domain(&ldh)? + .query_url(&args.object_args.base_url) + .expect("domain self href"); + let secure_dns = if !args.ds.is_empty() + || args.zone_signed.is_some() + || args.delegation_signed.is_some() + || args.max_sig_life.is_some() + { + let secure_dns = SecureDns::builder() + .and_zone_signed(args.zone_signed) + .and_delegation_signed(args.delegation_signed) + .and_max_sig_life(args.max_sig_life) + .ds_datas(args.ds.clone()) + .build(); + Some(secure_dns) + } else { + None + }; + let domain = Domain::builder() + .ldh_name(ldh) + .unicode_name(unicode_name) + .and_secure_dns(secure_dns) + .nameservers(nameservers(store, args.ns).await?) + .notices(args.object_args.notice.clone().to_notices()) + .remarks(args.object_args.remark.clone().to_remarks()) + .entities(entities(store, &args.object_args).await?) + .statuses(args.object_args.status.clone()) + .events(events(&args.object_args).unwrap_or_default()) + .links(links(&self_href).unwrap_or_default()) + .and_handle(args.handle); + let domain = domain.build(); + let id = RdapId::Domain(DomainId { + ldh_name: domain + .ldh_name + .clone() + .expect("domain created without ldhName"), + unicode_name: domain.unicode_name.clone(), + }); + let output = Output { + rdap: domain.to_response(), + id, + self_href, + }; + Ok(output) +} + +async fn make_autnum( + args: Box, + store: &dyn StoreOps, +) -> Result { + let self_href = QueryType::AsNumber(args.start_autnum) + .query_url(&args.object_args.base_url) + .expect("autnum self href"); + let autnum_range = args.start_autnum..args.end_autnum.unwrap_or(args.start_autnum); + let autnum = Autnum::builder() + .autnum_range(autnum_range) + .and_autnum_type(args.autnum_type) + .and_country(args.country) + .and_name(args.name) + .notices(args.object_args.notice.clone().to_notices()) + .remarks(args.object_args.remark.clone().to_remarks()) + .entities(entities(store, &args.object_args).await?) + .statuses(args.object_args.status.clone()) + .events(events(&args.object_args).unwrap_or_default()) + .links(links(&self_href).unwrap_or_default()) + .and_handle(args.handle); + let autnum = autnum.build(); + let id = RdapId::Autnum(AutnumId { + start_autnum: autnum + .start_autnum + .as_ref() + .and_then(|n| n.as_u32()) + .expect("autnum created with no start"), + end_autnum: autnum + .end_autnum + .as_ref() + .and_then(|n| n.as_u32()) + .expect("autnum create with no end"), + }); + let output = Output { + rdap: autnum.to_response(), + id, + self_href, + }; + Ok(output) +} + +async fn make_network( + args: Box, + store: &dyn StoreOps, +) -> Result { + let self_href = match &args.cidr { + IpCidr::V4(cidr) => QueryType::ipv4cidr(&cidr.to_string())? + .query_url(&args.object_args.base_url) + .expect("ipv4 network self href"), + IpCidr::V6(cidr) => QueryType::ipv6cidr(&cidr.to_string())? + .query_url(&args.object_args.base_url) + .expect("ipv6 network self href"), + }; + let network = Network::builder() + .cidr(args.cidr.to_string()) + .and_country(args.country) + .and_name(args.name) + .and_network_type(args.network_type) + .and_parent_handle(args.parent_handle) + .notices(args.object_args.notice.clone().to_notices()) + .remarks(args.object_args.remark.clone().to_remarks()) + .entities(entities(store, &args.object_args).await?) + .statuses(args.object_args.status.clone()) + .events(events(&args.object_args).unwrap_or_default()) + .links(links(&self_href).unwrap_or_default()) + .and_handle(args.handle); + let network = network.build()?; + let id = RdapId::Netowrk(NetworkId { + network_id: icann_rdap_srv::storage::data::NetworkIdType::Range { + start_address: network + .start_address + .clone() + .expect("netowrk created without start address"), + end_address: network + .end_address + .clone() + .expect("network created without end address"), + }, + }); + let output = Output { + rdap: network.to_response(), + id, + self_href, + }; + Ok(output) +} + +fn make_help(args: SrvHelpArgs) -> Result { + let help = Help::builder().notices(args.notice.to_notices()).build(); + let output = Output { + rdap: help.to_response(), + id: RdapId::Help, + self_href: args.host.unwrap_or("__default".to_string()), + }; + Ok(output) +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use icann_rdap_common::response::DsDatum; + + use crate::{parse_ds_datum, parse_notice_or_remark}; + + #[test] + fn cli_debug_assert_test() { + use clap::CommandFactory; + crate::Cli::command().debug_assert() + } + + #[test] + fn GIVEN_notice_arg_WHEN_parse_THEN_correct() { + // GIVEN + let arg = "This is a notice."; + + // WHEN + let actual = parse_notice_or_remark(arg).expect("parsing notice"); + + // THEN + assert!( + Into::>::into(actual.description.expect("no description!")) + .contains(&arg.to_string()) + ); + } + + #[test] + fn GIVEN_notice_with_link_arg_WHEN_parse_THEN_correct() { + // GIVEN + let description = "This is a notice."; + let media_type = "text/html"; + let rel = "about"; + let href = "https://example.com/stuff"; + let arg = format!("({rel};{media_type})[{href}] {description}"); + + // WHEN + let actual = parse_notice_or_remark(&arg).expect("parsing notice"); + + // THEN + assert!(actual + .description + .expect("no description!") + .into_vec() + .contains(&description.to_string())); + let Some(links) = actual.links else { + panic!("no links in notice") + }; + let Some(link) = links.first() else { + panic!("links are empty") + }; + assert_eq!(link.rel.as_ref().expect("no rel in link"), rel); + assert_eq!(link.href.as_ref().expect("link has no href"), href); + assert_eq!( + link.media_type.as_ref().expect("no media_type in link"), + media_type + ); + } + + #[test] + fn GIVEN_ds_data_WHEN_parse_THEN_correct() { + // GIVEN + let data = "123456 1 2 THISISADIGEST"; + + // WHEN + let actual = parse_ds_datum(data).expect("parsing ds datum"); + + // THEN + let expected = DsDatum::builder() + .key_tag(123456) + .algorithm(1) + .digest_type(2) + .digest("THISISADIGEST".to_string()) + .build(); + assert_eq!(expected, actual); + } +} diff --git a/icann-rdap-srv/src/bin/rdap-srv-store.rs b/icann-rdap-srv/src/bin/rdap-srv-store.rs new file mode 100644 index 0000000..593b563 --- /dev/null +++ b/icann-rdap-srv/src/bin/rdap-srv-store.rs @@ -0,0 +1,302 @@ +use std::{net::IpAddr, path::PathBuf}; + +use { + clap::Parser, + icann_rdap_common::{ + check::CheckClass, + prelude::{Numberish, ToResponse}, + response::RdapResponse, + VERSION, + }, + icann_rdap_srv::{ + config::{data_dir, debug_config_vars, LOG}, + error::RdapServerError, + storage::data::{ + trigger_reload, trigger_update, AutnumOrError, DomainOrError, EntityOrError, + NameserverOrError, NetworkIdType, NetworkOrError, Template, + }, + util::bin::check::{check_rdap, to_check_classes, CheckArgs}, + }, + ipnet::IpNet, + serde_json::Value, + tracing::{debug, error, warn}, + tracing_subscriber::{ + fmt, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, EnvFilter, + }, +}; + +#[derive(Parser, Debug)] +#[command(author, version = VERSION, about, long_about)] +/// This program moves RDAP files into storage. Files are checked for validity +/// before moving them. +struct Cli { + /// Directory containg RDAP JSON files. + #[arg()] + directory: Option, + + #[clap(flatten)] + check_args: CheckArgs, + + /// Update storage. + /// + /// If true, storage is updated. + #[arg(long, required = false, conflicts_with = "reload")] + update: bool, + + /// Reload storage. + /// + /// If true, storage is completely reloaded. + #[arg(long, required = false, conflicts_with = "update")] + reload: bool, +} + +#[tokio::main(flavor = "multi_thread")] +async fn main() -> Result<(), RdapServerError> { + dotenv::dotenv().ok(); + let cli = Cli::parse(); + tracing_subscriber::registry() + .with(fmt::layer()) + .with(EnvFilter::from_env(LOG)) + .init(); + + debug_config_vars(); + + let check_types = to_check_classes(&cli.check_args); + + let data_dir = data_dir(); + + if let Some(directory) = cli.directory { + if directory == data_dir { + return Err(RdapServerError::InvalidArg( + "Source directory is same as data (destination) directory.".to_string(), + )); + } + do_validate_then_move(&directory, &check_types, &data_dir).await?; + } + + // signal update or reload + if cli.reload { + trigger_reload(&data_dir).await?; + } else if cli.update { + trigger_update(&data_dir).await?; + }; + + Ok(()) +} + +async fn do_validate_then_move( + directory: &str, + check_types: &[CheckClass], + data_dir: &str, +) -> Result<(), RdapServerError> { + // validate files + let src_path = PathBuf::from(directory); + if !src_path.exists() || !src_path.is_dir() { + error!( + "Source Directory {} does not exist or is not a directory.", + src_path.to_string_lossy() + ); + return Err(RdapServerError::Config( + "Source directory does not exist or is not a directory.".to_string(), + )); + }; + + let mut entries = tokio::fs::read_dir(src_path.clone()).await?; + let mut errors_found = false; + while let Some(entry) = entries.next_entry().await? { + let entry = entry.path(); + let contents = tokio::fs::read_to_string(&entry).await?; + if entry.extension().map_or(false, |ext| ext == "template") { + errors_found |= verify_rdap_template(&contents, &entry.to_string_lossy(), check_types)?; + } else if entry.extension().map_or(false, |ext| ext == "json") { + errors_found |= verify_rdap(&contents, &entry.to_string_lossy(), check_types)?; + } + } + if errors_found { + return Err(RdapServerError::ErrorOnChecks); + } + + // if all files validate, then move them + let dest_path = PathBuf::from(&data_dir); + if !dest_path.exists() || !dest_path.is_dir() { + warn!( + "Destination Directory {} does not exist or is not a directory.", + dest_path.to_string_lossy() + ); + return Err(RdapServerError::Config( + "Destination directory does not exist or is not a directory.".to_string(), + )); + }; + let mut entries = tokio::fs::read_dir(src_path).await?; + while let Some(entry) = entries.next_entry().await? { + let source = entry.path(); + let mut dest = dest_path.clone(); + dest.push(source.file_name().expect("cannot get source file name")); + tokio::fs::copy(source, dest).await?; + } + Ok(()) +} + +/// Verifies the RDAP JSON file. +fn verify_rdap( + contents: &str, + path_name: &str, + check_types: &[CheckClass], +) -> Result { + let mut errors_found = false; + debug!("verifying {path_name}"); + let json = serde_json::from_str::(contents); + if let Ok(value) = json { + let rdap = RdapResponse::try_from(value); + if let Ok(rdap) = rdap { + if check_rdap(rdap, check_types) { + errors_found = true; + } + } else { + error!("Non RDAP file at {}", path_name.to_owned()); + errors_found = true; + } + } else { + error!("Non JSON file at {}", path_name.to_owned()); + errors_found = true; + }; + Ok(errors_found) +} + +/// Verifies the template files. +fn verify_rdap_template( + contents: &str, + path_name: &str, + check_types: &[CheckClass], +) -> Result { + let mut errors_found = false; + debug!("processing {path_name} template"); + let json = serde_json::from_str::