Preface
Last week, I met a customer who used it.Swoole Compiler
EncryptionDrupal
Lead toDrupal
The problem that the project cannot run is summarized as follows after step-by-step investigationDrupal
Some of the codes in are directly passed throughfile_get_contents
ObtainPHP
Source code, because the project code is encrypted, so direct accessPHP
Source code parsing can’t get what you want.
Note:
-
Swoole Compiler
:https://www.swoole-cloud.com/compiler.html -
Drupal
Is an open source content management framework written in PHP (CMF
), which is managed by a content management system (CMS
) andPHP
Development framework (Framework
) together.
Impact of encryptionDrupal
Main code to run
Code path
drupal/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionParser.php:126
//code content
protected function parse()
{
if ($this->parsed || ! $fileName = $this->finder->findFile($this->className)) {
return;
}
$this->parsed = true;
$contents = file_get_contents($fileName);
if ($this->classAnnotationOptimize) {
if (preg_match("/\A.*^\s*((abstract|final)\s+)? class\s+{$this->shortClassName}\s+/sm", $contents, $matches)) {
$contents = $matches[0];
}
}
$tokenParser = new TokenParser($contents);
......
}
Part of the code is as above, throughclass
Name to get the file path, and then through thefile_get_contents
ObtainPHP
The contents of the documentTokenParser
The constructor in the class is as follows
public function __construct($contents)
{
$this->tokens = token_get_all($contents);
token_get_all("<? php\n/**\n *\n */");
$this->numTokens = count($this->tokens);
}
The source code passed throughtoken_get_all
Analyze, and then obtain subsequent analysis codesPHP
The file’s classes, attributes, comments on methods, namespaces of parent classes, andclass
Name, this kind ofuse
Information, etc., because the file has been encrypted, sofile_get_contents
The obtained content is encrypted content.token_get_all
The correct information cannot be parsed, thus causing the program to fail to run.
Solution
For this use
2.1.1
Version of the encryptor, throughSwoole Compiler
The code encrypted by the encryptor is in the configuration filesave_doc
The configuration option must be set to1
If is set to0
The comment is not saved and the2.1.3
Versionswoole_loader.so
The newly added function in the extensionnaloinwenraswwww
You can’t get the information about use in the class. The specific function usage will be described in detail later.
1 $ref = new \ReflectionClass($this->className);
2
3 $parent_ref = $ref->getParentClass();
4
5 ......
6
7 if (is_file($fileName)) {
8 $php_file_info = unserialize(naloinwenraswwww(realpath($fileName)));
9 foreach ($php_file_info as $key => $info) {
10 if ($key == 'swoole_namespaces' || $key == 'swoole_class_name') {
11 continue;
12 }
13 $this->useStatements[$key] = $info;
14 }
15 }
16
17 $this->parentClassName = $parent_ref->getName();
18
19 if (strpos($this->parentClassName, '\\')! ==0) {
20 $this->parentClassName = '\\'.$this->parentClassName;
21 }
22
23 $static_properties = [];
24
25 $properties = $ref->getProperties();
26
27 $parent_properties = $this->createNewArrKey($parent_ref->getProperties());
28
29 ......
30
31 $static_methods = [];
32
33 $methods = $ref->getMethods();
34
35 ......
- Line 1 gets the reflection class by the class name
ReflectionClass
The object of the class. - Because this reflection class contains all the attributes and methods in the parent class, but as long as the attributes and methods in this class are obtained in the source code, the reflection class of the parent class should also be obtained and then the attributes and methods in the parent class should be eliminated through comparison, which is used in line 3.
ReflectionClass
ClassgetParentClass
The method gets the reflection class of the parent class, and this method returns the of the parent classReflectionClass
Object. - Line 25 Passed
ReflectionClass
ClassgetProperties
The method obtains the attributes of this class and the parent class respectively, and then compares them to eliminate the attributes of the parent class and retain the attributes of this class. this method returns aReflectionProperty
Class objects. - via
ReflectionProperty
ClassgetDocComment
Method to get the annotation of the attribute. - Ibid., line 33
ReflectionClass
ClassgetMethods
The method can get the methods in this class and the parent class, and then compare the methods to eliminate the parent class and retain the methods of this class. This method returns a.ReflectionMethod
Class objects. - via
ReflectionMethod
ObjectgetDocComment
Method to get the annotation of the method. - Pass line 17
ReflectionClass
ProvidedgetName
Method can get the class name.
Because reflection cannot be obtained
use
Class, so in the2.1.3
In versionswoole_loader.so
Add function to extensionnaloinwenraswwww
, this function passes in aPHP
The absolute path of the file, returning the serialized array of related information of the incoming file. The deserialized array is as follows
[
"swoole_namespaces" => "Drupal\Core\Datetime\Element",
"swoole_class_name" => "Drupal\Core\Datetime\Element\DateElementBase",
"nestedarray" => "Drupal\Component\Utility\NestedArray",
"drupaldatetime" => "Drupal\Core\Datetime\DrupalDateTime",
"formelement"=> "Drupal\Core\Render\Element\FormElement"
]
among themswoole_namespaces
For the namespace of the file,swoole_class_name
Add the class name to the namespace of the file, and the others areuse
Information, key isuse
The lower case letter of the class name of the class, or the lower case letter of the alias if there is an alias, with a value ofuse
Class namespace plus class name, which is compatible with reflection functionStaticReflectionParser
Can’t get the correct information after encryption in
Not affected after encryptionDrupal
Potential operational problems:
- Code path:
drupal/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php:39
- Code path:
drupal/vendor/symfony/class-loader/ClassMapGenerator.php:91
- Code path:
drupal/vendor/symfony/routing/Loader/AnnotationFileLoader.php:90
Drupal
Introduced inSymfony
Framework, part of the code in this framework is also throughfile_get_contents
Andtoken_get_all
To getPHP
The class name of the file, but currently noDruapl
The operation has an impact and the method may not be used.
Solution:
withStaticReflectionParser
The solution of the class is passed as well2.1.3
In versionswoole_loader.so
Add function to extensionnaloinwenraswwww
To obtain the namespace and class name of the encrypted file
Problems for which there is no better plan:
- Code path:
drupal/core/includes/install.inc:220
function drupal_rewrite_settings($settings = [], $settings_file = NULL)
{
if (!isset($settings_file)) {
$settings_file = \Drupal::service('site.path') . '/settings.php';
}
// Build list of setting names and insert the values into the global namespace.
$variable_names = [];
$settings_settings = [];
foreach ($settings as $setting => $data) {
if ($setting != 'settings') {
_drupal_rewrite_settings_global($GLOBALS[$setting], $data);
} else {
_drupal_rewrite_settings_global($settings_settings, $data);
}
$variable_names['$' . $setting] = $setting;
}
$contents = file_get_contents($settings_file);
if ($contents !== FALSE) {
// Initialize the contents for the settings.php file if it is empty.
if (trim($contents) === '') {
$contents = "<?php\n";
}
// Step through each token in settings.php and replace any variables that
// are in the passed-in array.
$buffer = '';
$state = 'default';
foreach (token_get_all($contents) as $token) {
if (is_array($token)) {
list($type, $value) = $token;
} else {
$type = -1;
$value = $token;
}
// Do not operate on whitespace.
if (!in_array($type, [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
switch ($state) {
case 'default':
if ($type === T_VARIABLE && isset($variable_names[$value])) {
// This will be necessary to unset the dumped variable.
$parent = &$settings;
// This is the current index in parent.
$index = $variable_names[$value];
// This will be necessary for descending into the array.
$current = &$parent[$index];
$state = 'candidate_left';
}
break;
case 'candidate_left':
if ($value == '[') {
$state = 'array_index';
}
if ($value == '=') {
$state = 'candidate_right';
}
break;
case 'array_index':
if (_drupal_rewrite_settings_is_array_index($type, $value)) {
$index = trim($value, '\'"');
$state = 'right_bracket';
} else {
// $a[foo()] or $a[$bar] or something like that.
throw new Exception('invalid array index');
}
break;
case 'right_bracket':
if ($value == ']') {
if (isset($current[$index])) {
// If the new settings has this index, descend into it.
$parent = &$current;
$current = &$parent[$index];
$state = 'candidate_left';
} else {
// Otherwise, jump back to the default state.
$state = 'wait_for_semicolon';
}
} else {
// $a[1 + 2].
throw new Exception('] expected');
}
break;
case 'candidate_right':
if (_drupal_rewrite_settings_is_simple($type, $value)) {
$value = _drupal_rewrite_settings_dump_one($current);
// Unsetting $current would not affect $settings at all.
unset($parent[$index]);
// Skip the semicolon because _drupal_rewrite_settings_dump_one() added one.
$state = 'semicolon_skip';
} else {
$state = 'wait_for_semicolon';
}
break;
case 'wait_for_semicolon':
if ($value == ';') {
$state = 'default';
}
break;
case 'semicolon_skip':
if ($value == ';') {
$value = '';
$state = 'default';
} else {
// If the expression was $a = 1 + 2; then we replaced 1 and
// the + is unexpected.
throw new Exception('Unexpected token after replacing value.');
}
break;
}
}
$buffer .= $value;
}
foreach ($settings as $name => $setting) {
$buffer .= _drupal_rewrite_settings_dump($setting, '$' . $name);
}
// Write the new settings file.
if (file_put_contents($settings_file, $buffer) === FALSE) {
throw new Exception(t('Failed to modify %settings. Verify the file permissions.', ['%settings' => $settings_file]));
} else {
// In case any $settings variables were written, import them into the
// Settings singleton.
if (!empty($settings_settings)) {
$old_settings = Settings::getAll();
new Settings($settings_settings + $old_settings);
}
// The existing settings.php file might have been included already. In
// case an opcode cache is enabled, the rewritten contents of the file
// will not be reflected in this process. Ensure to invalidate the file
// in case an opcode cache is enabled.
OpCodeCache::invalidate(DRUPAL_ROOT . '/' . $settings_file);
}
} else {
throw new Exception(t('Failed to open %settings. Verify the file permissions.', ['%settings' => $settings_file]));
}
}
Drupal
There are configuration files during installationdefault.setting.php
, which stores the default configuration array, will allow users to enter some configurations in the installation interface during the installation process, such asMysql
After input, this method passesfile_get_contents
Andtoken_get_all
To getsetting
Information in the, and then merge the user input information on the page, to save back the file, because the whole process involves reading the file, change the file information, in the file, soSwoole Compiler
There is no better solution here for the time being, so it is necessary to choose not to encrypt when encrypting.setting
Documents.
Code path:drupal/vendor/symfony/class-loader/ClassCollectionLoader.php:126
In this class isSymfony
Read PHP file and cache it into the file after corresponding processing. There are the same problems as the above code. No better solution has been found yet.