Subversion Repositories php-qbpwcf

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
3 liveuser 1
<?php
2
 
3
namespace React\Dns\Resolver;
4
 
5
use React\Dns\Model\Message;
6
use React\Dns\Query\ExecutorInterface;
7
use React\Dns\Query\Query;
8
use React\Dns\RecordNotFoundException;
9
 
10
/**
11
 * @see ResolverInterface for the base interface
12
 */
13
final 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->name . ' 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->name . ' 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
}