friendship ended with social-app. php is my new best friend
1<?php
2
3namespace React\Dns\Resolver;
4
5use React\Dns\Model\Message;
6use React\Dns\Query\ExecutorInterface;
7use React\Dns\Query\Query;
8use React\Dns\RecordNotFoundException;
9
10/**
11 * @see ResolverInterface for the base interface
12 */
13final class Resolver implements ResolverInterface
14{
15 private $executor;
16
17 public function __construct(ExecutorInterface $executor)
18 {
19 $this->executor = $executor;
20 }
21
22 public function resolve($domain)
23 {
24 return $this->resolveAll($domain, Message::TYPE_A)->then(function (array $ips) {
25 return $ips[array_rand($ips)];
26 });
27 }
28
29 public function resolveAll($domain, $type)
30 {
31 $query = new Query($domain, $type, Message::CLASS_IN);
32 $that = $this;
33
34 return $this->executor->query(
35 $query
36 )->then(function (Message $response) use ($query, $that) {
37 return $that->extractValues($query, $response);
38 });
39 }
40
41 /**
42 * [Internal] extract all resource record values from response for this query
43 *
44 * @param Query $query
45 * @param Message $response
46 * @return array
47 * @throws RecordNotFoundException when response indicates an error or contains no data
48 * @internal
49 */
50 public function extractValues(Query $query, Message $response)
51 {
52 // reject if response code indicates this is an error response message
53 $code = $response->rcode;
54 if ($code !== Message::RCODE_OK) {
55 switch ($code) {
56 case Message::RCODE_FORMAT_ERROR:
57 $message = 'Format Error';
58 break;
59 case Message::RCODE_SERVER_FAILURE:
60 $message = 'Server Failure';
61 break;
62 case Message::RCODE_NAME_ERROR:
63 $message = 'Non-Existent Domain / NXDOMAIN';
64 break;
65 case Message::RCODE_NOT_IMPLEMENTED:
66 $message = 'Not Implemented';
67 break;
68 case Message::RCODE_REFUSED:
69 $message = 'Refused';
70 break;
71 default:
72 $message = 'Unknown error response code ' . $code;
73 }
74 throw new RecordNotFoundException(
75 'DNS query for ' . $query->describe() . ' returned an error response (' . $message . ')',
76 $code
77 );
78 }
79
80 $answers = $response->answers;
81 $addresses = $this->valuesByNameAndType($answers, $query->name, $query->type);
82
83 // reject if we did not receive a valid answer (domain is valid, but no record for this type could be found)
84 if (0 === count($addresses)) {
85 throw new RecordNotFoundException(
86 'DNS query for ' . $query->describe() . ' did not return a valid answer (NOERROR / NODATA)'
87 );
88 }
89
90 return array_values($addresses);
91 }
92
93 /**
94 * @param \React\Dns\Model\Record[] $answers
95 * @param string $name
96 * @param int $type
97 * @return array
98 */
99 private function valuesByNameAndType(array $answers, $name, $type)
100 {
101 // return all record values for this name and type (if any)
102 $named = $this->filterByName($answers, $name);
103 $records = $this->filterByType($named, $type);
104 if ($records) {
105 return $this->mapRecordData($records);
106 }
107
108 // no matching records found? check if there are any matching CNAMEs instead
109 $cnameRecords = $this->filterByType($named, Message::TYPE_CNAME);
110 if ($cnameRecords) {
111 $cnames = $this->mapRecordData($cnameRecords);
112 foreach ($cnames as $cname) {
113 $records = array_merge(
114 $records,
115 $this->valuesByNameAndType($answers, $cname, $type)
116 );
117 }
118 }
119
120 return $records;
121 }
122
123 private function filterByName(array $answers, $name)
124 {
125 return $this->filterByField($answers, 'name', $name);
126 }
127
128 private function filterByType(array $answers, $type)
129 {
130 return $this->filterByField($answers, 'type', $type);
131 }
132
133 private function filterByField(array $answers, $field, $value)
134 {
135 $value = strtolower($value);
136 return array_filter($answers, function ($answer) use ($field, $value) {
137 return $value === strtolower($answer->$field);
138 });
139 }
140
141 private function mapRecordData(array $records)
142 {
143 return array_map(function ($record) {
144 return $record->data;
145 }, $records);
146 }
147}