[SECURITY] XML entity expansion
[Packages/TYPO3.CMS.git] / typo3 / sysext / lang / Classes / Utility / Connection / Ter.php
1 <?php
2 namespace TYPO3\CMS\Lang\Utility\Connection;
3 /**
4 * This file is part of the TYPO3 CMS project.
5 *
6 * It is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License, either version 2
8 * of the License, or any later version.
9 *
10 * For the full copyright and license information, please read the
11 * LICENSE.txt file that was distributed with this source code.
12 *
13 * The TYPO3 project - inspiring people to share!
14 */
15
16 /**
17 * Extends of extensionmanager ter connection to enrich with translation related methods
18 *
19 * @author Sebastian Fischer <typo3@evoweb.de>
20 */
21 class Ter extends \TYPO3\CMS\Extensionmanager\Utility\Connection\TerUtility {
22
23 /**
24 * Fetches extensions translation status
25 *
26 * @param string $extensionKey Extension Key
27 * @param string $mirrorUrl URL of mirror to use
28 * @return mixed
29 */
30 public function fetchTranslationStatus($extensionKey, $mirrorUrl) {
31 $result = FALSE;
32 $extPath = \TYPO3\CMS\Core\Utility\GeneralUtility::strtolower($extensionKey);
33 $mirrorUrl .= $extPath[0] . '/' . $extPath[1] . '/' . $extPath . '-l10n/' . $extPath . '-l10n.xml';
34 $remote = \TYPO3\CMS\Core\Utility\GeneralUtility::getURL($mirrorUrl, 0, array(TYPO3_user_agent));
35
36 if ($remote !== FALSE) {
37 $parsed = $this->parseL10nXML($remote);
38 $result = $parsed['languagePackIndex'];
39 }
40
41 return $result;
42 }
43
44 /**
45 * Parses content of *-l10n.xml into a suitable array
46 *
47 * @param string $string: XML data to parse
48 * @throws \TYPO3\CMS\Lang\Exception\XmlParser
49 * @return array Array representation of XML data
50 */
51 protected function parseL10nXML($string) {
52 // Create parser:
53 $parser = xml_parser_create();
54 // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
55 $previousValueOfEntityLoader = libxml_disable_entity_loader(TRUE);
56 $values = array();
57 $index = array();
58
59 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
60 xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0);
61
62 // Parse content
63 xml_parse_into_struct($parser, $string, $values, $index);
64 libxml_disable_entity_loader($previousValueOfEntityLoader);
65 // If error, return error message
66 if (xml_get_error_code($parser)) {
67 $line = xml_get_current_line_number($parser);
68 $error = xml_error_string(xml_get_error_code($parser));
69 xml_parser_free($parser);
70 throw new \TYPO3\CMS\Lang\Exception\XmlParser('Error in XML parser while decoding l10n XML file. Line ' . $line . ': ' . $error, 1345736517);
71 } else {
72 // Init vars
73 $stack = array(array());
74 $stacktop = 0;
75 $current = array();
76 $tagName = '';
77 $documentTag = '';
78
79 // Traverse the parsed XML structure:
80 foreach ($values as $val) {
81 // First, process the tag-name (which is used in both cases, whether "complete" or "close")
82 $tagName = ($val['tag'] == 'languagepack' && $val['type'] == 'open') ? $val['attributes']['language'] : $val['tag'];
83 if (!$documentTag) {
84 $documentTag = $tagName;
85 }
86
87 // Setting tag-values, manage stack:
88 switch ($val['type']) {
89 // If open tag it means there is an array stored in sub-elements.
90 // Therefore increase the stackpointer and reset the accumulation array
91 case 'open':
92 // Setting blank place holder
93 $current[$tagName] = array();
94 $stack[$stacktop++] = $current;
95 $current = array();
96 break;
97 // If the tag is "close" then it is an array which is closing and we decrease the stack pointer.
98 case 'close':
99 $oldCurrent = $current;
100 $current = $stack[--$stacktop];
101 // Going to the end of array to get placeholder key, key($current), and fill in array next
102 end($current);
103 $current[key($current)] = $oldCurrent;
104 unset($oldCurrent);
105 break;
106 // If "complete", then it's a value. Omits the tag if the value is empty.
107 case 'complete':
108 $trimmedValue = trim((string)$val['value']);
109 if ($trimmedValue !== '') {
110 $current[$tagName] = $trimmedValue;
111 }
112 break;
113 }
114 }
115 $result = $current[$tagName];
116 }
117
118 return $result;
119 }
120
121 /**
122 * Install translations for all selected languages for an extension
123 *
124 * @param string $extensionKey The extension key to install the translations for
125 * @param string $language Language code of translation to fetch
126 * @param string $mirrorUrl Mirror URL to fetch data from
127 * @return boolean TRUE on success, error string on fauilure
128 */
129 public function updateTranslation($extensionKey, $language, $mirrorUrl) {
130 $result = FALSE;
131 try {
132 $l10n = $this->fetchTranslation($extensionKey, $language, $mirrorUrl);
133 if (is_array($l10n)) {
134 $absolutePathToZipFile = \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName('typo3temp/' . $extensionKey . '-l10n-' . $language . '.zip');
135 $relativeLanguagePath = 'l10n' . '/' . $language . '/';
136 $absoluteLanguagePath = \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName(PATH_typo3conf . $relativeLanguagePath);
137 $absoluteExtensionLanguagePath = \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName(PATH_typo3conf . $relativeLanguagePath. $extensionKey . '/');
138 if (empty($absolutePathToZipFile) || empty($absoluteLanguagePath) || empty($absoluteExtensionLanguagePath)) {
139 throw new \TYPO3\CMS\Lang\Exception\Lang('Given path is invalid.', 1352565336);
140 }
141 if (!is_dir($absoluteLanguagePath)) {
142 \TYPO3\CMS\Core\Utility\GeneralUtility::mkdir_deep(PATH_typo3conf, $relativeLanguagePath);
143 }
144 \TYPO3\CMS\Core\Utility\GeneralUtility::writeFile($absolutePathToZipFile, $l10n[0]);
145 if (is_dir($absoluteExtensionLanguagePath)) {
146 \TYPO3\CMS\Core\Utility\GeneralUtility::rmdir($absoluteExtensionLanguagePath, TRUE);
147 }
148
149 if ($this->unzipTranslationFile($absolutePathToZipFile, $absoluteLanguagePath)) {
150 $result = TRUE;
151 }
152 }
153 } catch (\TYPO3\CMS\Core\Exception $exception) {
154 // @todo logging
155 }
156 return $result;
157 }
158
159 /**
160 * Fetches an extensions l10n file from the given mirror
161 *
162 * @param string $extensionKey Extension Key
163 * @param string $language The language code of the translation to fetch
164 * @param string $mirrorUrl URL of mirror to use
165 * @throws \TYPO3\CMS\Lang\Exception\XmlParser
166 * @return array Array containing l10n data
167 */
168 protected function fetchTranslation($extensionKey, $language, $mirrorUrl) {
169 $extensionPath = \TYPO3\CMS\Core\Utility\GeneralUtility::strtolower($extensionKey);
170 $mirrorUrl .= $extensionPath[0] . '/' . $extensionPath[1] . '/' . $extensionPath .
171 '-l10n/' . $extensionPath . '-l10n-' . $language . '.zip';
172 $l10nResponse = \TYPO3\CMS\Core\Utility\GeneralUtility::getURL($mirrorUrl, 0, array(TYPO3_user_agent));
173
174 if ($l10nResponse === FALSE) {
175 throw new \TYPO3\CMS\Lang\Exception\XmlParser('Error: Translation could not be fetched.', 1345736785);
176 } else {
177 return array($l10nResponse);
178 }
179 }
180
181 /**
182 * Unzip an language.zip.
183 *
184 * @param string $file path to zip file
185 * @param string $path path to extract to
186 * @throws \TYPO3\CMS\Lang\Exception\Lang
187 * @return boolean
188 */
189 protected function unzipTranslationFile($file, $path) {
190 $zip = zip_open($file);
191 if (is_resource($zip)) {
192 $result = TRUE;
193
194 if (!is_dir($path)) {
195 \TYPO3\CMS\Core\Utility\GeneralUtility::mkdir_deep($path);
196 }
197
198 while (($zipEntry = zip_read($zip)) !== FALSE) {
199 $zipEntryName = zip_entry_name($zipEntry);
200 if (strpos($zipEntryName, '/') !== FALSE) {
201 $zipEntryPathSegments = explode('/', $zipEntryName);
202 $fileName = array_pop($zipEntryPathSegments);
203 // It is a folder, because the last segment is empty, let's create it
204 if (empty($fileName)) {
205 \TYPO3\CMS\Core\Utility\GeneralUtility::mkdir_deep($path, implode('/', $zipEntryPathSegments));
206 } else {
207 $absoluteTargetPath = \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName($path . implode('/', $zipEntryPathSegments) . '/' . $fileName);
208 if (strlen(trim($absoluteTargetPath)) > 0) {
209 $return = \TYPO3\CMS\Core\Utility\GeneralUtility::writeFile(
210 $absoluteTargetPath, zip_entry_read($zipEntry, zip_entry_filesize($zipEntry))
211 );
212 if ($return === FALSE) {
213 throw new \TYPO3\CMS\Lang\Exception\Lang('Could not write file ' . $zipEntryName, 1345304560);
214 }
215 } else {
216 throw new \TYPO3\CMS\Lang\Exception\Lang('Could not write file ' . $zipEntryName, 1352566904);
217 }
218 }
219 } else {
220 throw new \TYPO3\CMS\Lang\Exception\Lang('Extension directory missing in zip file!', 1352566905);
221 }
222 }
223 } else {
224 throw new \TYPO3\CMS\Lang\Exception\Lang('Unable to open zip file ' . $file, 1345304561);
225 }
226
227 return $result;
228 }
229 }