Make WordPress Core

Changeset 41697


Ignore:
Timestamp:
10/03/2017 03:43:01 AM (7 years ago)
Author:
westonruter
Message:

Customize: Provide validation feedback for invalid Custom Link URLs in nav menu items.

Props RMarks, EGregor, umangvaghela123, andrew.taylor, celloexpressions, westonruter, voldemortensen.
Fixes #32816.

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/css/customize-nav-menus.css

    r41670 r41697  
    560560#custom-menu-item-name.invalid,
    561561#custom-menu-item-url.invalid,
     562.edit-menu-item-url.invalid,
    562563.menu-name-field.invalid,
    563564.menu-name-field.invalid:focus,
  • trunk/src/wp-admin/js/customize-nav-menus.js

    r41020 r41697  
    536536            var menuItem,
    537537                itemName = $( '#custom-menu-item-name' ),
    538                 itemUrl = $( '#custom-menu-item-url' );
     538                itemUrl = $( '#custom-menu-item-url' ),
     539                urlRegex,
     540                urlValue;
    539541
    540542            if ( ! this.currentMenuControl ) {
     
    542544            }
    543545
     546            /*
     547             * Copyright (c) 2010-2013 Diego Perini, MIT licensed
     548             * https://gist.github.com/dperini/729294
     549             * see also https://mathiasbynens.be/demo/url-regex
     550             * modified to allow protocol-relative URLs
     551             */
     552            urlRegex = /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i;
     553
     554            urlValue = itemUrl.val();
    544555            if ( '' === itemName.val() ) {
    545556                itemName.addClass( 'invalid' );
    546557                return;
    547             } else if ( '' === itemUrl.val() || 'http://' === itemUrl.val() ) {
     558            } else if ( '' === urlValue || 'http://' === urlValue || ! ( '/' === urlValue[0] || urlRegex.test( urlValue ) ) ) {
    548559                itemUrl.addClass( 'invalid' );
    549560                return;
     
    552563            menuItem = {
    553564                'title': itemName.val(),
    554                 'url': itemUrl.val(),
     565                'url': urlValue,
    555566                'type': 'custom',
    556567                'type_label': api.Menus.data.l10n.custom_label,
     
    13881399        _setupUpdateUI: function() {
    13891400            var control = this,
    1390                 settingValue = control.setting();
     1401                settingValue = control.setting(),
     1402                updateNotifications;
    13911403
    13921404            control.elements = {};
     
    14711483                }
    14721484            });
     1485
     1486            // Style the URL field as invalid when there is an invalid_url notification.
     1487            updateNotifications = function() {
     1488                control.elements.url.element.toggleClass( 'invalid', control.setting.notifications.has( 'invalid_url' ) );
     1489            };
     1490            control.setting.notifications.bind( 'add', updateNotifications );
     1491            control.setting.notifications.bind( 'removed', updateNotifications );
    14731492        },
    14741493
  • trunk/src/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php

    r41162 r41697  
    642642     *
    643643     * @param array $menu_item_value The value to sanitize.
    644      * @return array|false|null Null if an input isn't valid. False if it is marked for deletion.
    645      *                          Otherwise the sanitized value.
     644     * @return array|false|null|WP_Error Null or WP_Error if an input isn't valid. False if it is marked for deletion.
     645     *                                   Otherwise the sanitized value.
    646646     */
    647647    public function sanitize( $menu_item_value ) {
     
    702702        $menu_item_value['description'] = wp_unslash( apply_filters( 'content_save_pre', wp_slash( $menu_item_value['description'] ) ) );
    703703
    704         $menu_item_value['url'] = esc_url_raw( $menu_item_value['url'] );
     704        if ( '' !== $menu_item_value['url'] ) {
     705            $menu_item_value['url'] = esc_url_raw( $menu_item_value['url'] );
     706            if ( '' === $menu_item_value['url'] ) {
     707                return new WP_Error( 'invalid_url', __( 'Invalid URL.' ) ); // Fail sanitization if URL is invalid.
     708            }
     709        }
    705710        if ( 'publish' !== $menu_item_value['status'] ) {
    706711            $menu_item_value['status'] = 'draft';
  • trunk/tests/phpunit/tests/customize/nav-menu-item-setting.php

    r39393 r41697  
    473473        $this->assertNull( $setting->sanitize( 123 ) );
    474474
     475        $valid_urls = array(
     476            'http://example.com/',
     477            'https://foo.example.com/hello.html',
     478            'mailto:nobody@example.com?subject=hi',
     479            'ftp://example.com/',
     480            'ftps://example.com/',
     481            'news://news.server.example/example.group.this',
     482            'irc://irc.freenode.net/wordpress',
     483            'gopher://example.com',
     484            'nntp://news.server.example/example.group.this',
     485            'feed://example.com/',
     486            'telnet://example.com',
     487            'mms://example.com',
     488            'rtsp://example.com/',
     489            'svn://develop.svn.wordpress.org/trunk',
     490            'tel:000-000-000',
     491            'fax:000-000-000',
     492            'xmpp:user@host?message',
     493            'webcal://example.com',
     494            'urn:org.wordpress',
     495        );
     496        foreach ( $valid_urls as $valid_url ) {
     497            $url_setting = $setting->sanitize( array( 'url' => $valid_url ) );
     498            $this->assertInternalType( 'array', $url_setting );
     499            $this->assertEquals( $valid_url, $url_setting['url'] );
     500        }
     501
     502        $invalid_urls = array(
     503            'javascript:alert(1)',
     504            'unknown://something.out-there',
     505            'smtp://user:pass@mailserver.thing',
     506        );
     507        foreach ( $invalid_urls as $invalid_url ) {
     508            $url_setting = $setting->sanitize( array( 'url' => $invalid_url ) );
     509            $this->assertInstanceOf( 'WP_Error', $url_setting );
     510            $this->assertEquals( 'invalid_url', $url_setting->get_error_code() );
     511        }
     512
    475513        $unsanitized = array(
    476514            'object_id' => 'bad',
     
    480518            'type' => 'custom<b>',
    481519            'title' => '\o/ o\'o Hi<script>unfilteredHtml()</script>',
    482             'url' => 'javascript:alert(1)',
     520            'url' => '', // Note the javascript: protocol is checked above and results in a hard validation error, beyond mere sanitization.
    483521            'target' => '" onclick="',
    484522            'attr_title' => '\o/ o\'o <b>bolded</b><script>unfilteredHtml()</script>',
Note: See TracChangeset for help on using the changeset viewer.