FirefoxProfile.php 8.7 KB

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