commit f94dbc20e9a5a1335aebd78a055255007aabeddc
Author: Alain Schlesser <alain.schlesser@gmail.com>
Date:   Wed Nov 16 15:58:03 2016 +0100

    Accept both IPv4 and IPv6 addresses for comment author IP.
    
    - Add IPv4 & IPv6 versions of the `rest_is_ip_address()` function.
    - Provide validation routing for `ip`, `ipv4` and `ipv6`.
    - Validate `author_ip` against `ip` instead of `ipv4`

diff --git src/wp-includes/rest-api.php src/wp-includes/rest-api.php
index 255b8dc..411d275 100644
--- src/wp-includes/rest-api.php
+++ src/wp-includes/rest-api.php
@@ -869,23 +869,41 @@ function rest_parse_request_arg( $value, $request, $param ) {
 }
 
 /**
- * Determines if a IPv4 address is valid.
+ * Determines if an IP address is valid.
+ *
+ * Handles both IPv4 and IPv6 addresses.
+ *
+ * @since 4.7.0
  *
- * Does not handle IPv6 addresses.
+ * @param  string $ip IP address.
+ * @return string|false The valid IP address, otherwise false.
+ */
+function rest_is_ip_address( $ip ) {
+	return rest_is_ipv4_address( $ip ) || rest_is_ipv6_address( $ip );
+}
+
+/**
+ * Determines if a IPv4 address is valid.
  *
  * @since 4.7.0
  *
  * @param  string $ipv4 IP 32-bit address.
  * @return string|false The valid IPv4 address, otherwise false.
  */
-function rest_is_ip_address( $ipv4 ) {
-	$pattern = '/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/';
-
-	if ( ! preg_match( $pattern, $ipv4 ) ) {
-		return false;
-	}
+function rest_is_ipv4_address( $ipv4 ) {
+	return filter_var( $ipv4, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 );
+}
 
-	return $ipv4;
+/**
+ * Determines if a IPv6 address is valid.
+ *
+ * @since 4.7.0
+ *
+ * @param  string $ipv6 IPv6 address.
+ * @return string|false The valid IPv6 address, otherwise false.
+ */
+function rest_is_ipv6_address( $ipv6 ) {
+	return filter_var( $ipv6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 );
 }
 
 /**
@@ -1053,12 +1071,24 @@ function rest_validate_value_from_schema( $value, $args, $param = '' ) {
 					return new WP_Error( 'rest_invalid_email', __( 'The email address you provided is invalid.' ) );
 				}
 				break;
-			case 'ipv4' :
+			case 'ip' :
 				if ( ! rest_is_ip_address( $value ) ) {
-					/* translators: %s: IP address */ 
+					/* translators: %s: IP address */
 					return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $value ) );
 				}
 				break;
+			case 'ipv4' :
+				if ( ! rest_is_ipv4_address( $value ) ) {
+					/* translators: %s: IP address */
+					return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IPv4 address.' ), $value ) );
+				}
+				break;
+			case 'ipv6' :
+				if ( ! rest_is_ipv6_address( $value ) ) {
+					/* translators: %s: IP address */
+					return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IPv6 address.' ), $value ) );
+				}
+				break;
 		}
 	}
 
diff --git src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php
index 15e18f7..3a0ca0a 100644
--- src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php
+++ src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php
@@ -1115,7 +1115,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
 				'author_ip'     => array(
 					'description'  => __( 'IP address for the object author.' ),
 					'type'         => 'string',
-					'format'       => 'ipv4',
+					'format'       => 'ip',
 					'context'      => array( 'edit' ),
 					'default'      => '127.0.0.1',
 				),
diff --git tests/phpunit/tests/rest-api/rest-schema-validation.php tests/phpunit/tests/rest-api/rest-schema-validation.php
index 4e4a111..982bb10 100644
--- tests/phpunit/tests/rest-api/rest-schema-validation.php
+++ tests/phpunit/tests/rest-api/rest-schema-validation.php
@@ -83,6 +83,32 @@ class WP_Test_REST_Schema_Validation extends WP_UnitTestCase {
 		$this->assertWPError( rest_validate_value_from_schema( '2016-06-30', $schema ) );
 	}
 
