| @@ -106,12 +106,12 @@ | ||
| 106 | 106 | if ( ! $this->is_wpconfig_writable() ) { |
| 107 | 107 | return new WP_Error( 'not_writable', __( 'wp-config.php is not writable', 'vigilante' ) ); |
| 108 | 108 | } |
| 109 | 109 | |
| 110 | - // Safety check 2: Create backup BEFORE any modification | |
| 111 | - $backup_result = $this->create_backup(); | |
| 112 | - if ( is_wp_error( $backup_result ) ) { | |
| 113 | - return $backup_result; | |
| 110 | + // Safety check 2: the file must look whole BEFORE any modification | |
| 111 | + $check_result = $this->check_before_write(); | |
| 112 | + if ( is_wp_error( $check_result ) ) { | |
| 113 | + return $check_result; | |
| 114 | 114 | } |
| 115 | 115 | |
| 116 | 116 | // First clean up old plugin constants |
| 117 | 117 | $this->remove_old_constants(); |
| @@ -149,13 +149,18 @@ | ||
| 149 | 149 | return $result; |
| 150 | 150 | } |
| 151 | 151 | |
| 152 | 152 | /** |
| 153 | - * Create a backup of wp-config.php before modification | |
| 153 | + * Check wp-config.php before modifying it | |
| 154 | 154 | * |
| 155 | + * Until 2.11.6 this also stored the whole file in vigilante_wpconfig_backup, | |
| 156 | + * and with it the database password and the authentication keys and salts. | |
| 157 | + * Nothing ever read that copy back to restore anything: the checks are what | |
| 158 | + * protected the file, and the copy only put its secrets in the options table. | |
| 159 | + * | |
| 155 | 160 | * @return bool|WP_Error |
| 156 | 161 | */ |
| 157 | - private function create_backup() { | |
| 162 | + private function check_before_write() { | |
| 158 | 163 | if ( ! file_exists( $this->wpconfig_path ) ) { |
| 159 | 164 | return new WP_Error( 'no_config', __( 'wp-config.php does not exist', 'vigilante' ) ); |
| 160 | 165 | } |
| 161 | 166 | |
| @@ -169,25 +174,10 @@ | ||
| 169 | 174 | if ( ! $this->validate_wpconfig_content( $content ) ) { |
| 170 | 175 | return new WP_Error( 'invalid_config', __( 'wp-config.php does not appear to be a valid WordPress configuration file', 'vigilante' ) ); |
| 171 | 176 | } |
| 172 | 177 | |
| 173 | - // Store the backup in a private database option, never as a file under | |
| 174 | - // the web root. wp-config.php holds DB credentials and salts; a file in | |
| 175 | - // wp-content could be served by a misconfigured server. The option is | |
| 176 | - // not reachable over HTTP and is not autoloaded. | |
| 177 | - $stored = update_option( | |
| 178 | - 'vigilante_wpconfig_backup', | |
| 179 | - array( | |
| 180 | - 'content' => $content, | |
| 181 | - 'time' => time(), | |
| 182 | - ), | |
| 183 | - false | |
| 184 | - ); | |
| 185 | - | |
| 186 | - // update_option() returns false both on failure and when the value is | |
| 187 | - // unchanged; only treat it as an error if the content was not stored. | |
| 188 | - if ( false === $stored && $content !== $this->get_wpconfig_backup_content() ) { | |
| 189 | - return new WP_Error( 'backup_failed', __( 'Could not create wp-config.php backup', 'vigilante' ) ); | |
| 178 | + if ( ! self::constants_blocks_are_whole( $content ) ) { | |
| 179 | + return new WP_Error( 'block_incomplete', __( 'A Vigilant block in wp-config.php is missing one of its markers, so the file was left as it is.', 'vigilante' ) ); | |
| 190 | 180 | } |
| 191 | 181 | |
| 192 | 182 | return true; |
| 193 | 183 | } |
| @@ -192,15 +182,43 @@ | ||
| 192 | 182 | return true; |
| 193 | 183 | } |
| 194 | 184 | |
| 195 | 185 | /** |
| 196 | - * Get the stored wp-config.php backup content, or '' if none. | |
| 186 | + * Whether no constants block in wp-config.php is left without its END | |
| 197 | 187 | * |
| 198 | - * @return string | |
| 188 | + * A BEGIN with no END after it, or a second BEGIN before the END, means the | |
| 189 | + * block cannot be found whole. Removing it would still rewrite the file and | |
| 190 | + * uncomment the original constants around it while the block keeps defining | |
| 191 | + * them, and writing a new one would leave the broken block in place for the | |
| 192 | + * next removal to pair with the new END, taking everything in between. So | |
| 193 | + * the file is left as it is, and the block keeps working until someone | |
| 194 | + * removes it by hand. An END with no BEGIN before it is left out of the | |
| 195 | + * question: each BEGIN is paired with the next END and a stray END is left | |
| 196 | + * alone. | |
| 197 | + * | |
| 198 | + * Until 2.11.6 only deactivation asked this, from its own copy of the check. | |
| 199 | + * | |
| 200 | + * @since 2.11.6 | |
| 201 | + * | |
| 202 | + * @param string $content wp-config.php content. | |
| 203 | + * @return bool | |
| 199 | 204 | */ |
| 200 | - private function get_wpconfig_backup_content() { | |
| 201 | - $backup = get_option( 'vigilante_wpconfig_backup' ); | |
| 202 | - return ( is_array( $backup ) && isset( $backup['content'] ) ) ? (string) $backup['content'] : ''; | |
| 205 | + private static function constants_blocks_are_whole( $content ) { | |
| 206 | + preg_match_all( '/' . preg_quote( self::MARKER_START, '/' ) . '|' . preg_quote( self::MARKER_END, '/' ) . '/', $content, $markers ); | |
| 207 | + | |
| 208 | + $inside = false; | |
| 209 | + foreach ( $markers[0] as $marker ) { | |
| 210 | + if ( self::MARKER_START === $marker ) { | |
| 211 | + if ( $inside ) { | |
| 212 | + return false; | |
| 213 | + } | |
| 214 | + $inside = true; | |
| 215 | + } else { | |
| 216 | + $inside = false; | |
| 217 | + } | |
| 218 | + } | |
| 219 | + | |
| 220 | + return ! $inside; | |
| 203 | 221 | } |
| 204 | 222 | |
| 205 | 223 | /** |
| 206 | 224 | * Read file directly without WP_Filesystem (more reliable) |
| @@ -402,8 +420,14 @@ | ||
| 402 | 420 | $content = $this->read_file_directly( $this->wpconfig_path ); |
| 403 | 421 | |
| 404 | 422 | if ( false === $content ) { |
| 405 | 423 | return false; |
| 424 | + } | |
| 425 | + | |
| 426 | + // A block that lost a marker cannot come out without cutting or | |
| 427 | + // duplicating what surrounds it, so the file is left as it is. | |
| 428 | + if ( ! self::constants_blocks_are_whole( $content ) ) { | |
| 429 | + return new WP_Error( 'block_incomplete', __( 'A Vigilant block in wp-config.php is missing one of its markers, so the file was left as it is.', 'vigilante' ) ); | |
| 406 | 430 | } |
| 407 | 431 | |
| 408 | 432 | // If our markers don't exist, just try to uncomment originals |
| 409 | 433 | if ( strpos( $content, self::MARKER_START ) === false ) { |