xref: /plugin/combo/vendor/php-webdriver/webdriver/lib/Firefox/FirefoxProfile.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
1*04fd306cSNickeau<?php
2*04fd306cSNickeau
3*04fd306cSNickeaunamespace Facebook\WebDriver\Firefox;
4*04fd306cSNickeau
5*04fd306cSNickeauuse Facebook\WebDriver\Exception\WebDriverException;
6*04fd306cSNickeauuse FilesystemIterator;
7*04fd306cSNickeauuse RecursiveDirectoryIterator;
8*04fd306cSNickeauuse RecursiveIteratorIterator;
9*04fd306cSNickeauuse ZipArchive;
10*04fd306cSNickeau
11*04fd306cSNickeauclass FirefoxProfile
12*04fd306cSNickeau{
13*04fd306cSNickeau    /**
14*04fd306cSNickeau     * @var array
15*04fd306cSNickeau     */
16*04fd306cSNickeau    private $preferences = [];
17*04fd306cSNickeau    /**
18*04fd306cSNickeau     * @var array
19*04fd306cSNickeau     */
20*04fd306cSNickeau    private $extensions = [];
21*04fd306cSNickeau    /**
22*04fd306cSNickeau     * @var array
23*04fd306cSNickeau     */
24*04fd306cSNickeau    private $extensions_datas = [];
25*04fd306cSNickeau    /**
26*04fd306cSNickeau     * @var string
27*04fd306cSNickeau     */
28*04fd306cSNickeau    private $rdf_file;
29*04fd306cSNickeau
30*04fd306cSNickeau    /**
31*04fd306cSNickeau     * @param string $extension The path to the xpi extension.
32*04fd306cSNickeau     * @return FirefoxProfile
33*04fd306cSNickeau     */
34*04fd306cSNickeau    public function addExtension($extension)
35*04fd306cSNickeau    {
36*04fd306cSNickeau        $this->extensions[] = $extension;
37*04fd306cSNickeau
38*04fd306cSNickeau        return $this;
39*04fd306cSNickeau    }
40*04fd306cSNickeau
41*04fd306cSNickeau    /**
42*04fd306cSNickeau     * @param string $extension_datas The path to the folder containing the datas to add to the extension
43*04fd306cSNickeau     * @return FirefoxProfile
44*04fd306cSNickeau     */
45*04fd306cSNickeau    public function addExtensionDatas($extension_datas)
46*04fd306cSNickeau    {
47*04fd306cSNickeau        if (!is_dir($extension_datas)) {
48*04fd306cSNickeau            return null;
49*04fd306cSNickeau        }
50*04fd306cSNickeau
51*04fd306cSNickeau        $this->extensions_datas[basename($extension_datas)] = $extension_datas;
52*04fd306cSNickeau
53*04fd306cSNickeau        return $this;
54*04fd306cSNickeau    }
55*04fd306cSNickeau
56*04fd306cSNickeau    /**
57*04fd306cSNickeau     * @param string $rdf_file The path to the rdf file
58*04fd306cSNickeau     * @return FirefoxProfile
59*04fd306cSNickeau     */
60*04fd306cSNickeau    public function setRdfFile($rdf_file)
61*04fd306cSNickeau    {
62*04fd306cSNickeau        if (!is_file($rdf_file)) {
63*04fd306cSNickeau            return null;
64*04fd306cSNickeau        }
65*04fd306cSNickeau
66*04fd306cSNickeau        $this->rdf_file = $rdf_file;
67*04fd306cSNickeau
68*04fd306cSNickeau        return $this;
69*04fd306cSNickeau    }
70*04fd306cSNickeau
71*04fd306cSNickeau    /**
72*04fd306cSNickeau     * @param string $key
73*04fd306cSNickeau     * @param string|bool|int $value
74*04fd306cSNickeau     * @throws WebDriverException
75*04fd306cSNickeau     * @return FirefoxProfile
76*04fd306cSNickeau     */
77*04fd306cSNickeau    public function setPreference($key, $value)
78*04fd306cSNickeau    {
79*04fd306cSNickeau        if (is_string($value)) {
80*04fd306cSNickeau            $value = sprintf('"%s"', $value);
81*04fd306cSNickeau        } else {
82*04fd306cSNickeau            if (is_int($value)) {
83*04fd306cSNickeau                $value = sprintf('%d', $value);
84*04fd306cSNickeau            } else {
85*04fd306cSNickeau                if (is_bool($value)) {
86*04fd306cSNickeau                    $value = $value ? 'true' : 'false';
87*04fd306cSNickeau                } else {
88*04fd306cSNickeau                    throw new WebDriverException(
89*04fd306cSNickeau                        'The value of the preference should be either a string, int or bool.'
90*04fd306cSNickeau                    );
91*04fd306cSNickeau                }
92*04fd306cSNickeau            }
93*04fd306cSNickeau        }
94*04fd306cSNickeau        $this->preferences[$key] = $value;
95*04fd306cSNickeau
96*04fd306cSNickeau        return $this;
97*04fd306cSNickeau    }
98*04fd306cSNickeau
99*04fd306cSNickeau    /**
100*04fd306cSNickeau     * @param mixed $key
101*04fd306cSNickeau     * @return mixed
102*04fd306cSNickeau     */
103*04fd306cSNickeau    public function getPreference($key)
104*04fd306cSNickeau    {
105*04fd306cSNickeau        if (array_key_exists($key, $this->preferences)) {
106*04fd306cSNickeau            return $this->preferences[$key];
107*04fd306cSNickeau        }
108*04fd306cSNickeau
109*04fd306cSNickeau        return null;
110*04fd306cSNickeau    }
111*04fd306cSNickeau
112*04fd306cSNickeau    /**
113*04fd306cSNickeau     * @return string
114*04fd306cSNickeau     */
115*04fd306cSNickeau    public function encode()
116*04fd306cSNickeau    {
117*04fd306cSNickeau        $temp_dir = $this->createTempDirectory('WebDriverFirefoxProfile');
118*04fd306cSNickeau
119*04fd306cSNickeau        if (isset($this->rdf_file)) {
120*04fd306cSNickeau            copy($this->rdf_file, $temp_dir . DIRECTORY_SEPARATOR . 'mimeTypes.rdf');
121*04fd306cSNickeau        }
122*04fd306cSNickeau
123*04fd306cSNickeau        foreach ($this->extensions as $extension) {
124*04fd306cSNickeau            $this->installExtension($extension, $temp_dir);
125*04fd306cSNickeau        }
126*04fd306cSNickeau
127*04fd306cSNickeau        foreach ($this->extensions_datas as $dirname => $extension_datas) {
128*04fd306cSNickeau            mkdir($temp_dir . DIRECTORY_SEPARATOR . $dirname);
129*04fd306cSNickeau            $iterator = new RecursiveIteratorIterator(
130*04fd306cSNickeau                new RecursiveDirectoryIterator($extension_datas, RecursiveDirectoryIterator::SKIP_DOTS),
131*04fd306cSNickeau                RecursiveIteratorIterator::SELF_FIRST
132*04fd306cSNickeau            );
133*04fd306cSNickeau            foreach ($iterator as $item) {
134*04fd306cSNickeau                $target_dir = $temp_dir . DIRECTORY_SEPARATOR . $dirname . DIRECTORY_SEPARATOR
135*04fd306cSNickeau                    . $iterator->getSubPathName();
136*04fd306cSNickeau
137*04fd306cSNickeau                if ($item->isDir()) {
138*04fd306cSNickeau                    mkdir($target_dir);
139*04fd306cSNickeau                } else {
140*04fd306cSNickeau                    copy($item, $target_dir);
141*04fd306cSNickeau                }
142*04fd306cSNickeau            }
143*04fd306cSNickeau        }
144*04fd306cSNickeau
145*04fd306cSNickeau        $content = '';
146*04fd306cSNickeau        foreach ($this->preferences as $key => $value) {
147*04fd306cSNickeau            $content .= sprintf("user_pref(\"%s\", %s);\n", $key, $value);
148*04fd306cSNickeau        }
149*04fd306cSNickeau        file_put_contents($temp_dir . '/user.js', $content);
150*04fd306cSNickeau
151*04fd306cSNickeau        // Intentionally do not use `tempnam()`, as it creates empty file which zip extension may not handle.
152*04fd306cSNickeau        $temp_zip = sys_get_temp_dir() . '/' . uniqid('WebDriverFirefoxProfileZip', false);
153*04fd306cSNickeau
154*04fd306cSNickeau        $zip = new ZipArchive();
155*04fd306cSNickeau        $zip->open($temp_zip, ZipArchive::CREATE);
156*04fd306cSNickeau
157*04fd306cSNickeau        $dir = new RecursiveDirectoryIterator($temp_dir);
158*04fd306cSNickeau        $files = new RecursiveIteratorIterator($dir);
159*04fd306cSNickeau
160*04fd306cSNickeau        $dir_prefix = preg_replace(
161*04fd306cSNickeau            '#\\\\#',
162*04fd306cSNickeau            '\\\\\\\\',
163*04fd306cSNickeau            $temp_dir . DIRECTORY_SEPARATOR
164*04fd306cSNickeau        );
165*04fd306cSNickeau
166*04fd306cSNickeau        foreach ($files as $name => $object) {
167*04fd306cSNickeau            if (is_dir($name)) {
168*04fd306cSNickeau                continue;
169*04fd306cSNickeau            }
170*04fd306cSNickeau
171*04fd306cSNickeau            $path = preg_replace("#^{$dir_prefix}#", '', $name);
172*04fd306cSNickeau            $zip->addFile($name, $path);
173*04fd306cSNickeau        }
174*04fd306cSNickeau        $zip->close();
175*04fd306cSNickeau
176*04fd306cSNickeau        $profile = base64_encode(file_get_contents($temp_zip));
177*04fd306cSNickeau
178*04fd306cSNickeau        // clean up
179*04fd306cSNickeau        $this->deleteDirectory($temp_dir);
180*04fd306cSNickeau        unlink($temp_zip);
181*04fd306cSNickeau
182*04fd306cSNickeau        return $profile;
183*04fd306cSNickeau    }
184*04fd306cSNickeau
185*04fd306cSNickeau    /**
186*04fd306cSNickeau     * @param string $extension The path to the extension.
187*04fd306cSNickeau     * @param string $profile_dir The path to the profile directory.
188*04fd306cSNickeau     * @return string The path to the directory of this extension.
189*04fd306cSNickeau     */
190*04fd306cSNickeau    private function installExtension($extension, $profile_dir)
191*04fd306cSNickeau    {
192*04fd306cSNickeau        $temp_dir = $this->createTempDirectory('WebDriverFirefoxProfileExtension');
193*04fd306cSNickeau        $this->extractTo($extension, $temp_dir);
194*04fd306cSNickeau
195*04fd306cSNickeau        // This is a hacky way to parse the id since there is no offical RDF parser library.
196*04fd306cSNickeau        // Find the correct namespace for the id element.
197*04fd306cSNickeau        $install_rdf_path = $temp_dir . '/install.rdf';
198*04fd306cSNickeau        $xml = simplexml_load_file($install_rdf_path);
199*04fd306cSNickeau        $ns = $xml->getDocNamespaces();
200*04fd306cSNickeau        $prefix = '';
201*04fd306cSNickeau        if (!empty($ns)) {
202*04fd306cSNickeau            foreach ($ns as $key => $value) {
203*04fd306cSNickeau                if (mb_strpos($value, '//www.mozilla.org/2004/em-rdf') > 0) {
204*04fd306cSNickeau                    if ($key != '') {
205*04fd306cSNickeau                        $prefix = $key . ':'; // Separate the namespace from the name.
206*04fd306cSNickeau                    }
207*04fd306cSNickeau                    break;
208*04fd306cSNickeau                }
209*04fd306cSNickeau            }
210*04fd306cSNickeau        }
211*04fd306cSNickeau        // Get the extension id from the install manifest.
212*04fd306cSNickeau        $matches = [];
213*04fd306cSNickeau        preg_match('#<' . $prefix . 'id>([^<]+)</' . $prefix . 'id>#', $xml->asXML(), $matches);
214*04fd306cSNickeau        if (isset($matches[1])) {
215*04fd306cSNickeau            $ext_dir = $profile_dir . '/extensions/' . $matches[1];
216*04fd306cSNickeau            mkdir($ext_dir, 0777, true);
217*04fd306cSNickeau            $this->extractTo($extension, $ext_dir);
218*04fd306cSNickeau        } else {
219*04fd306cSNickeau            $this->deleteDirectory($temp_dir);
220*04fd306cSNickeau
221*04fd306cSNickeau            throw new WebDriverException('Cannot get the extension id from the install manifest.');
222*04fd306cSNickeau        }
223*04fd306cSNickeau
224*04fd306cSNickeau        $this->deleteDirectory($temp_dir);
225*04fd306cSNickeau
226*04fd306cSNickeau        return $ext_dir;
227*04fd306cSNickeau    }
228*04fd306cSNickeau
229*04fd306cSNickeau    /**
230*04fd306cSNickeau     * @param string $prefix Prefix of the temp directory.
231*04fd306cSNickeau     *
232*04fd306cSNickeau     * @throws WebDriverException
233*04fd306cSNickeau     * @return string The path to the temp directory created.
234*04fd306cSNickeau     */
235*04fd306cSNickeau    private function createTempDirectory($prefix = '')
236*04fd306cSNickeau    {
237*04fd306cSNickeau        $temp_dir = tempnam(sys_get_temp_dir(), $prefix);
238*04fd306cSNickeau        if (file_exists($temp_dir)) {
239*04fd306cSNickeau            unlink($temp_dir);
240*04fd306cSNickeau            mkdir($temp_dir);
241*04fd306cSNickeau            if (!is_dir($temp_dir)) {
242*04fd306cSNickeau                throw new WebDriverException('Cannot create firefox profile.');
243*04fd306cSNickeau            }
244*04fd306cSNickeau        }
245*04fd306cSNickeau
246*04fd306cSNickeau        return $temp_dir;
247*04fd306cSNickeau    }
248*04fd306cSNickeau
249*04fd306cSNickeau    /**
250*04fd306cSNickeau     * @param string $directory The path to the directory.
251*04fd306cSNickeau     */
252*04fd306cSNickeau    private function deleteDirectory($directory)
253*04fd306cSNickeau    {
254*04fd306cSNickeau        $dir = new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS);
255*04fd306cSNickeau        $paths = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::CHILD_FIRST);
256*04fd306cSNickeau
257*04fd306cSNickeau        foreach ($paths as $path) {
258*04fd306cSNickeau            if ($path->isDir() && !$path->isLink()) {
259*04fd306cSNickeau                rmdir($path->getPathname());
260*04fd306cSNickeau            } else {
261*04fd306cSNickeau                unlink($path->getPathname());
262*04fd306cSNickeau            }
263*04fd306cSNickeau        }
264*04fd306cSNickeau
265*04fd306cSNickeau        rmdir($directory);
266*04fd306cSNickeau    }
267*04fd306cSNickeau
268*04fd306cSNickeau    /**
269*04fd306cSNickeau     * @param string $xpi The path to the .xpi extension.
270*04fd306cSNickeau     * @param string $target_dir The path to the unzip directory.
271*04fd306cSNickeau     *
272*04fd306cSNickeau     * @throws \Exception
273*04fd306cSNickeau     * @return FirefoxProfile
274*04fd306cSNickeau     */
275*04fd306cSNickeau    private function extractTo($xpi, $target_dir)
276*04fd306cSNickeau    {
277*04fd306cSNickeau        $zip = new ZipArchive();
278*04fd306cSNickeau        if (file_exists($xpi)) {
279*04fd306cSNickeau            if ($zip->open($xpi)) {
280*04fd306cSNickeau                $zip->extractTo($target_dir);
281*04fd306cSNickeau                $zip->close();
282*04fd306cSNickeau            } else {
283*04fd306cSNickeau                throw new \Exception("Failed to open the firefox extension. '$xpi'");
284*04fd306cSNickeau            }
285*04fd306cSNickeau        } else {
286*04fd306cSNickeau            throw new \Exception("Firefox extension doesn't exist. '$xpi'");
287*04fd306cSNickeau        }
288*04fd306cSNickeau
289*04fd306cSNickeau        return $this;
290*04fd306cSNickeau    }
291*04fd306cSNickeau}
292