Index: src/wp-includes/class-wp-rewrite.php
===================================================================
--- src/wp-includes/class-wp-rewrite.php	(revision 37579)
+++ src/wp-includes/class-wp-rewrite.php	(working copy)
@@ -1938,6 +1938,74 @@
 	}
 
 	/**
+	 * Get the rewrite rule that matches a request.
+	 *
+	 * @since 4.6.0
+	 *
+	 * @param string $requested_path  The URL path to match against.
+	 * @param string $requested_file  The requested file, for PATHINFO links.
+	 * @return array|false $rewritten If a rule matches, an array containing
+	 *                                the match and the query.
+	 *                                False otherwise.
+	 */
+	public function get_rewrite_rule_for_request( $requested_path, $requested_file ) {
+		$rewrite_rules = $this->wp_rewrite_rules();
+		if ( empty( $rewrite_rules ) ) {
+			return false;
+		}
+
+		// Look for matches.
+		$request_match = $requested_path;
+		if ( empty( $request_match ) ) {
+			// An empty request could only match against ^$ regex
+			if ( isset( $rewrite_rules['$'] ) ) {
+				$matched_regex = '$';
+				$query = $rewrite_rules['$'];
+				$matches = array('');
+			}
+		} else {
+			foreach ( (array) $rewrite_rules as $regex => $query ) {
+				// If the requesting file is the anchor of the match, prepend it to the path info.
+				if ( ! empty($requested_file) && strpos($regex, $requested_file) === 0 && $requested_file != $requested_path )
+					$request_match = $requested_file . '/' . $requested_path;
+
+				if ( preg_match("#^$regex#", $request_match, $matches) ||
+					preg_match("#^$regex#", urldecode($request_match), $matches) ) {
+
+					if ( $this->use_verbose_page_rules && preg_match( '/pagename=\$matches\[([0-9]+)\]/', $query, $varmatch ) ) {
+						// This is a verbose page match, let's check to be sure about it.
+						$page = get_page_by_path( $matches[ $varmatch[1] ] );
+						if ( ! $page ) {
+							continue;
+						}
+
+						$post_status_obj = get_post_status_object( $page->post_status );
+						if ( ! $post_status_obj->public && ! $post_status_obj->protected
+							&& ! $post_status_obj->private && $post_status_obj->exclude_from_search ) {
+							continue;
+						}
+					}
+
+					// Got a match.
+					$matched_regex = $regex;
+					break;
+				}
+			}
+		}
+		if ( isset( $matched_regex ) ) {
+			// Trim the query of everything up to the '?'.
+			$query = preg_replace("!^.+\?!", '', $query);
+
+			// Substitute the substring matches into the query.
+			$query = addslashes(WP_MatchesMapRegex::apply($query, $matches));
+
+			return array( $matched_regex, $query );
+		} else {
+			return false;
+		}
+	}
+
+	/**
 	 * Constructor - Calls init(), which runs setup.
 	 *
 	 * @since 1.5.0
Index: src/wp-includes/class-wp.php
===================================================================
--- src/wp-includes/class-wp.php	(revision 37579)
+++ src/wp-includes/class-wp.php	(working copy)
@@ -212,57 +212,13 @@
 			$requested_file = $req_uri;
 
 			$this->request = $requested_path;
+			$rewrite_match = $wp_rewrite->get_rewrite_rule_for_request( $requested_path, $requested_file );
 
-			// Look for matches.
-			$request_match = $requested_path;
-			if ( empty( $request_match ) ) {
-				// An empty request could only match against ^$ regex
-				if ( isset( $rewrite['$'] ) ) {
-					$this->matched_rule = '$';
-					$query = $rewrite['$'];
-					$matches = array('');
-				}
-			} else {
-				foreach ( (array) $rewrite as $match => $query ) {
-					// If the requested file is the anchor of the match, prepend it to the path info.
-					if ( ! empty($requested_file) && strpos($match, $requested_file) === 0 && $requested_file != $requested_path )
-						$request_match = $requested_file . '/' . $requested_path;
-
-					if ( preg_match("#^$match#", $request_match, $matches) ||
-						preg_match("#^$match#", urldecode($request_match), $matches) ) {
-
-						if ( $wp_rewrite->use_verbose_page_rules && preg_match( '/pagename=\$matches\[([0-9]+)\]/', $query, $varmatch ) ) {
-							// This is a verbose page match, let's check to be sure about it.
-							$page = get_page_by_path( $matches[ $varmatch[1] ] );
-							if ( ! $page ) {
-						 		continue;
-							}
-
-							$post_status_obj = get_post_status_object( $page->post_status );
-							if ( ! $post_status_obj->public && ! $post_status_obj->protected
-								&& ! $post_status_obj->private && $post_status_obj->exclude_from_search ) {
-								continue;
-							}
-						}
-
-						// Got a match.
-						$this->matched_rule = $match;
-						break;
-					}
-				}
-			}
-
-			if ( isset( $this->matched_rule ) ) {
-				// Trim the query of everything up to the '?'.
-				$query = preg_replace("!^.+\?!", '', $query);
-
-				// Substitute the substring matches into the query.
-				$query = addslashes(WP_MatchesMapRegex::apply($query, $matches));
-
-				$this->matched_query = $query;
-
+			if ( $rewrite_match ) {
+				$this->matched_rule = $rewrite_match[0];
+				$this->matched_query = $rewrite_match[1];
 				// Parse the query.
-				parse_str($query, $perma_query_vars);
+				parse_str($this->matched_query, $perma_query_vars);
 
 				// If we're processing a 404 request, clear the error var since we found something.
 				if ( '404' == $error )
Index: src/wp-includes/rewrite.php
===================================================================
--- src/wp-includes/rewrite.php	(revision 37579)
+++ src/wp-includes/rewrite.php	(working copy)
@@ -539,61 +539,34 @@
 	}
 
 	// Look for matches.
-	$request_match = $request;
-	foreach ( (array)$rewrite as $match => $query) {
+	$rewrite_match = $wp_rewrite->get_rewrite_rule_for_request( $request, $url );
+	if ( $rewrite_match ) {
+		$match = $rewrite_match[0];
+		$query = $rewrite_match[1];
 
-		// If the requesting file is the anchor of the match, prepend it
-		// to the path info.
-		if ( !empty($url) && ($url != $request) && (strpos($match, $url) === 0) )
-			$request_match = $url . '/' . $request;
-
-		if ( preg_match("#^$match#", $request_match, $matches) ) {
-
-			if ( $wp_rewrite->use_verbose_page_rules && preg_match( '/pagename=\$matches\[([0-9]+)\]/', $query, $varmatch ) ) {
-				// This is a verbose page match, let's check to be sure about it.
-				$page = get_page_by_path( $matches[ $varmatch[1] ] );
-				if ( ! $page ) {
-					continue;
+		// Filter out non-public query vars
+		global $wp;
+		parse_str( $query, $query_vars );
+		$query = array();
+		foreach ( (array) $query_vars as $key => $value ) {
+			if ( in_array( $key, $wp->public_query_vars ) ){
+				$query[$key] = $value;
+				if ( isset( $post_type_query_vars[$key] ) ) {
+					$query['post_type'] = $post_type_query_vars[$key];
+					$query['name'] = $value;
 				}
-
-				$post_status_obj = get_post_status_object( $page->post_status );
-				if ( ! $post_status_obj->public && ! $post_status_obj->protected
-					&& ! $post_status_obj->private && $post_status_obj->exclude_from_search ) {
-					continue;
-				}
 			}
+		}
 
-			// Got a match.
-			// Trim the query of everything up to the '?'.
-			$query = preg_replace("!^.+\?!", '', $query);
+		// Resolve conflicts between posts with numeric slugs and date archive queries.
+		$query = wp_resolve_numeric_slug_conflicts( $query );
 
-			// Substitute the substring matches into the query.
-			$query = addslashes(WP_MatchesMapRegex::apply($query, $matches));
-
-			// Filter out non-public query vars
-			global $wp;
-			parse_str( $query, $query_vars );
-			$query = array();
-			foreach ( (array) $query_vars as $key => $value ) {
-				if ( in_array( $key, $wp->public_query_vars ) ){
-					$query[$key] = $value;
-					if ( isset( $post_type_query_vars[$key] ) ) {
-						$query['post_type'] = $post_type_query_vars[$key];
-						$query['name'] = $value;
-					}
-				}
-			}
-
-			// Resolve conflicts between posts with numeric slugs and date archive queries.
-			$query = wp_resolve_numeric_slug_conflicts( $query );
-
-			// Do the query
-			$query = new WP_Query( $query );
-			if ( ! empty( $query->posts ) && $query->is_singular )
-				return $query->post->ID;
-			else
-				return 0;
-		}
+		// Do the query
+		$query = new WP_Query( $query );
+		if ( ! empty( $query->posts ) && $query->is_singular )
+			return $query->post->ID;
+		else
+			return 0;
 	}
 	return 0;
 }
Index: tests/phpunit/tests/rewrite/getRewriteRuleForRequest.php
===================================================================
--- tests/phpunit/tests/rewrite/getRewriteRuleForRequest.php	(nonexistent)
+++ tests/phpunit/tests/rewrite/getRewriteRuleForRequest.php	(working copy)
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * @group rewrite
+ */
+class Tests_Rewrite_FindMatch extends WP_UnitTestCase {
+
+	public function test_get_rewrite_rule_for_request_for_front_page() {
+		global $wp_rewrite;
+
+		$rules = array( '$' => 'index.php?page=home' );
+		update_option( 'rewrite_rules', $rules );
+		$match = $wp_rewrite->get_rewrite_rule_for_request( '', '' );
+		$this->assertSame( 'page=home', $match[1] );
+	}
+
+	public function test_get_rewrite_rule_for_request() {
+		global $wp_rewrite;
+
+		$rules = array(
+			'page/?([0-9]{1,})/?$' => 'index.php?paged=$matches[1]',
+			'search/(.+)/?$' => 'index.php?s=$matches[1]',
+			'blog/author/([^/]+)/page/?([0-9]{1,})/?$' => 'index.php?author_name=$matches[1]&paged=$matches[2]',
+			'blog/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]',
+		);
+		update_option( 'rewrite_rules', $rules );
+
+		$match = $wp_rewrite->get_rewrite_rule_for_request( 'page/2', '' );
+		$this->assertSame( 'paged=2', $match[1] );
+
+		$match = $wp_rewrite->get_rewrite_rule_for_request( 'search/kombucha', '' );
+		$this->assertSame( 's=kombucha', $match[1] );
+
+		$match = $wp_rewrite->get_rewrite_rule_for_request( 'blog/author/glerf/page/29', '' );
+		$this->assertSame( 'author_name=glerf&paged=29', $match[1] );
+
+		$match = $wp_rewrite->get_rewrite_rule_for_request( 'blog/2015/06/29', '', $rules );
+		$this->assertSame( 'year=2015&monthnum=06&day=29', $match[1] );
+	}
+
+	public function test_match_page_with_verbose_page_rules_on() {
+		global $wp_rewrite;
+		$this->set_permalink_structure( '/%postname%/' );
+
+		$page = self::factory()->post->create( array(
+			'post_type' => 'page',
+			'post_status' => 'publish',
+			'post_name' => 'how-to-brew-kombucha',
+		) );
+
+		$rules = array(
+			'(.?.+?)(?:/([0-9]+))?/?$' => 'index.php?pagename=$matches[1]&page=$matches[2]',
+			'([^/]+)(?:/([0-9]+))?/?$' => 'index.php?name=$matches[1]&page=$matches[2]',
+		);
+		update_option( 'rewrite_rules', $rules );
+
+		$match = $wp_rewrite->get_rewrite_rule_for_request( 'how-to-brew-kombucha', 'how-to-brew-kombucha' );
+		$expected = 'pagename=how-to-brew-kombucha&page=';
+		$this->assertSame( $expected, $match[1] );
+
+		$this->set_permalink_structure();
+	}
+
+	public function test_skip_page_rule_if_page_doesnt_exist_with_verbose_page_rules_on() {
+		global $wp_rewrite;
+		$this->set_permalink_structure( '/%postname%/' );
+
+		$page = self::factory()->post->create( array(
+			'post_type' => 'page',
+			'post_status' => 'publish',
+			'post_name' => 'how-to-brew-kombucha',
+		) );
+
+		$rules = array(
+			'(.?.+?)(?:/([0-9]+))?/?$' => 'index.php?pagename=$matches[1]&page=$matches[2]',
+			'([^/]+)(?:/([0-9]+))?/?$' => 'index.php?name=$matches[1]&page=$matches[2]',
+		);
+		update_option( 'rewrite_rules', $rules );
+
+		$match = $wp_rewrite->get_rewrite_rule_for_request( 'a-vertical-garden-design-for-urban-living', 'a-vertical-garden-design-for-urban-living' );
+		$expected = 'name=a-vertical-garden-design-for-urban-living&page=';
+		$this->assertSame( $expected, $match[1] );
+
+		$this->set_permalink_structure();
+	}
+
+	public function test_skip_page_rule_for_a_trashed_page_with_verbose_page_rules_on() {
+		global $wp_rewrite;
+		$this->set_permalink_structure( '/%postname%/' );
+
+		$trashed_page = self::factory()->post->create( array(
+			'post_type' => 'page',
+			'post_status' => 'trash',
+			'post_name' => 'how-to-brew-kombucha',
+		) );
+
+		$rules = array(
+			'(.?.+?)(?:/([0-9]+))?/?$' => 'index.php?pagename=$matches[1]&page=$matches[2]',
+			'([^/]+)(?:/([0-9]+))?/?$' => 'index.php?name=$matches[1]&page=$matches[2]',
+		);
+		update_option( 'rewrite_rules', $rules );
+
+		$match = $wp_rewrite->get_rewrite_rule_for_request( 'how-to-brew-kombucha', 'how-to-brew-kombucha' );
+		$expected = 'name=how-to-brew-kombucha&page=';
+		$this->assertSame( $expected, $match[1] );
+
+		$this->set_permalink_structure();
+	}
+
+	public function test_get_rewrite_rule_for_request_with_path_info_links() {
+		global $wp_rewrite;
+
+		$rules = array(
+			'index.php/page/?([0-9]{1,})/?$' => 'index.php?paged=$matches[1]',
+			'index.php/search/(.+)/?$' => 'index.php?s=$matches[1]',
+			'index.php/blog/author/([^/]+)/page/?([0-9]{1,})/?$' => 'index.php?author_name=$matches[1]&paged=$matches[2]',
+			'index.php/blog/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]',
+		);
+		update_option( 'rewrite_rules', $rules );
+
+		$match = $wp_rewrite->get_rewrite_rule_for_request( 'page/2', 'index.php' );
+		$this->assertSame( 'paged=2', $match[1] );
+
+		$match = $wp_rewrite->get_rewrite_rule_for_request( 'search/kombucha', 'index.php' );
+		$this->assertSame( 's=kombucha', $match[1] );
+
+		$match = $wp_rewrite->get_rewrite_rule_for_request( 'blog/author/glerf/page/29', 'index.php' );
+		$this->assertSame( 'author_name=glerf&paged=29', $match[1] );
+
+		$match = $wp_rewrite->get_rewrite_rule_for_request( 'blog/2015/06/29', 'index.php' );
+		$this->assertSame( 'year=2015&monthnum=06&day=29', $match[1] );
+	}
+}