+	public function test_format_ip() {
+		$schema = array(
+			'type'  => 'string',
+			'format' => 'ip',
+		);
+
+		// IPv4.
+		$this->assertTrue( rest_validate_value_from_schema( '127.0.0.1', $schema ) );
+		$this->assertWPError( rest_validate_value_from_schema( '3333.3333.3333.3333', $schema ) );
+		$this->assertWPError( rest_validate_value_from_schema( '1', $schema ) );
+
+		// IPv6.
+		$this->assertTrue( rest_validate_value_from_schema( '::1', $schema ) ); // Loopback, compressed, non-routable.
+		$this->assertTrue( rest_validate_value_from_schema( '::', $schema ) ); // Unspecified, compressed, non-routable.
+		$this->assertTrue( rest_validate_value_from_schema( '0:0:0:0:0:0:0:1', $schema ) ); // Loopback, full.
+		$this->assertTrue( rest_validate_value_from_schema( '0:0:0:0:0:0:0:0', $schema ) ); // Unspecified, full.
+		$this->assertTrue( rest_validate_value_from_schema( '2001:DB8:0:0:8:800:200C:417A', $schema ) ); // Unicast, full.
+		$this->assertTrue( rest_validate_value_from_schema( 'FF01:0:0:0:0:0:0:101', $schema ) ); // Multicast, full.
+		$this->assertTrue( rest_validate_value_from_schema( '2001:DB8::8:800:200C:417A', $schema ) ); // Unicast, compressed.
+		$this->assertTrue( rest_validate_value_from_schema( 'FF01::101', $schema ) ); // Multicast, compressed.
+		$this->assertTrue( rest_validate_value_from_schema( 'fe80::217:f2ff:fe07:ed62', $schema ) );
+		$this->assertWPError( rest_validate_value_from_schema( '', $schema ) ); // Empty string.
+		$this->assertWPError( rest_validate_value_from_schema( '2001:DB8:0:0:8:800:200C:417A:221', $schema ) ); // Unicast, full.
+		$this->assertWPError( rest_validate_value_from_schema( 'FF01::101::2', $schema ) ); // Multicast, compressed.
+	}
+
 	public function test_format_ipv4() {
 		$schema = array(
 			'type'  => 'string',
@@ -93,6 +119,25 @@ class WP_Test_REST_Schema_Validation extends WP_UnitTestCase {
 		$this->assertWPError( rest_validate_value_from_schema( '1', $schema ) );
 	}
 
+	public function test_format_ipv6() {
+		$schema = array(
+			'type'  => 'string',
+			'format' => 'ipv6',
+		);
+		$this->assertTrue( rest_validate_value_from_schema( '::1', $schema ) ); // Loopback, compressed, non-routable.
+		$this->assertTrue( rest_validate_value_from_schema( '::', $schema ) ); // Unspecified, compressed, non-routable.
+		$this->assertTrue( rest_validate_value_from_schema( '0:0:0:0:0:0:0:1', $schema ) ); // Loopback, full.
+		$this->assertTrue( rest_validate_value_from_schema( '0:0:0:0:0:0:0:0', $schema ) ); // Unspecified, full.
+		$this->assertTrue( rest_validate_value_from_schema( '2001:DB8:0:0:8:800:200C:417A', $schema ) ); // Unicast, full.
+		$this->assertTrue( rest_validate_value_from_schema( 'FF01:0:0:0:0:0:0:101', $schema ) ); // Multicast, full.
+		$this->assertTrue( rest_validate_value_from_schema( '2001:DB8::8:800:200C:417A', $schema ) ); // Unicast, compressed.
+		$this->assertTrue( rest_validate_value_from_schema( 'FF01::101', $schema ) ); // Multicast, compressed.
+		$this->assertTrue( rest_validate_value_from_schema( 'fe80::217:f2ff:fe07:ed62', $schema ) );
+		$this->assertWPError( rest_validate_value_from_schema( '', $schema ) ); // Empty string.
+		$this->assertWPError( rest_validate_value_from_schema( '2001:DB8:0:0:8:800:200C:417A:221', $schema ) ); // Unicast, full.
+		$this->assertWPError( rest_validate_value_from_schema( 'FF01::101::2', $schema ) ); // Multicast, compressed.
+	}
+
 	public function test_type_array() {
 		$schema = array(
 			'type' => 'array',
