nsXPInstall.php 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
  1. <?php
  2. // This Source Code Form is subject to the terms of the Mozilla Public
  3. // License, v. 2.0. If a copy of the MPL was not distributed with this
  4. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  5. // DO NOT EXECUTE THIS
  6. print("This operation has been canceled due to restrictions in effect in this source file.. Namely this message.");
  7. exit(1);
  8. class classXPInstall {
  9. /********************************************************************************************************************
  10. * Known Application IDs
  11. * Application IDs are normally in the form of a {GUID} or user@host ID.
  12. *
  13. * Mozilla Suite: {86c18b42-e466-45a9-ae7a-9b95ba6f5640}
  14. * Firefox: {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
  15. * Thunderbird: {3550f703-e582-4d05-9a08-453d09bdfdc6} (Also, Interlink Mail & News)
  16. * SeaMonkey: {92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}
  17. * Fennec (Android): {aa3c5121-dab2-40e2-81ca-7ea25febc110}
  18. * Fennec (XUL): {a23983c0-fd0e-11dc-95ff-0800200c9a66}
  19. * Sunbird: {718e30fb-e89b-41dd-9da7-e25a45638b28}
  20. * Instantbird: {33cb9019-c295-46dd-be21-8c4936574bee}
  21. * Netscape Browser: {3db10fab-e461-4c80-8b97-957ad5f8ea47}
  22. *
  23. * Nvu: {136c295a-4a5a-41cf-bf24-5cee526720d5}
  24. * Flock: {a463f10c-3994-11da-9945-000d60ca027b}
  25. * Kompozer: {20aa4150-b5f4-11de-8a39-0800200c9a66}
  26. * BlueGriffon: bluegriffon@bluegriffon.com
  27. * Adblock Browser: {55aba3ac-94d3-41a8-9e25-5c21fe874539}
  28. * Postbox: postbox@postbox-inc.com
  29. *
  30. * Pale Moon 25+: {8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
  31. * Borealis 0.9: {a3210b97-8e8a-4737-9aa0-aa0e607640b9}
  32. * Ambassador (Standalone): {4523665a-317f-4a66-9376-3763d1ad1978} (Soft-abandoned, also, same as extension)
  33. * XUL Example: example@uxp.app
  34. *
  35. * IceDove-UXP: {3aa07e56-beb0-47a0-b0cb-c735edd25419}
  36. * IceApe-UXP: {9184b6fe-4a5c-484d-8b4b-efbfccbfb514}
  37. */
  38. /* ----------------------------------------------------------------------------------------------------------------- */
  39. /* Olympia Add-on Types
  40. * ADDON_ANY = 0
  41. * ADDON_EXTENSION = 1
  42. * ADDON_THEME = 2
  43. * ADDON_DICT = 3
  44. * ADDON_SEARCH = 4
  45. * ADDON_LPAPP = 5 XXXTobin: This seems to be the PROPER locale type originally defined in XPInstall
  46. * ADDON_LPADDON = 6 XXXTobin: What the hell is the difference between LPAPP and LPADDON?!
  47. * ADDON_PLUGIN = 7
  48. * ADDON_API = 8 XXXOlympia: not actually a type but used to identify extensions + themes
  49. * XXXTobin: Are these actual multipackage or on-the-fly multipackage via AMO Collections?
  50. * ADDON_PERSONA = 9
  51. * ADDON_WEBAPP = 11 XXXOlympia: Calling this ADDON_* is gross but we've gotta ship code.
  52. * XXXTobin: no1curr
  53. */
  54. /* ----------------------------------------------------------------------------------------------------------------- */
  55. /* Olympia Update Types
  56. * ADDON_EXTENSION : 'extension',
  57. * ADDON_THEME : 'theme',
  58. * ADDON_DICT : 'extension', XXXTobin: extensions.. Really?
  59. * ADDON_SEARCH : 'search', XXXTobin: We may never find out how this was intended to be handled.
  60. * ADDON_LPAPP : 'item',
  61. * ADDON_LPADDON : 'extension', XXXTobin: See Olympia Add-on Types
  62. * ADDON_PERSONA : 'background-theme', XXXTobin: Ditto re: search
  63. * ADDON_PLUGIN : 'plugin',
  64. ********************************************************************************************************************/
  65. /********************************************************************************************************************
  66. * Class constants
  67. ********************************************************************************************************************/
  68. /* Features are as follows:
  69. * 'e-cat', 't-cat', 'p-cat', 'unified', 'disable-xpinstall',
  70. * 'extensions', 'themes', 'language-packs', 'dictionaries',
  71. * 'search-plugins', 'personas', 'user-scripts', 'user-styles'
  72. */
  73. const TARGET_APPLICATION = array(
  74. 'toolkit' => array(
  75. 'id' => 'toolkit@mozilla.org',
  76. 'bit' => 1,
  77. 'minVersion' => '5.0.0a1',
  78. 'maxVersion' => '5.*',
  79. 'maxOldVersion' => '4.*',
  80. 'domain' => 'addons.thereisonlyxul.org',
  81. 'unified' => false,
  82. 'name' => 'Goanna Runtime Environment',
  83. 'shortName' => 'GRE',
  84. 'commonType' => 'platform',
  85. 'vendor' => 'GRE Alliance',
  86. 'siteTitle' => EMPTY_STRING,
  87. 'features' => EMPTY_ARRAY
  88. ),
  89. 'borealis' => array(
  90. 'id' => '{86c18b42-e466-4580-8b97-957ad5f8ea47}',
  91. 'bit' => 2,
  92. 'minVersion' => '8.5.7900a1',
  93. 'maxVersion' => '8.5.8400',
  94. 'maxOldVersion' => '8.4.*',
  95. 'domain' => 'addons.binaryoutcast.com',
  96. 'unified' => true,
  97. 'name' => 'Borealis Navigator',
  98. 'shortName' => 'Borealis',
  99. 'commonType' => 'navigator',
  100. 'vendor' => 'Binary Outcast',
  101. 'siteTitle' => 'Add-ons - Binary Outcast',
  102. 'features' => ['extensions', 'themes', 'dictionaries', 'search-plugins']
  103. ),
  104. 'interlink' => array(
  105. 'id' => '{3550f703-e582-4d05-9a08-453d09bdfdc6}',
  106. 'bit' => 4,
  107. 'minVersion' => '52.9.7900a1',
  108. 'maxVersion' => '52.9.8400',
  109. 'maxOldVersion' => '52.9.7899', /* Basically irrelevant for non-web clients */
  110. 'domain' => 'addons.binaryoutcast.com',
  111. 'unified' => true,
  112. 'name' => 'Interlink Mail &amp; News',
  113. 'shortName' => 'Interlink',
  114. 'commonType' => 'client',
  115. 'vendor' => 'Binary Outcast',
  116. 'siteTitle' => 'Add-ons - Binary Outcast',
  117. 'features' => ['disable-xpinstall', 'extensions', 'themes', 'dictionaries', 'search-plugins']
  118. ),
  119. );
  120. // ------------------------------------------------------------------------------------------------------------------
  121. const XPINSTALL_TYPES = array(
  122. 'app' => 1, // No longer applicable
  123. 'extension' => 2,
  124. 'theme' => 4,
  125. 'locale' => 8,
  126. 'plugin' => 16, // No longer applicable
  127. 'multipackage' => 32, // Forbidden on Phobos
  128. 'dictionary' => 64,
  129. 'experiment' => 128, // No longer applicable
  130. 'apiextension' => 256, // No longer applicable
  131. 'external' => 512, // Phobos only
  132. 'persona' => 1024, // Phobos only
  133. 'search-plugin' => 2048, // Phobos only
  134. 'user-script' => 4096, // Phobos only
  135. 'user-style' => 8192, // Phobos only
  136. );
  137. // These are the supported "real" XPInstall types
  138. const VALID_XPI_TYPES = self::XPINSTALL_TYPES['extension'] | self::XPINSTALL_TYPES['theme'] |
  139. self::XPINSTALL_TYPES['locale'] | self::XPINSTALL_TYPES['dictionary'];
  140. // These are add-on types only Phobos understands. They are NOT installable in the application directly
  141. // We will treat them as any other xpi but deliver them to the client in different ways
  142. const EXTRA_XPI_TYPES = self::XPINSTALL_TYPES['persona'] | self::XPINSTALL_TYPES['search-plugin'] |
  143. self::XPINSTALL_TYPES['user-script'] | self::XPINSTALL_TYPES['user-style'];
  144. // These are unsupported "real" XPInstall types (plus external because it is completely virtual)
  145. const INVALID_XPI_TYPES = self::XPINSTALL_TYPES['app'] | self::XPINSTALL_TYPES['plugin'] | self::XPINSTALL_TYPES['multipackage'] |
  146. self::XPINSTALL_TYPES['experiment'] | self::XPINSTALL_TYPES['apiextension'] | self::XPINSTALL_TYPES['external'];
  147. // Originally XPInstall only needed to a handful of types since it was killed much refactoring and Olympia reused
  148. // older types. We are gonna match that for now even if they aren't actually implemented.
  149. const AUS_XPI_TYPES = array(
  150. self::XPINSTALL_TYPES['extension'] => 'extension',
  151. self::XPINSTALL_TYPES['theme'] => 'theme',
  152. self::XPINSTALL_TYPES['dictionary'] => 'extension',
  153. self::XPINSTALL_TYPES['search-plugin'] => 'search',
  154. self::XPINSTALL_TYPES['locale'] => 'item',
  155. self::XPINSTALL_TYPES['persona'] => 'background-theme',
  156. );
  157. // Add-ons Manager Search uses the Olympia types so map the XPInstall Types to Olympia which match the Add-ons Manager
  158. const SEARCH_XPI_TYPES = array(
  159. self::XPINSTALL_TYPES['extension'] => 1,
  160. self::XPINSTALL_TYPES['theme'] => 2,
  161. self::XPINSTALL_TYPES['dictionary'] => 3,
  162. self::XPINSTALL_TYPES['search-plugin'] => 4,
  163. self::XPINSTALL_TYPES['locale'] => 5,
  164. self::XPINSTALL_TYPES['persona'] => 9,
  165. );
  166. // ------------------------------------------------------------------------------------------------------------------
  167. const MANIFEST_FILES = array(
  168. 'xpinstall' => 'install.js',
  169. 'rdfinstall' => RDF_INSTALL_MANIFEST,
  170. 'jsoninstall' => JSON_INSTALL_MANIFEST,
  171. 'chrome' => 'chrome.manifest',
  172. 'bootstrap' => 'bootstrap.js',
  173. 'cfxJetpack' => 'harness-options.json',
  174. 'npmJetpack' => 'package.json',
  175. 'webex' => 'manifest.json',
  176. );
  177. // ------------------------------------------------------------------------------------------------------------------
  178. // Define the specific technology that Add-ons can have
  179. const ADDON_TECHNOLOGY = ['overlay' => 1, 'xpcom' => 2, 'bootstrap' => 4, 'jetpack' => 8];
  180. // These ID fragments are NOT allowed anywhere in an Add-on ID unless you are a member of the Add-ons Team or higher
  181. const RESTRICTED_IDS = array(
  182. 'bfc5-fc555c87dbc4', // Moonchild Productions
  183. '9376-3763d1ad1978', // Pseudo-Static
  184. '9aa0-aa0e607640b9', // Binary Outcast
  185. 'moonchild', // Moonchild Productions
  186. 'palemoon', // Moonchild Productions
  187. 'basilisk', // Moonchild Productions
  188. 'binaryoutcast', // Binary Outcast
  189. 'mattatobin', // Binary Outcast
  190. 'thereisonlyxul',
  191. 'mozilla.org',
  192. 'lootyhoof', // Ryan
  193. 'srazzano', // BANNED FOR LIFE
  194. 'justoff', // BANNED FOR LIFE
  195. );
  196. // ------------------------------------------------------------------------------------------------------------------
  197. const SECTIONS = array(
  198. 'extensions' => array('type' => self::XPINSTALL_TYPES['extension'],
  199. 'name' => 'Extensions',
  200. 'description' =>
  201. 'Extensions are small add-ons that add new functionality to {%APPLICATION_SHORTNAME},' . SPACE .
  202. 'from a simple toolbar button to a completely new feature.' . SPACE .
  203. 'They allow you to customize the {%APPLICATION_COMMONTYPE} to fit your own needs' . SPACE .
  204. 'and preferences, while keeping the core itself light and lean.'
  205. ),
  206. 'themes' => array('type' => self::XPINSTALL_TYPES['theme'],
  207. 'name' => 'Themes',
  208. 'description' =>
  209. 'Themes allow you to change the look and feel of the user interface' . SPACE .
  210. 'and personalize it to your tastes.' . SPACE .
  211. 'A theme can simply change the colors of the UI or it can change every aspect of its appearance.'
  212. ),
  213. 'language-packs' => array('type' => self::XPINSTALL_TYPES['locale'],
  214. 'name' => 'Language Packs',
  215. 'description' => 'These add-ons provide strings for the user interface in your local language.'
  216. ),
  217. 'dictionaries' => array('type' => self::XPINSTALL_TYPES['dictionary'],
  218. 'name' => 'Dictionaries',
  219. 'description' =>
  220. '{%APPLICATION_SHORTNAME} has spell checking features, with this type of add-on' . SPACE .
  221. 'you can add check the spelling in additional languages.'
  222. ),
  223. 'personas' => array('type' => self::XPINSTALL_TYPES['persona'],
  224. 'name' => 'Personas',
  225. 'description' => 'Lightweight themes which allow you personalize {%APPLICATION_SHORTNAME} further.'
  226. ),
  227. 'search-plugins' => array('type' => self::XPINSTALL_TYPES['search-plugin'],
  228. 'name' => 'Search Plugins',
  229. 'description' =>
  230. 'A search plugin provides the ability to access a search engine from a web browser,' . SPACE .
  231. 'without having to go to the engine\'s website first.<br />' .
  232. 'Technically, a search plugin is a small Extensible Markup Language file that tells' . SPACE .
  233. 'the browser what information to send to a search engine and how the results are to be retrieved. '
  234. ),
  235. 'user-scripts' => ['type' => self::XPINSTALL_TYPES['user-script'], 'name' => 'User Scripts', 'description' => null],
  236. 'user-styles' => ['type' => self::XPINSTALL_TYPES['user-style'], 'name' => 'User Styles', 'description' => null],
  237. );
  238. // ------------------------------------------------------------------------------------------------------------------
  239. const CATEGORIES = array(
  240. 'unlisted' => ['bit' => 0, 'name' => 'Unlisted', 'type' => 0],
  241. 'alerts-and-updates' => ['bit' => 1, 'name' => 'Alerts &amp; Updates',
  242. 'type' => self::XPINSTALL_TYPES['extension']],
  243. 'appearance' => ['bit' => 2, 'name' => 'Appearance',
  244. 'type' => self::XPINSTALL_TYPES['extension']],
  245. 'bookmarks-and-tabs' => ['bit' => 4, 'name' => 'Bookmarks &amp; Tabs',
  246. 'type' => self::XPINSTALL_TYPES['extension']],
  247. 'download-management' => ['bit' => 8, 'name' => 'Download Management',
  248. 'type' => self::XPINSTALL_TYPES['extension'],],
  249. 'feeds-news-and-blogging' => ['bit' => 16, 'name' => 'Feeds, News, &amp; Blogging',
  250. 'type' => self::XPINSTALL_TYPES['extension'],],
  251. 'privacy-and-security' => ['bit' => 32, 'name' => 'Privacy &amp; Security',
  252. 'type' => self::XPINSTALL_TYPES['extension']],
  253. 'search-tools' => ['bit' => 64, 'name' => 'Search Tools',
  254. 'type' => self::XPINSTALL_TYPES['extension']],
  255. 'social-and-communication' => ['bit' => 128, 'name' => 'Social &amp; Communication',
  256. 'type' => self::XPINSTALL_TYPES['extension']],
  257. 'tools-and-utilities' => ['bit' => 256, 'name' => 'Tools &amp; Utilities',
  258. 'type' => self::XPINSTALL_TYPES['extension']],
  259. 'web-development' => ['bit' => 512, 'name' => 'Web Development',
  260. 'type' => self::XPINSTALL_TYPES['extension']],
  261. 'abstract' => ['bit' => 1024, 'name' => 'Abstract',
  262. 'type' => self::XPINSTALL_TYPES['persona']],
  263. 'brands' => ['bit' => 4096, 'name' => 'Brands',
  264. 'type' => self::XPINSTALL_TYPES['persona']],
  265. 'compact' => ['bit' => 8192, 'name' => 'Compact',
  266. 'type' => self::XPINSTALL_TYPES['theme']],
  267. 'dark' => ['bit' => 16384, 'name' => 'Dark',
  268. 'type' => self::XPINSTALL_TYPES['theme'] | self::XPINSTALL_TYPES['persona']],
  269. 'large' => ['bit' => 32768, 'name' => 'Large',
  270. 'type' => self::XPINSTALL_TYPES['theme']],
  271. 'modern' => ['bit' => 65536, 'name' => 'Modern',
  272. 'type' => self::XPINSTALL_TYPES['theme']],
  273. 'music' => ['bit' => 131072, 'name' => 'Music',
  274. 'type' => self::XPINSTALL_TYPES['persona']],
  275. 'nature' => ['bit' => 262144, 'name' => 'nature',
  276. 'type' => self::XPINSTALL_TYPES['persona']],
  277. 'other-web-clients' => ['bit' => 524288, 'name' => 'Browsers, Explorers, &amp; Navigators',
  278. 'type' => self::XPINSTALL_TYPES['theme']],
  279. 'retro' => ['bit' => 1048576, 'name' => 'Retro &amp; Classic',
  280. 'type' => self::XPINSTALL_TYPES['theme'] | self::XPINSTALL_TYPES['persona']],
  281. 'os-integration' => ['bit' => 2097152, 'name' => 'OS Integration',
  282. 'type' => self::XPINSTALL_TYPES['theme']],
  283. 'scenery' => ['bit' => 4194304, 'name' => 'Scenery',
  284. 'type' => self::XPINSTALL_TYPES['persona']],
  285. 'seasonal' => ['bit' => 8388608, 'name' => 'Seasonal',
  286. 'type' => self::XPINSTALL_TYPES['persona']],
  287. 'other' => ['bit' => 16777216, 'name' => 'Other',
  288. 'type' => self::XPINSTALL_TYPES['extension'] | self::XPINSTALL_TYPES['theme'] | self::XPINSTALL_TYPES['persona']],
  289. );
  290. // ------------------------------------------------------------------------------------------------------------------
  291. // Open Source Licenses users can set for their Add-ons
  292. const LICENSES = array(
  293. 'Apache-2.0' => 'Apache License 2.0',
  294. 'Apache-1.1' => 'Apache License 1.1',
  295. 'BSD-3-Clause' => 'BSD 3-Clause',
  296. 'BSD-2-Clause' => 'BSD 2-Clause',
  297. 'GPL-3.0' => 'GNU General Public License 3.0',
  298. 'GPL-2.0' => 'GNU General Public License 2.0',
  299. 'LGPL-3.0' => 'GNU Lesser General Public License 3.0',
  300. 'LGPL-2.1' => 'GNU Lesser General Public License 2.1',
  301. 'AGPL-3.0' => 'GNU Affero General Public License v3',
  302. 'MIT' => 'MIT License',
  303. 'MPL-2.0' => 'Mozilla Public License 2.0',
  304. 'MPL-1.1' => 'Mozilla Public License 1.1',
  305. 'PD' => 'Public Domain',
  306. 'COPYRIGHT' => '&copy;',
  307. 'Custom' => 'Custom License',
  308. );
  309. /********************************************************************************************************************
  310. * Class constructor that sets initial state of things
  311. ********************************************************************************************************************/
  312. function __construct() {
  313. return true;
  314. }
  315. // XML Stuff and Things
  316. const RDF_NS = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
  317. const EM_NS = 'http://www.mozilla.org/2004/em-rdf#';
  318. const MF_RES = 'urn:mozilla:install-manifest';
  319. const ANON_PREFIX = '#genid';
  320. // ------------------------------------------------------------------------------------------------------------------
  321. // Single properties
  322. // em:multiprocessCompatible' em:hasEmbeddedWebExtension are considered invalid for gregoriantojd
  323. // The following props only currently matter to Phobos. We /may/ add these to the Add-ons Manager at some point.
  324. // em:slug, em:category, em:license, em:repositoryURL, em:supportURL, and em:supportEmail
  325. const SINGLE_PROPS = ['id', 'type', 'version', 'creator', 'homepageURL', 'updateURL', 'updateKey', 'bootstrap',
  326. 'skinnable', 'strictCompatibility', 'iconURL', 'icon64URL', 'optionsURL', 'optionsType',
  327. 'aboutURL', 'iconURL', 'unpack', 'multiprocessCompatible', 'hasEmbeddedWebExtension',
  328. 'slug', 'category', 'license', 'repositoryURL', 'supportURL', 'supportEmail'];
  329. // Multiple properties (because this is shared with other methods we use a class constant)
  330. // According to documentation, em:file is supposed to be used as a fallback when no chrome.manifest exists.
  331. // It would then use em:file and old style contents.rdf to generate a chrome manifest but I cannot find
  332. // any existing code to facilitate this at our level.
  333. // em:additionalLicenses is a Phobos-only multi-prop
  334. const MULTI_PROPS = ['contributor', 'developer', 'translator', 'otherLicenses',
  335. 'targetPlatform', 'localized', 'targetApplication'];
  336. /********************************************************************************************************************
  337. * Parses install.rdf using Rdf_parser class
  338. *
  339. * @param string $aManifestData
  340. * @return array $data["manifest"]
  341. ********************************************************************************************************************/
  342. public function parseInstallManifest($aManifestData, $aEmDevloperMerge = null) {
  343. $data = EMPTY_ARRAY;
  344. // ----------------------------------------------------------------------------------------------------------------
  345. // Setup the repat rdf parser
  346. require_once(LIBRARIES['rdfParser']);
  347. $rdf = new Rdf_parser();
  348. $rdf->rdf_parser_create(null);
  349. $rdf->rdf_set_user_data($data);
  350. $rdf->rdf_set_statement_handler(['classAviary', 'mfStatementHandler']);
  351. $rdf->rdf_set_base(EMPTY_STRING);
  352. // If the install manifest can't be parsed return why as a string.
  353. if (!$rdf->rdf_parse($aManifestData, strlen($aManifestData), true)) {
  354. $parseError = 'RDF Parsing Error' . COLON . SPACE .
  355. xml_error_string(xml_get_error_code($rdf->rdf_parser['xml_parser'])) . NEW_LINE .
  356. 'Line Number' . SPACE .
  357. xml_get_current_line_number($rdf->rdf_parser['xml_parser']) . SPACE .
  358. ', Column' . SPACE . xml_get_current_column_number($rdf->rdf_parser['xml_parser']) . DOT;
  359. return $parseError;
  360. }
  361. // ----------------------------------------------------------------------------------------------------------------
  362. // We need to resolve em:localized by attaching the associated genid data into the manifest data
  363. if (array_key_exists('localized', $data['manifest']) &&
  364. is_array($data['manifest']['localized'])) {
  365. $localized = ['name' => EMPTY_ARRAY, 'description' => EMPTY_ARRAY, 'contributor' => EMPTY_ARRAY,
  366. 'developer' => EMPTY_ARRAY, 'translator' => EMPTY_ARRAY];
  367. foreach ($data['manifest']['localized'] as $_value) {
  368. if (!array_key_exists(self::EM_NS . 'locale', $data[$_value])) {
  369. continue;
  370. }
  371. if ($data[$_value][self::EM_NS . 'locale'] == 'en-US') {
  372. continue;
  373. }
  374. foreach ($data[$_value] as $_key2 => $_value2) {
  375. switch ($_key2) {
  376. case self::EM_NS . 'name':
  377. case self::EM_NS . 'description':
  378. if ($_value2 != $data['manifest'][str_replace(self::EM_NS, EMPTY_STRING, $_key2)]['en-US']) {
  379. $localized[str_replace(self::EM_NS, EMPTY_STRING, $_key2)]
  380. [$data[$_value][self::EM_NS . 'locale']] = $_value2;
  381. }
  382. break;
  383. case self::EM_NS . 'contributor':
  384. case self::EM_NS . 'developer':
  385. case self::EM_NS . 'translator':
  386. $localized[str_replace(self::EM_NS, EMPTY_STRING, $_key2)] =
  387. array_merge($localized[str_replace(self::EM_NS, EMPTY_STRING, $_key2)], $_value2);
  388. break;
  389. }
  390. }
  391. }
  392. unset($data['manifest']['localized']);
  393. foreach($localized as $_key => $_value) {
  394. if ($_value == EMPTY_ARRAY) {
  395. continue;
  396. }
  397. $data['manifest'][$_key] = array_key_exists($_key, $data['manifest']) ?
  398. array_merge($data['manifest'][$_key], $_value) :
  399. $_value;
  400. if (!in_array($_key, ['name', 'description'])) {
  401. $data['manifest'][$_key] = array_values(array_unique($data['manifest'][$_key]));
  402. }
  403. }
  404. }
  405. // ----------------------------------------------------------------------------------------------------------------
  406. // em:developer is no longer supported. Merge it with em:contributors
  407. if ($aEmDevloperMerge && array_key_exists('developer', $data['manifest'])) {
  408. if (array_key_exists('contributor', $data['manifest'])) {
  409. $data['manifest']['contributor'] = array_values(array_unique(array_merge($data['manifest']['contributor'],
  410. $data['manifest']['developer'])));
  411. }
  412. else {
  413. $data['manifest']['contributor'] = $data['manifest']['developer'];
  414. }
  415. unset($data['manifest']['developer']);
  416. }
  417. // ----------------------------------------------------------------------------------------------------------------
  418. // Set the targetApplication data
  419. if (array_key_exists('targetApplication', $data['manifest']) &&
  420. is_array($data['manifest']['targetApplication'])) {
  421. $targetApplication = EMPTY_ARRAY;
  422. foreach ($data['manifest']['targetApplication'] as $_value) {
  423. $id = $data[$_value][self::EM_NS . "id"];
  424. $targetApplication[$id]['minVersion'] = $data[$_value][self::EM_NS . 'minVersion'];
  425. $targetApplication[$id]['maxVersion'] = $data[$_value][self::EM_NS . 'maxVersion'];
  426. unset($data[$_value]);
  427. }
  428. unset($data['manifest']['targetApplication']);
  429. $data['manifest']['targetApplication'] = $targetApplication;
  430. }
  431. // ----------------------------------------------------------------------------------------------------------------
  432. // Tell the repat rdf parser to fuck off
  433. $rdf->rdf_parser_free();
  434. // Return the manifest
  435. return $data['manifest'];
  436. }
  437. /********************************************************************************************************************
  438. * Parses install.rdf for our desired properties
  439. *
  440. * @param array &$aData
  441. * @param string $aSubjectType
  442. * @param string $aSubject
  443. * @param string $aPredicate
  444. * @param int $aOrdinal
  445. * @param string $aObjectType
  446. * @param string $aObject
  447. * @param string $aXmlLang
  448. ********************************************************************************************************************/
  449. static function mfStatementHandler(&$aData, $aSubjectType, $aSubject, $aPredicate,
  450. $aOrdinal, $aObjectType, $aObject, $aXmlLang) {
  451. // Look for properties on the install manifest itself
  452. if ($aSubject == self::MF_RES && $aObject != 'false') {
  453. // we're only really interested in EM properties
  454. if (str_starts_with($aPredicate, self::EM_NS)) {
  455. $emProp = str_replace(self::EM_NS, EMPTY_STRING, $aPredicate);
  456. if (in_array($emProp, self::SINGLE_PROPS)) {
  457. $aData['manifest'][$emProp] = $aObject;
  458. }
  459. elseif (in_array($emProp, self::MULTI_PROPS)) {
  460. $aData['manifest'][$emProp][] = $aObject;
  461. }
  462. elseif (in_array($emProp, ['name', 'description'])) {
  463. $aData['manifest'][$emProp][($aXmlLang ? $aXmlLang : 'en-US')] = $aObject;
  464. }
  465. }
  466. }
  467. else {
  468. // Previously, Mozilla did not BOTHER to even ATTEMPT to handle em:localized props
  469. // Here we will attempt it. Though it does mean any multi-prop with localized-props
  470. // COULD have these set but it /GENERALLY/ is not the job of the install manifest
  471. // parser or the statement handler to say if that is right or wrong..
  472. // Just make it possble.
  473. if (in_array(str_replace(self::EM_NS, EMPTY_STRING, $aPredicate),
  474. ['contributor', 'developer', 'translator'])) {
  475. $aData[$aSubject][$aPredicate][] = $aObject;
  476. }
  477. else {
  478. // We don't know what it is so save it anyway as Mozilla always did.
  479. $aData[$aSubject][$aPredicate] = $aObject;
  480. }
  481. }
  482. // And return
  483. return $aData;
  484. }
  485. /********************************************************************************************************************
  486. * Parses manifest array into install.rdf
  487. *
  488. * @dep gfCreateXML()
  489. * @param $aManifest Parsed installManifest
  490. * @param $aDirectOutput If we should directly output the XML to the client
  491. * @returns String with XML markup if not aDirectOutput
  492. ********************************************************************************************************************/
  493. public function createInstallManifest($aManifest, $aDirectOutput = null) {
  494. // The Root Element of an install manifest
  495. $installManifest = array(
  496. '@element' => 'RDF',
  497. '@attributes' => array(
  498. 'xmlns' => self::RDF_NS,
  499. 'xmlns:em' => self::EM_NS,
  500. )
  501. );
  502. // The main description of an install manifest
  503. $mainDescription = array(
  504. '@element' => 'Description',
  505. '@attributes' => array(
  506. 'about' => self::MF_RES,
  507. )
  508. );
  509. // ----------------------------------------------------------------------------------------------------------------
  510. // XXXTobin: Bump version if not bumpped
  511. if (!str_ends_with($aManifest['version'], '.1-fxguid')) {
  512. $aManifest['version'] .= '.1-fxguid';
  513. }
  514. // XXXTobin: Remove email addresses from creator..
  515. $aManifest['creator'] = preg_replace('<[\w.]+@[\w.]+>', EMPTY_STRING, $aManifest['creator']);
  516. $aManifest['creator'] = trim($aManifest['creator']);
  517. // XXXTobin: aboutURL is the add-on's about box NOT website
  518. if (array_key_exists('aboutURL', $aManifest)) {
  519. if (!str_starts_with($aManifest['aboutURL'], 'chrome://')) {
  520. unset($aManifest['aboutURL']);
  521. }
  522. }
  523. // XXXTobin: OptionsURL data:text
  524. if (array_key_exists('optionsURL', $aManifest)) {
  525. if (str_starts_with($aManifest['optionsURL'], 'data:text')) {
  526. unset($aManifest['optionsURL']);
  527. unset($aManifest['optionsType']);
  528. }
  529. }
  530. // XXXTobin: multiprocessCompatible means nothing to us
  531. if (array_key_exists('multiprocessCompatible', $aManifest)) {
  532. unset($aManifest['multiprocessCompatible']);
  533. }
  534. // XXXTobin: We tend to mangle homepageURL to repositoryURL when it is a known forge
  535. // However, we should mangle back unless both are used.
  536. // This should be removed after the launch of Phobos since we are introducing an em:repositoryURL
  537. if (!array_key_exists('homepageURL', $aManifest)) {
  538. if (array_key_exists('repositoryURL', $aManifest)) {
  539. $aManifest['homepageURL'] = $aManifest['repositoryURL'];
  540. unset($aManifest['repositoryURL']);
  541. }
  542. }
  543. // ----------------------------------------------------------------------------------------------------------------
  544. // Add single props as attributes to the main description
  545. foreach ($aManifest as $_key => $_value) {
  546. if (in_array($_key, self::MULTI_PROPS)) {
  547. continue;
  548. }
  549. if (in_array($_key, ['name', 'description'])) {
  550. $mainDescription['@attributes']['em:' . $_key] = $_value['en-US'];
  551. continue;
  552. }
  553. $mainDescription['@attributes']['em:' . $_key] = $_value;
  554. }
  555. // ----------------------------------------------------------------------------------------------------------------
  556. // Add multiprops as elements
  557. foreach (['em:contributor' => $aManifest['contributor'] ?? null,
  558. 'em:developer' => $aManifest['developer'] ?? null,
  559. 'em:translator' => $aManifest['translator'] ?? null,
  560. 'em:otherLicenses' => $aManifest['otherLicenses'] ?? null,
  561. 'em:targetPlatform' => $aManifest['targetPlatform'] ?? null]
  562. as $_key => $_value) {
  563. if (!$_value) {
  564. continue;
  565. }
  566. foreach ($_value as $_value2) {
  567. $mainDescription[] = ['@element' => $_key, '@content' => trim($_value2)];
  568. }
  569. }
  570. // ----------------------------------------------------------------------------------------------------------------
  571. $locales = array_unique(array_merge(array_keys($aManifest['name']), array_keys($aManifest['description'])));
  572. sort($locales);
  573. foreach ($locales as $_value) {
  574. if ($_value == 'en-US') {
  575. continue;
  576. }
  577. $_name = $aManifest['name'][$_value] ?? null;
  578. $_desc = $aManifest['description'][$_value] ?? null;
  579. $_attrs = ['em:locale' => $_value];
  580. if ($_name) {
  581. $_attrs['em:name'] = $_name;
  582. }
  583. if ($_desc) {
  584. $_attrs['em:description'] = $_desc;
  585. }
  586. if (count($_attrs) < 2) {
  587. continue;
  588. }
  589. $mainDescription[] = ['@element' => 'em:localized', ['@element' => 'Description', '@attributes' => $_attrs]];
  590. }
  591. // ----------------------------------------------------------------------------------------------------------------
  592. // Add targetApplications as elements with attrs of the targetApplication description
  593. foreach ($aManifest['targetApplication'] as $_key => $_value) {
  594. $mainDescription[] = array(
  595. '@element' => 'em:targetApplication',
  596. array(
  597. '@element' => 'Description',
  598. '@attributes' => array(
  599. 'em:id' => $_key,
  600. 'em:minVersion' => $_value['minVersion'],
  601. 'em:maxVersion' => $_value['maxVersion'],
  602. )
  603. )
  604. );
  605. }
  606. // ----------------------------------------------------------------------------------------------------------------
  607. // Attach the main description to the root element
  608. $installManifest[] = $mainDescription;
  609. // Generate XML (or RDF in this case)
  610. $installManifest = gfCreateXML($installManifest, $aDirectOutput);
  611. // ----------------------------------------------------------------------------------------------------------------
  612. return $installManifest;
  613. }
  614. /********************************************************************************************************************
  615. * Parses manifest array into update.rdf
  616. *
  617. * @dep AUS_XPI_TYPES
  618. * @dep gfCreateXML()
  619. * @param $aManifest Parsed installManifest
  620. * @param $aDirectOutput If we should directly output the XML to the client
  621. * @returns String with XML markup if not aDirectOutput
  622. ********************************************************************************************************************/
  623. public function createUpdateManifest($aManifest, $aDirectOutput = null) {
  624. global $gaRuntime;
  625. if ($aManifest == null) {
  626. gfOutput(XML_TAG . RDF_AUS_BLANK, 'xml');
  627. }
  628. $aManifest['type'] = AUS_XPI_TYPES[$aManifest['type']] ?? 'item';
  629. // ----------------------------------------------------------------------------------------------------------------
  630. // Construct the Update Manifest
  631. $updateManifest = array(
  632. '@element' => 'RDF:RDF',
  633. '@attributes' => array(
  634. 'xmlns:RDF' => self::RDF_NS,
  635. 'xmlns:em' => self::EM_NS,
  636. ),
  637. array(
  638. '@element' => 'Description',
  639. '@attributes' => array(
  640. 'about' => 'urn:mozilla:' . $aManifest['type'] . COLON . $aManifest['id']
  641. ),
  642. array(
  643. '@element' => 'em:updates',
  644. array(
  645. '@element' => 'RDF:Seq',
  646. array(
  647. '@element' => 'RDF:li',
  648. array(
  649. '@element' => 'Description',
  650. '@attributes' => array(
  651. 'em:version' => $aManifest['version']
  652. ),
  653. )
  654. )
  655. )
  656. )
  657. )
  658. );
  659. // ----------------------------------------------------------------------------------------------------------------
  660. // Add targetApplications as elements with attrs of the targetApplication description
  661. foreach ($aManifest['targetApplication'] as $_key => $_value) {
  662. $_updateLink = $gaRuntime['currentScheme'] . SCHEME_SUFFIX . gfGetAppDomainByID($_key) . $aManifest['updateLink'];
  663. if ($gaRuntime['debugMode']) {
  664. $_updateLink = $gaRuntime['currentScheme'] . SCHEME_SUFFIX . DEVELOPER_DOMAIN . $aManifest['updateLink'];
  665. if (($_key != TARGET_APPLICATION[$gaRuntime['currentApplication']]['id']) &&
  666. ($_key != TARGET_APPLICATION['palemoon']['id'])) {
  667. $_updateLink .= '&appOverride=' . gfGetAppNameByID($_key);
  668. }
  669. }
  670. // RDF:RDF -> Description -> em:updates -> RDF:Seq -> RDF:li -> Description
  671. $updateManifest[0][0][0][0][0][] = array(
  672. '@element' => 'em:targetApplication',
  673. array(
  674. '@element' => 'Description',
  675. '@attributes' => array(
  676. 'em:id' => $_key,
  677. 'em:minVersion' => $_value['minVersion'],
  678. 'em:maxVersion' => $_value['maxVersion'],
  679. 'em:updateHash' => $aManifest['updateHash']
  680. ),
  681. array(
  682. '@element' => 'em:updateLink',
  683. '@content' => $_updateLink,
  684. ),
  685. )
  686. );
  687. }
  688. // ----------------------------------------------------------------------------------------------------------------
  689. return gfCreateXML($updateManifest, $aDirectOutput);
  690. }
  691. /********************************************************************************************************************
  692. * Creates a search result that is consumed by the Add-ons Manager
  693. *
  694. * @dep EMPTY_ARRAY
  695. * @dep SEARCH_XPI_TYPES (which requires XPINSTALL_TYPES)
  696. * @dep gfCreateXML()
  697. * @dep $gaRuntime
  698. * @param $aManifests List of parsed installManifests with additional stored and calculated data
  699. * @param $aDirectOutput If we should directly output the XML to the client
  700. * @returns String with XML markup if not aDirectOutput
  701. *
  702. * Each installManifest in the list needs the following stored or calculated data:
  703. * @dbAddon - slug
  704. * @dbAddon - hasIcon
  705. * @dbXPInstall - epoch
  706. * @dbXPInstall - size
  707. * @dbXPInstall - hash
  708. * @classAddon - addonURL
  709. * @classAddon - creatorURL
  710. * @classAddon - iconURL
  711. * @classAddon - downloadURL
  712. ********************************************************************************************************************/
  713. public function createSearchResults($aManifests, $aDirectOutput = null) {
  714. global $gaRuntime;
  715. $count = 0;
  716. $warnings = EMPTY_ARRAY;
  717. // Create the root searchresults element
  718. $searchResults = ['@element' => 'searchresults', '@attributes' => EMPTY_ARRAY];
  719. // Make sure aManifests is actually and array and an indexed list of manifests
  720. if (!is_array($aManifests) || !array_is_list($aManifests)) {
  721. // Log a warning if it is not
  722. $warnings[] = 'Not a list of search results.';
  723. // Make aManifests an empty array so that the subsequent foreach won't bitch.
  724. $aManifests = EMPTY_ARRAY;
  725. }
  726. // Loop through manifests to create the structure for a search result add-on
  727. // If it is null then assume empty array and pass through
  728. foreach ($aManifests as $_key => $_value) {
  729. $_addon = ['@element' => 'addon'];
  730. $_addon[] = ['@element' => 'type', '@attributes' => ['id' => SEARCH_XPI_TYPES[$_value['type']]]];
  731. $_addon[] = ['@element' => 'guid', '@content' => $_value['id']];
  732. $_addon[] = ['@element' => 'name', '@content' => $_value['name']['en-US']];
  733. $_addon[] = ['@element' => 'version', '@content' => $_value['version']];
  734. $_addon[] = ['@element' => 'icon', '@attributes' => ['size' => '48'], '@content' => $_value['iconURL']];
  735. $_addon[] = ['@element' => 'learnmore', '@content' => $_value['addonURL']];
  736. if (array_key_exists('homepageURL', $_value)) {
  737. $_addon[] = ['@element' => 'homepage', '@content' => $_value['homepageURL']];
  738. }
  739. // Deal with Authors
  740. $_authors = ['@element' => 'authors'];
  741. // The creator MUST first author element
  742. $_authors[] = ['@element' => 'author', ['@element' => 'name', '@content' => $_value['creator']],
  743. ['@element' => 'link', '@content' => $_value['creatorURL']]];
  744. // Assign authors to the addon element
  745. $_addon[] = $_authors;
  746. // Deal with targetApplications
  747. $targetApps = ['@element' => 'compatible_applications'];
  748. foreach ($_value['targetApplication'] as $_key2 => $_value2) {
  749. if (TARGET_APPLICATION[$gaRuntime['currentApplication']]['id'] != $_key) {
  750. continue;
  751. }
  752. $targetApps[] = ['@element' => 'application', ['@element' => 'appID', '@content' => $_key2],
  753. ['@element' => 'min_version', '@content' => $_value2['minVersion']],
  754. ['@element' => 'max_version', '@content' => $_value2['maxVersion']]];
  755. }
  756. // Assign the targetApplications as application elements
  757. $_addon[] = $targetApps;
  758. $_addon[] = ['@element' => 'last_updated', '@attributes' => ['epoch' => $_value['epoch']]];
  759. $_addon[] = ['@element' => 'install',
  760. '@attributes' => ['size' => $_value['size'], 'hash' => $_value['hash']],
  761. '@content' => $_value['downloadURL']];
  762. $searchResults[] = $_addon;
  763. $count++;
  764. }
  765. // If the count has not be increased then there are no results so log a warning.
  766. if ($count == 0) {
  767. $warnings[] = 'No results.';
  768. }
  769. // Attach the total number of results to the searchresults element
  770. $searchResults['@attributes']['total_results'] = $count;
  771. // If we are in debug mode and we have warnings then create a phobos element
  772. // and emit the warnings as warning elements.
  773. // We do this so that this method has a safe failure that won't piss off either the
  774. // xml parser or the code that consumes the search results in the Add-ons Manager code.
  775. if ($gaRuntime['debugMode'] && $warnings != EMPTY_ARRAY) {
  776. // Create phobos element
  777. $warningResults = ['@element' => 'phobos'];
  778. // Loop through warnings and create warning elements and attach them to the phobos element.
  779. foreach ($warnings as $_value) {
  780. $warningResults[] = ['@element' => 'warning', '@content' => $_value];
  781. }
  782. // Attach the phobos element with the warnings to the search results element.
  783. $searchResults[] = $warningResults;
  784. }
  785. // Create the XML and return if not direct output.
  786. return gfCreateXML($searchResults, $aDirectOutput);
  787. }
  788. }
  789. ?>