| 1 |
<?php |
| 2 |
|
| 3 |
class Red_Htaccess { |
| 4 |
private $items = array(); |
| 5 |
const INSERT_REGEX = '@\n?# Created by Redirection(?:.*?)# End of Redirection\n?@sm'; |
| 6 |
|
| 7 |
private function encode_from( $url ) { |
| 8 |
$url = $this->encode( $url ); |
| 9 |
|
| 10 |
// Apache 2 does not need a leading slashing |
| 11 |
$url = ltrim( $url, '/' ); |
| 12 |
|
| 13 |
// Exactly match the URL |
| 14 |
return '^' . $url . '$'; |
| 15 |
} |
| 16 |
|
| 17 |
// URL encode some things, but other things can be passed through |
| 18 |
private function encode2nd( $url ) { |
| 19 |
$allowed = [ |
| 20 |
'%2F' => '/', |
| 21 |
'%3F' => '?', |
| 22 |
'%3A' => ':', |
| 23 |
'%3D' => '=', |
| 24 |
'%26' => '&', |
| 25 |
'%25' => '%', |
| 26 |
'+' => '%20', |
| 27 |
'%24' => '$', |
| 28 |
'%23' => '#', |
| 29 |
]; |
| 30 |
|
| 31 |
$url = urlencode( $url ); |
| 32 |
return $this->replace_encoding( $url, $allowed ); |
| 33 |
} |
| 34 |
|
| 35 |
private function replace_encoding( $str, $allowed ) { |
| 36 |
foreach ( $allowed as $before => $after ) { |
| 37 |
$str = str_replace( $before, $after, $str ); |
| 38 |
} |
| 39 |
|
| 40 |
return $str; |
| 41 |
} |
| 42 |
|
| 43 |
private function encode( $url ) { |
| 44 |
$allowed = [ |
| 45 |
'%2F' => '/', |
| 46 |
'%3F' => '?', |
| 47 |
'+' => '%20', |
| 48 |
'.' => '\\.', |
| 49 |
]; |
| 50 |
|
| 51 |
return $this->replace_encoding( urlencode( $url ), $allowed ); |
| 52 |
} |
| 53 |
|
| 54 |
private function encode_regex( $url ) { |
| 55 |
// Remove any newlines |
| 56 |
$url = preg_replace( "/[\r\n\t].*?$/s", '', $url ); |
| 57 |
|
| 58 |
// Remove invalid characters |
| 59 |
$url = preg_replace( '/[^\PC\s]/u', '', $url ); |
| 60 |
|
| 61 |
// Make sure spaces are quoted |
| 62 |
$url = str_replace( ' ', '%20', $url ); |
| 63 |
$url = str_replace( '%24', '$', $url ); |
| 64 |
|
| 65 |
// No leading slash |
| 66 |
$url = ltrim( $url, '/' ); |
| 67 |
|
| 68 |
// If pattern has a ^ at the start then ensure we don't have a slash immediatley after |
| 69 |
$url = preg_replace( '@^\^/@', '^', $url ); |
| 70 |
|
| 71 |
return $url; |
| 72 |
} |
| 73 |
|
| 74 |
private function add_referrer( $item, $match ) { |
| 75 |
$from = $this->encode_from( ltrim( $item->get_url(), '/' ) ); |
| 76 |
if ( $item->is_regex() ) { |
| 77 |
$from = $this->encode_regex( ltrim( $item->get_url(), '/' ) ); |
| 78 |
} |
| 79 |
|
| 80 |
if ( ( $match->url_from || $match->url_notfrom ) && $match->referrer ) { |
| 81 |
$this->items[] = sprintf( 'RewriteCond %%{HTTP_REFERER} %s [NC]', ( $match->regex ? $this->encode_regex( $match->referrer ) : $this->encode_from( $match->referrer ) ) ); |
| 82 |
|
| 83 |
if ( $match->url_from ) { |
| 84 |
$to = $this->target( $item->get_action_type(), $match->url_from, $item->get_action_code(), $item->is_regex() ); |
| 85 |
$this->items[] = sprintf( 'RewriteRule %s %s', $from, $to ); |
| 86 |
} |
| 87 |
|
| 88 |
if ( $match->url_notfrom ) { |
| 89 |
$to = $this->target( $item->get_action_type(), $match->url_notfrom, $item->get_action_code(), $item->is_regex() ); |
| 90 |
$this->items[] = sprintf( 'RewriteRule %s %s', $from, $to ); |
| 91 |
} |
| 92 |
} |
| 93 |
} |
| 94 |
|
| 95 |
private function add_agent( $item, $match ) { |
| 96 |
$from = $this->encode( ltrim( $item->get_url(), '/' ) ); |
| 97 |
if ( $item->is_regex() ) { |
| 98 |
$from = $this->encode_regex( ltrim( $item->get_url(), '/' ) ); |
| 99 |
} |
| 100 |
|
| 101 |
if ( ( $match->url_from || $match->url_notfrom ) && $match->user_agent ) { |
| 102 |
$this->items[] = sprintf( 'RewriteCond %%{HTTP_USER_AGENT} %s [NC]', ( $match->regex ? $this->encode_regex( $match->user_agent ) : $this->encode2nd( $match->user_agent ) ) ); |
| 103 |
|
| 104 |
if ( $match->url_from ) { |
| 105 |
$to = $this->target( $item->get_action_type(), $match->url_from, $item->get_action_code(), $item->is_regex() ); |
| 106 |
$this->items[] = sprintf( 'RewriteRule %s %s', $from, $to ); |
| 107 |
} |
| 108 |
|
| 109 |
if ( $match->url_notfrom ) { |
| 110 |
$to = $this->target( $item->get_action_type(), $match->url_notfrom, $item->get_action_code(), $item->is_regex() ); |
| 111 |
$this->items[] = sprintf( 'RewriteRule %s %s', $from, $to ); |
| 112 |
} |
| 113 |
} |
| 114 |
} |
| 115 |
|
| 116 |
private function add_server( $item, $match ) { |
| 117 |
$match->url = $match->url_from; |
| 118 |
$this->items[] = sprintf( 'RewriteCond %%{HTTP_HOST} ^%s$ [NC]', preg_quote( $match->server ) ); |
| 119 |
$this->add_url( $item, $match ); |
| 120 |
} |
| 121 |
|
| 122 |
private function add_url( $item, $match ) { |
| 123 |
$url = $item->get_url(); |
| 124 |
|
| 125 |
if ( $item->is_regex() === false && strpos( $url, '?' ) !== false || strpos( $url, '&' ) !== false ) { |
| 126 |
$url_parts = wp_parse_url( $url ); |
| 127 |
$url = $url_parts['path']; |
| 128 |
$query = isset( $url_parts['query'] ) ? $url_parts['query'] : ''; |
| 129 |
$this->items[] = sprintf( 'RewriteCond %%{QUERY_STRING} ^%s$', $query ); |
| 130 |
} |
| 131 |
|
| 132 |
$to = $this->target( $item->get_action_type(), $match->url, $item->get_action_code(), $item->is_regex() ); |
| 133 |
$from = $this->encode_from( $url ); |
| 134 |
|
| 135 |
if ( $item->is_regex() ) { |
| 136 |
$from = $this->encode_regex( $item->get_url() ); |
| 137 |
} |
| 138 |
|
| 139 |
if ( $to ) { |
| 140 |
$this->items[] = sprintf( 'RewriteRule %s %s', $from, $to ); |
| 141 |
} |
| 142 |
} |
| 143 |
|
| 144 |
private function action_random( $data, $code, $regex ) { |
| 145 |
// Pick a WP post at random |
| 146 |
global $wpdb; |
| 147 |
|
| 148 |
$post = $wpdb->get_var( "SELECT ID FROM {$wpdb->posts} ORDER BY RAND() LIMIT 0,1" ); |
| 149 |
$url = wp_parse_url( get_permalink( $post ) ); |
| 150 |
|
| 151 |
return sprintf( '%s [R=%d,L]', $this->encode( $url['path'] ), $code ); |
| 152 |
} |
| 153 |
|
| 154 |
private function action_pass( $data, $code, $regex ) { |
| 155 |
if ( $regex ) { |
| 156 |
return sprintf( '%s [L]', $this->encode2nd( $data ), $code ); |
| 157 |
} |
| 158 |
return sprintf( '%s [L]', $this->encode2nd( $data ), $code ); |
| 159 |
} |
| 160 |
|
| 161 |
private function action_error( $data, $code, $regex ) { |
| 162 |
if ( $code === 410 ) { |
| 163 |
return '/ [G]'; |
| 164 |
} |
| 165 |
return '/ [F]'; |
| 166 |
} |
| 167 |
|
| 168 |
private function action_url( $data, $code, $regex ) { |
| 169 |
if ( $regex ) { |
| 170 |
return sprintf( '%s [R=%d,L]', $this->encode2nd( $data ), $code ); |
| 171 |
} |
| 172 |
return sprintf( '%s [R=%d,L]', $this->encode2nd( $data ), $code ); |
| 173 |
} |
| 174 |
|
| 175 |
private function target( $action, $data, $code, $regex ) { |
| 176 |
$target = 'action_' . $action; |
| 177 |
|
| 178 |
if ( method_exists( $this, $target ) ) { |
| 179 |
return $this->$target( $data, $code, $regex ); |
| 180 |
} |
| 181 |
return ''; |
| 182 |
} |
| 183 |
|
| 184 |
private function generate() { |
| 185 |
$version = red_get_plugin_data( dirname( dirname( __FILE__ ) ) . '/redirection.php' ); |
| 186 |
|
| 187 |
if ( count( $this->items ) === 0 ) { |
| 188 |
return ''; |
| 189 |
} |
| 190 |
|
| 191 |
$text[] = '# Created by Redirection'; |
| 192 |
$text[] = '# ' . date( 'r' ); |
| 193 |
$text[] = '# Redirection ' . trim( $version['Version'] ) . ' - https://redirection.me'; |
| 194 |
$text[] = ''; |
| 195 |
|
| 196 |
// mod_rewrite section |
| 197 |
$text[] = '<IfModule mod_rewrite.c>'; |
| 198 |
|
| 199 |
// Add http => https option |
| 200 |
$options = red_get_options(); |
| 201 |
if ( $options['https'] ) { |
| 202 |
$text[] = 'RewriteCond %{HTTPS} off'; |
| 203 |
$text[] = 'RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI}'; |
| 204 |
} |
| 205 |
|
| 206 |
// Add redirects |
| 207 |
$text = array_merge( $text, array_filter( array_map( [ $this, 'sanitize_redirect' ], $this->items ) ) ); |
| 208 |
|
| 209 |
// End of mod_rewrite |
| 210 |
$text[] = '</IfModule>'; |
| 211 |
$text[] = ''; |
| 212 |
|
| 213 |
// End of redirection section |
| 214 |
$text[] = '# End of Redirection'; |
| 215 |
|
| 216 |
$text = implode( "\n", $text ); |
| 217 |
return "\n" . $text . "\n"; |
| 218 |
} |
| 219 |
|
| 220 |
public function add( $item ) { |
| 221 |
$target = 'add_' . $item->get_match_type(); |
| 222 |
|
| 223 |
if ( method_exists( $this, $target ) && $item->is_enabled() ) { |
| 224 |
$this->$target( $item, $item->match ); |
| 225 |
} |
| 226 |
} |
| 227 |
|
| 228 |
public function get( $existing = false ) { |
| 229 |
$text = $this->generate(); |
| 230 |
|
| 231 |
if ( $existing ) { |
| 232 |
if ( preg_match( self::INSERT_REGEX, $existing ) > 0 ) { |
| 233 |
$text = preg_replace( self::INSERT_REGEX, str_replace( '$', '\\$', $text ), $existing ); |
| 234 |
} else { |
| 235 |
$text = $text . "\n" . trim( $existing ); |
| 236 |
} |
| 237 |
} |
| 238 |
|
| 239 |
return trim( $text ); |
| 240 |
} |
| 241 |
|
| 242 |
public function sanitize_redirect( $text ) { |
| 243 |
return str_replace( [ '<?', '>' ], '', $text ); |
| 244 |
} |
| 245 |
|
| 246 |
public function sanitize_filename( $filename ) { |
| 247 |
return str_replace( '.php', '', $filename ); |
| 248 |
} |
| 249 |
|
| 250 |
public function save( $filename, $content_to_save = false ) { |
| 251 |
$existing = false; |
| 252 |
$filename = $this->sanitize_filename( $filename ); |
| 253 |
|
| 254 |
if ( file_exists( $filename ) ) { |
| 255 |
$existing = file_get_contents( $filename ); |
| 256 |
} |
| 257 |
|
| 258 |
$file = @fopen( $filename, 'w' ); |
| 259 |
if ( $file ) { |
| 260 |
$result = fwrite( $file, $this->get( $existing ) ); |
| 261 |
fclose( $file ); |
| 262 |
|
| 263 |
return $result !== false; |
| 264 |
} |
| 265 |
|
| 266 |
return false; |
| 267 |
} |
| 268 |
} |
| 269 |
|