Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Domain/Constant/EventAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ final class EventAction extends Enum
public const REREGISTRATION = 'reregistration';
public const LAST_CHANGED = 'last changed';
public const EXPIRATION = 'expiration';
public const REGISTRAR_EXPIRATION = 'registrar expiration';
public const DELETION = 'deletion';
public const REINSTANTIATION = 'reinstantiation';
public const TRANSFER = 'transfer';
Expand Down
29 changes: 1 addition & 28 deletions src/Domain/Entity/Common.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,6 @@

abstract class Common
{
public const DEFAULT_RDAP_CONFORMANCE = 'rdap_level_0';

/**
* @var string[] The data structure named "rdapConformance" is an array of strings,
* each providing a hint as to the specifications used in the construction of the response.
* This data structure appears only in the topmost JSON object of a response.
*/
private $rdapConformance = [self::DEFAULT_RDAP_CONFORMANCE];

/**
* @var ObjectClassName
Expand Down Expand Up @@ -76,14 +68,6 @@ public function __construct(ObjectClassName $objectClassName)
$this->objectClassName = $objectClassName;
}

/**
* @return string[]
*/
public function getRdapConformance(): array
{
return $this->rdapConformance;
}

/**
* @return Link[]|null
*/
Expand Down Expand Up @@ -143,22 +127,11 @@ public function getPort43(): ?DomainName
/**
* @return Status[]|null
*/
public function getStatuses(): ?array
public function getStatus(): ?array
{
return $this->status;
}

/**
* @param string $rdapConformance
* @return Common
*/
public function addRdapConformance(string $rdapConformance): Common
{
$this->rdapConformance[] = $rdapConformance;

return $this;
}

/**
* @param Link $link
* @return Common
Expand Down
44 changes: 43 additions & 1 deletion src/Domain/Entity/Domain.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

final class Domain extends Common
{
use TopMostEntityTrait;

/**
* @var DomainName
*/
Expand Down Expand Up @@ -58,11 +60,16 @@ final class Domain extends Common
*/
private $network;

/** @var array */
private $redacted;

public function __construct(DomainName $ldhName)
{
parent::__construct(ObjectClassName::DOMAIN());

$this->ldhName = $ldhName->toLDH();

$this->secureDNS = new SecureDNS();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Verify eager SecureDNS initialization.

The constructor now always creates a SecureDNS() instance with default values (delegationSigned=false). Since the property is typed as SecureDNS|null, this changes the behavior to never return null from getSecureDNS().

Confirm this is intentional—previously the property could be null until explicitly set via setSecureDNS().


🏁 Script executed:

#!/bin/bash
# Description: Check how SecureDNS is used in domain construction and if null checks exist

# Look for null checks on getSecureDNS()
rg -nP --type=php -C3 '->getSecureDNS\(\)\s*(?:===|!==|==|!=)\s*null'

# Look for Domain construction patterns to see if SecureDNS is always set
rg -nP --type=php -C5 'new Domain\(' | head -50

Length of output: 4596



---

🏁 Script executed:

```shell
#!/bin/bash
# Inspect the Domain constructor signature
cat src/Domain/Entity/Domain.php | head -100

echo "---"

# Search for getSecureDNS() usage patterns (escaped properly)
rg -nP --type=php 'getSecureDNS' -C3

Length of output: 2882



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for null checks on SecureDNS (various patterns)
echo "=== Checking for null checks on getSecureDNS() ==="
rg -nP --type=php 'getSecureDNS\(\)' -C3

echo "=== Checking for secureDNS property access ==="
rg -nP --type=php 'secureDNS' -C2

echo "=== Checking SecureDNS constructor to understand default behavior ==="
fd -t f 'SecureDNS\.php' --exec head -80 {}

Length of output: 4060


Update return type annotation to reflect non-nullable contract.

The constructor now eagerly initializes SecureDNS with default values, ensuring getSecureDNS() never returns null. However, the return type annotation remains ?SecureDNS (line 123 in src/Domain/Entity/Domain.php), which is now inaccurate.

Change line 123 from:

public function getSecureDNS(): ?SecureDNS

to:

public function getSecureDNS(): SecureDNS

This reflects the actual contract that the property is always initialized after construction.

🤖 Prompt for AI Agents
In src/Domain/Entity/Domain.php (constructor change at line 72 and getter at
line 123), the constructor now eagerly sets $this->secureDNS so getSecureDNS()
is no longer nullable; update the method signature at line 123 from a nullable
return type to a non-nullable one by changing the declaration to return
SecureDNS (and update any PHPDoc/@return annotations accordingly), and run
static checks to ensure callers are compatible with the non-nullable return.

}

/**
Expand Down Expand Up @@ -123,7 +130,11 @@ public function getSecureDNS(): ?SecureDNS
*/
public function getEntities(): ?array
{
return $this->entities;
if (empty($this->entities['entities'])) {
return $this->entities ?? [];
}

return $this->entities['entities'];
}

/**
Expand Down Expand Up @@ -207,6 +218,7 @@ public function addEntity(Entity $entity): Domain
if ($this->entities === null) {
$this->entities = [];
}

$this->entities[] = $entity;

return $this;
Expand All @@ -222,4 +234,34 @@ public function setNetwork(IPNetwork $network): Domain

return $this;
}

public function getRedacted(): array
{
if (empty($this->redacted)) {
$this->setDefaultRedactedRules();
}

return $this->redacted;
}

private function setDefaultRedactedRules(): void
{
$this->redacted = [];
foreach (($this->getEntities() ?? []) as $k => $v) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused variable $v in foreach loop.

The loop variable $v is declared but never used. Consider using array_keys($this->getEntities() ?? []) to iterate over indices only.

Apply this diff:

-        foreach (($this->getEntities() ?? []) as $k => $v) {
+        foreach (array_keys($this->getEntities() ?? []) as $k) {

Based on static analysis.

🧰 Tools
🪛 PHPMD (2.15.0)

250-250: Avoid unused local variables such as '$v'. (undefined)

(UnusedLocalVariable)

🤖 Prompt for AI Agents
In src/Domain/Entity/Domain.php around line 250 the foreach declares ($k => $v)
but $v is never used; replace the iteration to only iterate keys, e.g. call
array_keys on the possibly-null array: foreach (array_keys($this->getEntities()
?? []) as $k) { ... } so only $k is declared and the unused $v is removed.

if ($v->getHandle() !== null) {
continue;
}
$this->redacted[] = [
'name' => [
'type' => 'Registry registrant ID',
],
'prePath' => "$.entities[{$k}].handle",
'pathLang' => "jsonpath",
'method' => 'removal',
'reason' => [
'description' => 'Server policy',
]
];
}
}
Comment on lines +247 to +266
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify entity ordering assumption.

The redaction rules assume entities are ordered as registrant (index 0), admin (1), tech (2), billing (3). The prePath references like "$.entities[{$k}].handle" rely on this exact ordering. Ensure that entity ordering is guaranteed when building the domain object, or the redaction rules will target incorrect entities.

Run the following script to verify how entities are added and if ordering is enforced:


🏁 Script executed:

#!/bin/bash
# Description: Check if entity ordering is enforced or documented

# Search for code that adds entities to Domain objects
rg -nP --type=php -C5 '->addEntity\(' | grep -A5 -B5 'Domain'

# Look for role-based entity ordering logic or documentation
rg -nP --type=php -C3 'registrant|admin|tech|billing' | grep -i 'entit'

# Check for any role constants or enums
ast-grep --pattern 'class Role {
  $$$
}'

Length of output: 2016


Now let me search for more details on the specific codebase structure:

Use role-based JSONPath filters instead of hardcoded positional indices for entity redaction rules.

The RFC 9537 standard explicitly warns against removing array elements by position where that position determines semantic meaning, and recommends using role-based JSONPath filters like $.entities[?(@.roles[0]=='administrative')] instead of positional indices.

The current code assumes entities are ordered as registrant (index 0), admin (1), tech (2), billing (3), but entity ordering in RDAP responses varies across servers and is not guaranteed. This will cause redaction rules to target incorrect entities when the server returns entities in a different order.

Replace the hardcoded indices with role-based filters to ensure the rules target the correct entities regardless of their position in the array.

🤖 Prompt for AI Agents
In src/Domain/Entity/Domain.php around lines 245-261, the redaction rules
currently use hardcoded array indices for entities which is fragile; change
prePath to use role-based JSONPath filters instead. For each role (registrant,
admin/administrative, tech/technical, billing) build the prePath using a
JSONPath filter that matches the entity's roles (e.g.
$.entities[?(@.roles[*]=='registrant')].handle or the appropriate variant for
administrative/technical) so the rule targets entities by role rather than
position; keep the rest of the rule fields (pathLang, method, reason) the same.
Ensure the role name variants used match the RDAP responses your system expects
(administrative vs admin, technical vs tech) and handle any pluralization/array
match with the JSONPath expression.

}
7 changes: 4 additions & 3 deletions src/Domain/Entity/Entity.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,19 +91,20 @@ public function setHandle(?string $handle): void
/**
* @return VCard[]|null
*/
public function getVcardArray(): ?array
public function getVcardArray()
{
return $this->vcardArray;
}

/**
* @param VCard $vcard
*/
public function addVcard(VCard $vcard): void
public function addVcard( $vcard): void
{
if (empty($this->vcardArray)) {
$this->vcardArray = [];
$this->vcardArray = ['vcard'];
}

$this->vcardArray[] = $vcard;
}
Comment on lines +102 to 109
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Removed type hints and confusing data structure initialization.

Several critical issues in this method:

  1. Removed type hint: The VCard type hint was removed from the $vcard parameter without justification, eliminating compile-time type safety.

  2. Confusing initialization: Line 105 initializes $vcardArray with ['vcard'] (a string), then line 108 appends $vcard objects. This creates a mixed-type array like ['vcard', VCard, VCard, ...], which is error-prone and confusing.

  3. Inconsistent with usage: The VcardNormalizer expects domain VCard objects, but this mixed structure (string + objects) may cause serialization issues.

Apply this diff to restore type safety and clarify the data structure:

     /**
      * @param VCard $vcard
      */
-    public function addVcard( $vcard): void
+    public function addVcard(VCard $vcard): void
     {
         if (empty($this->vcardArray)) {
-            $this->vcardArray = ['vcard'];
+            $this->vcardArray = [];
         }
-
         $this->vcardArray[] = $vcard;
     }

If the ['vcard'] prefix is intentional for jCard format compliance, please add a comment explaining this structure and verify the serialization path handles it correctly.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function addVcard( $vcard): void
{
if (empty($this->vcardArray)) {
$this->vcardArray = [];
$this->vcardArray = ['vcard'];
}
$this->vcardArray[] = $vcard;
}
public function addVcard(VCard $vcard): void
{
if (empty($this->vcardArray)) {
$this->vcardArray = [];
}
$this->vcardArray[] = $vcard;
}


Expand Down
48 changes: 48 additions & 0 deletions src/Domain/Entity/TopMostEntityTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);

namespace hiqdev\rdap\core\Domain\Entity;

/**
* Trait TopMostEntityTrait
*
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
*/
trait TopMostEntityTrait
{
/**
* @var string[] The data structure named "rdapConformance" is an array of strings,
* each providing a hint as to the specifications used in the construction of the response.
* This data structure appears only in the topmost JSON object of a response.
*/
private $rdapConformance = [
'rdap_level_0',
'icann_rdap_technical_implementation_guide_1',
'icann_rdap_response_profile_1',
'redacted'
];

/**
* @return string[]
*/
public function getRdapConformance(): array
{
return $this->rdapConformance;
}

/**
* @param string[] $rdapConformance
*/
public function setRdapConformance(array $rdapConformance): void
{
$this->rdapConformance = $rdapConformance;
}

/**
* @param string $rdapConformance
*/
public function addRdapConformance(string $rdapConformance): void
{
$this->rdapConformance[] = $rdapConformance;
}
}
130 changes: 130 additions & 0 deletions src/Domain/Entity/VCard.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php

namespace hiqdev\rdap\core\Domain\Entity;

final class VCard implements \JsonSerializable
{
const VERSION = '4.0';

/** @var array */
private $availableProperties = [
'version' => 'text',
'fn' => 'text',
'n' => 'text',
'tel' => 'text',
'email' => 'text',
'org' => 'text',
'adr' => 'text',
'url' => 'uri',
];

/** @var array */
private $vCard;

public function __construct(array $vCard = [])
{
$this->setVersion(self::VERSION);
array_merge($this->vCard, $vCard);
}
Comment on lines +24 to +28
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Constructor fails to merge provided array.

The array_merge result on line 27 is not assigned back to $this->vCard, so the constructor parameter is silently ignored. This will cause any VCard data passed to the constructor to be lost.

Apply this diff:

     public function __construct(array $vCard = [])
     {
         $this->setVersion(self::VERSION);
-        array_merge($this->vCard, $vCard);
+        $this->vCard = array_merge($this->vCard, $vCard);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function __construct(array $vCard = [])
{
$this->setVersion(self::VERSION);
array_merge($this->vCard, $vCard);
}
public function __construct(array $vCard = [])
{
$this->setVersion(self::VERSION);
$this->vCard = array_merge($this->vCard, $vCard);
}
🤖 Prompt for AI Agents
In src/Domain/Entity/VCard.php around lines 24 to 28, the constructor calls
array_merge but doesn't assign its result back to $this->vCard so the passed-in
$vCard is ignored; fix it by assigning the merged array to $this->vCard (e.g.
set $this->vCard = array_merge($this->vCard, $vCard)) so provided values are
preserved/override defaults, keeping the existing setVersion call intact.


public function jsonSerialize()
{
return array_values($this->vCard);
}

public function setVersion(string $version): VCard
{
return $this->setPropery('version', $version);
}

public function setFullName(string $name, array $properties = []): VCard
{
return $this->setPropery('fn', $name, $properties);
}

public function setName(string $name, array $properties = []): VCard
{
return $this->setPropery('n', $name, $properties);
}

public function setEmail(string $email, array $properties = []): VCard
{
return $this->setPropery('email', $email, $properties);
}

public function setOrg(?string $org = null, array $properties = []): VCard
{
if (empty($org)) {
return $this;
}

return $this->setPropery('org', $org, $properties);
}

public function setCompany(?string $company = null, array $properties = []): VCard
{
return $this->setOrg($company, $properties);
}

public function setUrl(string $url, array $properties = []): VCard
{
return $this->setPropery('url', $url, $properties);
}

public function setTel(string $tel, array $properties = []): VCard
{
if (empty($properties) || empty($properties['type'])) {
$properties['type'] = 'voice';
}

return $this->setPropery('tel', $tel, $properties);
}

public function setAddress(
string $street,
string $city,
?string $state = '',
string $zip,
?string $country = null,
string $ext = '',
string $name = '',
array $properties = []
): VCard {
$properties['cc'] = strtoupper($properties['cc'] ?? $country);

$this->vCard['adr'] = [
'adr',
$properties,
$this->availableProperties['adr'],
[
$name,
$ext,
$street,
$city,
$state,
$zip,
'',
],
];
return $this;
}

/**
* @return array
*/
public function getVCard(): array
{
return $this->vCard;
}

protected function setPropery(string $property, string $value, array $properties = [], string $type = 'text'): VCard
{
$this->vCard[$property] = [
$property,
empty($properties) ? new \stdClass() : $properties,
$this->availableProperties[$property] ?? $type,
$value
];
return $this;
}
Comment on lines +120 to +129
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Undefined property reference and typo in method name.

Line 127 references $this->availableProperties which doesn't exist (the actual property is $availableProperies with a typo at line 10). This will cause a runtime error. Additionally, the method name setPropery is missing a 't'.

After fixing the property name at line 10, apply this diff:

-    protected function setPropery(string $property, string $value, array $properties = [], string $type = 'text'): VCard
+    protected function setProperty(string $property, string $value, array $properties = [], string $type = 'text'): VCard
     {
         $this->vCard[$property] = [
             $property,
             empty($properties) ? new \stdClass() : $properties,
             $this->availableProperties[$property] ?? $type,
             $value
         ];
         return $this;
     }

And update all calls to setPropery throughout the class to setProperty.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/Domain/Entity/VCard.php around lines 122 to 131 (and line 10), the method
name setPropery is misspelled and the code references $this->availableProperties
which doesn't exist because the class property was declared as
$availableProperies (typo). Rename the class property at its declaration (line
~10) from availableProperies to availableProperties, rename the method
setPropery to setProperty, and update every call to setPropery within this class
to setProperty so the property reference and method name are consistent and the
runtime error is resolved.

}
6 changes: 3 additions & 3 deletions src/Domain/ValueObject/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ final class Event
/**
* @var Link[]
*/
private $links = [];
private $links;
Comment on lines 34 to +36
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Update the docblock to reflect the nullable type.

The docblock declares @var Link[], but the property can now be null since it's no longer initialized and getLinks() returns ?array.

Apply this diff to update the docblock:

     /**
-     * @var Link[]
+     * @var Link[]|null
      */
     private $links;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* @var Link[]
*/
private $links = [];
private $links;
/**
* @var Link[]|null
*/
private $links;
🤖 Prompt for AI Agents
In src/Domain/ValueObject/Event.php around lines 34 to 36, the property docblock
currently says "@var Link[]" but the property can be null and getLinks() returns
?array; update the docblock to reflect the nullable type by changing it to "@var
Link[]|null" (or "Link[]|null") so the documentation and static analyzers match
the actual nullable property; no other code changes required.


private function __construct(EventAction $eventAction, DateTimeImmutable $eventDate)
{
Expand Down Expand Up @@ -88,8 +88,8 @@ public function getEventDate(): DateTimeImmutable
/**
* @return Link[]
*/
public function getLinks(): array
public function getLinks(): ?array
{
return $this->links;
return $this->links ?? null;
}
}
4 changes: 2 additions & 2 deletions src/Domain/ValueObject/Link.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public function setType(string $type): void
*/
public function getType(): ?string
{
return $this->type;
return $this->type ?? 'text/html';
}

/**
Expand Down Expand Up @@ -148,7 +148,7 @@ public function setTitle(string $title): void
*/
public function getValue(): ?string
{
return $this->value;
return $this->value ?? $this->getHref();
}

/**
Expand Down
Loading