Some problems arising from the encryption of Drupal by Swoole Compiler.



Last week, I met a customer who used it.Swoole CompilerEncryptionDrupalLead toDrupalThe problem that the project cannot run is summarized as follows after step-by-step investigationDrupalSome of the codes in are directly passed throughfile_get_contentsObtainPHPSource code, because the project code is encrypted, so direct accessPHPSource code parsing can’t get what you want.

  • Swoole Compiler
  • DrupalIs an open source content management framework written in PHP (CMF), which is managed by a content management system (CMS) andPHPDevelopment framework (Framework) together.

Impact of encryptionDrupalMain code to run

Code path


//code content
 protected function parse()
 if ($this->parsed || !  $fileName = $this->finder->findFile($this->className)) {
 $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, throughclassName to get the file path, and then through thefile_get_contentsObtainPHPThe contents of the documentTokenParserThe 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_allAnalyze, and then obtain subsequent analysis codesPHPThe file’s classes, attributes, comments on methods, namespaces of parent classes, andclassName, this kind ofuseInformation, etc., because the file has been encrypted, sofile_get_contentsThe obtained content is encrypted content.token_get_allThe correct information cannot be parsed, thus causing the program to fail to run.


For this use2.1.1Version of the encryptor, throughSwoole CompilerThe code encrypted by the encryptor is in the configuration filesave_docThe configuration option must be set to1If is set to0The comment is not saved and the2.1.3Versionswoole_loader.soThe newly added function in the extensionnaloinwenraswwwwYou 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);
 3    $parent_ref = $ref->getParentClass();
 5      ......
 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   }
 17   $this->parentClassName = $parent_ref->getName();
 19   if (strpos($this->parentClassName, '\\')!  ==0) {
 20       $this->parentClassName = '\\'.$this->parentClassName;
 21   }
 23   $static_properties = [];
 25   $properties = $ref->getProperties();
 27   $parent_properties = $this->createNewArrKey($parent_ref->getProperties());
 29     ......
 31   $static_methods = [];
 33   $methods = $ref->getMethods();
 35     ......
  1. Line 1 gets the reflection class by the class nameReflectionClassThe object of the class.
  2. 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.ReflectionClassClassgetParentClassThe method gets the reflection class of the parent class, and this method returns the of the parent classReflectionClassObject.
  3. Line 25 PassedReflectionClassClassgetPropertiesThe 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 aReflectionPropertyClass objects.
  4. viaReflectionPropertyClassgetDocCommentMethod to get the annotation of the attribute.
  5. Ibid., line 33ReflectionClassClassgetMethodsThe 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.ReflectionMethodClass objects.
  6. viaReflectionMethodObjectgetDocCommentMethod to get the annotation of the method.
  7. Pass line 17ReflectionClassProvidedgetNameMethod can get the class name.

Because reflection cannot be obtaineduseClass, so in the2.1.3In versionswoole_loader.soAdd function to extensionnaloinwenraswwww, this function passes in aPHPThe 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_namespacesFor the namespace of the file,swoole_class_nameAdd the class name to the namespace of the file, and the others areuseInformation, key isuseThe 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 ofuseClass namespace plus class name, which is compatible with reflection functionStaticReflectionParserCan’t get the correct information after encryption in

Not affected after encryptionDrupalPotential 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

DrupalIntroduced inSymfonyFramework, part of the code in this framework is also throughfile_get_contentsAndtoken_get_allTo getPHPThe class name of the file, but currently noDruaplThe operation has an impact and the method may not be used.


withStaticReflectionParserThe solution of the class is passed as well2.1.3In versionswoole_loader.soAdd function to extensionnaloinwenraswwwwTo obtain the namespace and class name of the encrypted file

Problems for which there is no better plan:

  • Code path:drupal/core/includes/
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';
                        case 'candidate_left':
                            if ($value == '[') {
                                $state = 'array_index';
                            if ($value == '=') {
                                $state = 'candidate_right';
                        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');
                        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');
                        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.
                                // Skip the semicolon because _drupal_rewrite_settings_dump_one() added one.
                                $state = 'semicolon_skip';
                            } else {
                                $state = 'wait_for_semicolon';
                        case 'wait_for_semicolon':
                            if ($value == ';') {
                                $state = 'default';
                        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.');
                $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]));

DrupalThere 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 asMysqlAfter input, this method passesfile_get_contentsAndtoken_get_allTo getsettingInformation 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 CompilerThere is no better solution here for the time being, so it is necessary to choose not to encrypt when encrypting.settingDocuments.

Code path:drupal/vendor/symfony/class-loader/ClassCollectionLoader.php:126
In this class isSymfonyRead 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.