1<?php
2
3namespace React\Dns\Resolver;
4
5use React\Cache\ArrayCache;
6use React\Cache\CacheInterface;
7use React\Dns\Config\HostsFile;
8use React\Dns\Query\CachingExecutor;
9use React\Dns\Query\CoopExecutor;
10use React\Dns\Query\ExecutorInterface;
11use React\Dns\Query\HostsFileExecutor;
12use React\Dns\Query\RetryExecutor;
13use React\Dns\Query\SelectiveTransportExecutor;
14use React\Dns\Query\TcpTransportExecutor;
15use React\Dns\Query\TimeoutExecutor;
16use React\Dns\Query\UdpTransportExecutor;
17use React\EventLoop\LoopInterface;
18
19final class Factory
20{
21    /**
22     * @param string        $nameserver
23     * @param LoopInterface $loop
24     * @return \React\Dns\Resolver\ResolverInterface
25     */
26    public function create($nameserver, LoopInterface $loop)
27    {
28        $executor = $this->decorateHostsFileExecutor($this->createExecutor($nameserver, $loop));
29
30        return new Resolver($executor);
31    }
32
33    /**
34     * @param string          $nameserver
35     * @param LoopInterface   $loop
36     * @param ?CacheInterface $cache
37     * @return \React\Dns\Resolver\ResolverInterface
38     */
39    public function createCached($nameserver, LoopInterface $loop, CacheInterface $cache = null)
40    {
41        // default to keeping maximum of 256 responses in cache unless explicitly given
42        if (!($cache instanceof CacheInterface)) {
43            $cache = new ArrayCache(256);
44        }
45
46        $executor = $this->createExecutor($nameserver, $loop);
47        $executor = new CachingExecutor($executor, $cache);
48        $executor = $this->decorateHostsFileExecutor($executor);
49
50        return new Resolver($executor);
51    }
52
53    /**
54     * Tries to load the hosts file and decorates the given executor on success
55     *
56     * @param ExecutorInterface $executor
57     * @return ExecutorInterface
58     * @codeCoverageIgnore
59     */
60    private function decorateHostsFileExecutor(ExecutorInterface $executor)
61    {
62        try {
63            $executor = new HostsFileExecutor(
64                HostsFile::loadFromPathBlocking(),
65                $executor
66            );
67        } catch (\RuntimeException $e) {
68            // ignore this file if it can not be loaded
69        }
70
71        // Windows does not store localhost in hosts file by default but handles this internally
72        // To compensate for this, we explicitly use hard-coded defaults for localhost
73        if (DIRECTORY_SEPARATOR === '\\') {
74            $executor = new HostsFileExecutor(
75                new HostsFile("127.0.0.1 localhost\n::1 localhost"),
76                $executor
77            );
78        }
79
80        return $executor;
81    }
82
83    private function createExecutor($nameserver, LoopInterface $loop)
84    {
85        $parts = \parse_url($nameserver);
86
87        if (isset($parts['scheme']) && $parts['scheme'] === 'tcp') {
88            $executor = $this->createTcpExecutor($nameserver, $loop);
89        } elseif (isset($parts['scheme']) && $parts['scheme'] === 'udp') {
90            $executor = $this->createUdpExecutor($nameserver, $loop);
91        } else {
92            $executor = new SelectiveTransportExecutor(
93                $this->createUdpExecutor($nameserver, $loop),
94                $this->createTcpExecutor($nameserver, $loop)
95            );
96        }
97
98        return new CoopExecutor($executor);
99    }
100
101    private function createTcpExecutor($nameserver, LoopInterface $loop)
102    {
103        return new TimeoutExecutor(
104            new TcpTransportExecutor($nameserver, $loop),
105            5.0,
106            $loop
107        );
108    }
109
110    private function createUdpExecutor($nameserver, LoopInterface $loop)
111    {
112        return new RetryExecutor(
113            new TimeoutExecutor(
114                new UdpTransportExecutor(
115                    $nameserver,
116                    $loop
117                ),
118                5.0,
119                $loop
120            )
121        );
122    }
123}
124