Index: src/wp-includes/link-template.php
===================================================================
--- src/wp-includes/link-template.php	(revision 37033)
+++ src/wp-includes/link-template.php	(working copy)
@@ -106,6 +106,56 @@
 }
 
 /**
+ * Retrieves the canonical URL for current post or post ID.
+ * 
+ * @since 
+ *
+ * @param int|WP_Post $post Optional. Post ID or post object. Default is the global `$post`.
+ *
+ * @return false|string The canonical URL or false if the post does not exist or has not been published yet.
+ */
+function get_canonical_url( $post = 0 ) {
+	$post = get_post( $post );
+	
+	if ( ! is_object( $post ) ) {
+		return false;
+	}
+
+	if ( 'publish' !== $post->post_status ) {
+		return false;
+	}
+
+	$canonical_url = get_permalink( $post, false );
+
+	// If we're generating a canonical for the current page, make sure it has pagination if needed.
+	if ( $post->ID === get_queried_object_id() ) {
+		$page = get_query_var( 'page', 0 );
+		if ( $page >= 2 ) {
+			if ( '' == get_option( 'permalink_structure' ) ) {
+				$canonical_url = add_query_arg( 'page', $page, $canonical_url );
+			} else {
+				$canonical_url = trailingslashit( $canonical_url ) . user_trailingslashit( $page, 'single_paged' );
+			}
+		}
+
+		$cpage = get_query_var( 'cpage' );
+		if ( $cpage ) {
+			$canonical_url = get_comments_pagenum_link( $cpage );
+		}
+	}
+
+	/**
+	 * Filter the canonical URL.
+	 *
+	 * @since
+	 *
+	 * @param string $string Canonical URL.
+	 * @param WP_Post $post  Post object. Default is the global `$post`.
+	 */
+	return apply_filters( 'get_canonical_url', $canonical_url, $post );
+}
+
+/**
  * Retrieve full permalink for current post or post ID.
  *
  * @since 1.0.0
@@ -3511,26 +3561,15 @@
 		return;
 	}
 
-	if ( ! $id = get_queried_object_id() ) {
+	$id = get_queried_object_id();
+	if ( 0 === $id ) {
 		return;
 	}
 
-	$url = get_permalink( $id );
-
-	$page = get_query_var( 'page' );
-	if ( $page >= 2 ) {
-		if ( '' == get_option( 'permalink_structure' ) ) {
-			$url = add_query_arg( 'page', $page, $url );
-		} else {
-			$url = trailingslashit( $url ) . user_trailingslashit( $page, 'single_paged' );
-		}
+	$url = get_canonical_url( $id );
+	if ( ! empty( $url ) && is_string( $url ) ) {
+		echo '<link rel="canonical" href="' . esc_url( $url ) . '" />' . "\n";
 	}
-
-	$cpage = get_query_var( 'cpage' );
-	if ( $cpage ) {
-		$url = get_comments_pagenum_link( $cpage );
-	}
-	echo '<link rel="canonical" href="' . esc_url( $url ) . "\" />\n";
 }
 
 /**
Index: tests/phpunit/tests/canonical/getCanonicalURL.php
===================================================================
--- tests/phpunit/tests/canonical/getCanonicalURL.php	(nonexistent)
+++ tests/phpunit/tests/canonical/getCanonicalURL.php	(working copy)
@@ -0,0 +1,158 @@
+<?php
+
+/**
+ * @group canonical
+ */
+
+class Tests_Get_Canonical_URL extends WP_UnitTestCase {
+
+	/**
+	 * Cleaning up
+	 */
+	public function tearDown() {
+		parent::tearDown();
+
+		delete_option('permalink_structure');
+
+		remove_filter( 'get_canonical_url', array($this, 'apply_canonical_filter' ) );
+	}
+
+	/**
+	 * Test for a non existing post
+	 */
+	public function test_non_existing_post() {
+		$subject = get_canonical_url(1);
+		
+		$this->assertFalse( $subject );
+	}
+
+	/**
+	 * Test for a page that is not published
+	 */
+	public function test_post_status() {
+		$post = new stdClass();
+		$post->post_status = 'draft';
+		
+		$subject = get_canonical_url( $post );
+		
+		$this->assertFalse( $subject );
+	}
+
+	/**
+	 * Test for a page that is not the queried object
+	 */
+	public function test_non_current_page() {
+		$post_id = self::factory()->post->create( array(
+			'post_status' => 'publish',
+		) );
+		
+		$subject = get_canonical_url( $post_id );
+		$this->assertEquals( get_permalink( $post_id ), $subject );
+	}
+
+	/**
+	 * Test non permalink structure page usage
+	 */
+	public function test_paged() {
+		global $wp_query;
+		
+		$page = 2;
+		$post_id = self::factory()->post->create( array(
+			'post_status' => 'publish',
+		) );
+		
+		// Should add page to the URL.
+		$wp_query->query_vars['page'] = $page;
+
+		// Set up queried object.
+		$wp_query->queried_object = get_post($post_id);
+		$wp_query->queried_object_id = $post_id;
+		
+		$url = get_permalink( $post_id );
+		$test = add_query_arg( 'page', $page, $url );
+		
+		$subject = get_canonical_url( $post_id );
+		
+		$this->assertEquals( $test, $subject );	
+	}
+
+	/**
+	 * Test permalink structure page usage
+	 */
+	public function test_paged_permalink_structure() {
+		global $wp_query;
+		
+		update_option('permalink_structure', '1');
+		
+		$page = 2;
+		$post_id = self::factory()->post->create( array(
+			'post_status' => 'publish',
+		) );
+
+		// Should add page to the URL.
+		$wp_query->query_vars['page'] = $page;
+		
+		// Set up queried object.
+		$wp_query->queried_object = get_post($post_id);
+		$wp_query->queried_object_id = $post_id;
+
+		$url = get_permalink( $post_id );
+		$test = trailingslashit( $url ) . user_trailingslashit( $page, 'single_paged' );
+		
+		$subject = get_canonical_url( $post_id );
+		
+		$this->assertEquals( $test, $subject );
+	}
+
+	/**
+	 * Test usage of cpage
+	 */
+	public function test_cpage() {
+		global $wp_query;
+		
+		$cpage = 1;
+
+		$post_id = self::factory()->post->create( array(
+			'post_status' => 'publish',
+		) );
+		
+		// Set cpage value.
+		$wp_query->query_vars['cpage'] = $cpage;
+
+		// Set up queried object.
+		$wp_query->queried_object = get_post($post_id);
+		$wp_query->queried_object_id = $post_id;
+		
+		$subject = get_canonical_url( $post_id );
+		
+		$this->assertEquals( get_comments_pagenum_link( $cpage ), $subject );
+	}
+
+	/**
+	 * Test calling of filter
+	 */
+	public function test_filter_applied() {
+		$post_id = self::factory()->post->create( array(
+			'post_status' => 'publish',
+		) );
+
+		// Apply filter
+		add_filter( 'get_canonical_url', array($this, 'apply_canonical_filter' ), 10, 2 );
+		
+		$subject = get_canonical_url( $post_id );
+
+		$this->assertEquals( $this->apply_canonical_filter( get_permalink( $post_id ), get_post( $post_id ) ), $subject );
+	}
+
+	/**
+	 * Filter callback for testing of filter usage
+	 * 
+	 * @param $url
+	 * @param $post
+	 *
+	 * @return string
+	 */
+	public function apply_canonical_filter( $url, $post ) {
+		return 'http://filtered.canonical.com';
+	}
+}
