Changeset 201404 in webkit


Ignore:
Timestamp:
May 25, 2016 2:58:08 PM (8 years ago)
Author:
Brent Fulgham
Message:

[WebSockets] No infrastructure for testing secure web sockets (wss)
https://bugs.webkit.org/show_bug.cgi?id=157884
<rdar://problem/26477197>

Reviewed by Andy Estes.

Source/WebCore:

Add a new test-only flag used to tell CFNetwork that we do not wish to
validate the SLL certificate chain. This allows us to use self-signed
certificates in test cases.

Tests: http/tests/websocket/tests/hybi/simple-wss.html

  • page/Settings.cpp:

(WebCore::Settings::setAllowsAnySSLCertificate): Added.
(WebCore::Settings::allowsAnySSLCertificate): Added. This defaults
to False.

  • page/Settings.h:
  • platform/network/cf/SocketStreamHandleCFNet.cpp:

(WebCore::SocketStreamHandle::createStreams): When running under our
testing infrastructure, do not require full certificate validation.

  • testing/js/WebCoreTestSupport.cpp:

(WebCoreTestSupport::setAllowsAnySSLCertificate): Added.

  • testing/js/WebCoreTestSupport.h:
  • testing/InternalSettings.cpp:

(WebCore::InternalSettings::setAllowsAnySSLCertificate): Added.

  • testing/InternalSettings.h:

Tools:

Add support to webkitpy to start and stop a secure Web Socket server running on port 9323
using the certificate, private-key from file LayoutTests/http/conf/webkit-httpd.pem. Also
teaches run-webkit-httpd to start and stop the Web Socket servers.

Modify DumpRenderTree and WebKitTestRunner to understand a new testRunner method,
'setAllowsAnySSLCertificate', which allows us to use the same self-signed test certificate
we do for our HTTPS tests.

  • DumpRenderTree/TestRunner.cpp:

(setAllowsAnySSLCertificateCallback):
(TestRunner::setAllowsAnySSLCertificate):

  • DumpRenderTree/TestRunner.h:
  • DumpRenderTree/mac/DumpRenderTree.mm:

(resetWebViewToConsistentStateBeforeTesting): Make sure we turn off the new flag between tests.

  • Scripts/run-webkit-httpd:

(main): Start the websocket server at launch.

  • Scripts/webkitpy/layout_tests/controllers/manager.py:

(Manager.init): Remove dead code.

  • Scripts/webkitpy/layout_tests/servers/websocket_server.py:

(PyWebSocket.init): Cleanup code.
(PyWebSocket): Pass '--tls-client-ca' to start command.
(PyWebSocket._prepare_config): Cleanups.

  • Scripts/webkitpy/port/base.py:

(Port.to.start_http_server):
(Port.to):
(Port.to._extract_certificate_from_pem): Added.
(Port.to._extract_private_key_from_pem): Added.
(Port.to.start_websocket_server): Start secure socket server.
(Port.to.stop_websocket_server): Stop secure socket server.

  • WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl: Add new API.
  • WebKitTestRunner/InjectedBundle/InjectedBundle.cpp:

(WTR::InjectedBundle::setAllowsAnySSLCertificate): Added.

  • WebKitTestRunner/InjectedBundle/InjectedBundle.h:
  • WebKitTestRunner/InjectedBundle/TestRunner.cpp:

(WTR::TestRunner::setAllowsAnySSLCertificate): Added.

  • WebKitTestRunner/InjectedBundle/TestRunner.h:

LayoutTests:

  • http/tests/websocket/tests/hybi/simple-wss-expected.txt: Added.
  • http/tests/websocket/tests/hybi/simple-wss.html: Added.
Location:
trunk
Files:
2 added
22 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r201400 r201404  
     12016-05-25  Daniel Bates  <dabates@apple.com> and Brent Fulgham  <bfulgham@apple.com>
     2
     3        [WebSockets] No infrastructure for testing secure web sockets (wss)
     4        https://bugs.webkit.org/show_bug.cgi?id=157884
     5        <rdar://problem/26477197>
     6
     7        Reviewed by Andy Estes.
     8
     9        * http/tests/websocket/tests/hybi/simple-wss-expected.txt: Added.
     10        * http/tests/websocket/tests/hybi/simple-wss.html: Added.
     11
    1122016-05-25  Ryan Haddad  <ryanhaddad@apple.com>
    213
  • trunk/Source/WebCore/ChangeLog

    r201403 r201404  
     12016-05-25  Brent Fulgham  <bfulgham@apple.com>
     2
     3        [WebSockets] No infrastructure for testing secure web sockets (wss)
     4        https://bugs.webkit.org/show_bug.cgi?id=157884
     5        <rdar://problem/26477197>
     6
     7        Reviewed by Andy Estes.
     8
     9        Add a new test-only flag used to tell CFNetwork that we do not wish to
     10        validate the SLL certificate chain. This allows us to use self-signed
     11        certificates in test cases.
     12
     13        Tests: http/tests/websocket/tests/hybi/simple-wss.html
     14
     15        * page/Settings.cpp:
     16        (WebCore::Settings::setAllowsAnySSLCertificate): Added.
     17        (WebCore::Settings::allowsAnySSLCertificate): Added. This defaults
     18        to False.
     19        * page/Settings.h:
     20        * platform/network/cf/SocketStreamHandleCFNet.cpp:
     21        (WebCore::SocketStreamHandle::createStreams): When running under our
     22        testing infrastructure, do not require full certificate validation.
     23        * testing/js/WebCoreTestSupport.cpp:
     24        (WebCoreTestSupport::setAllowsAnySSLCertificate): Added.
     25        * testing/js/WebCoreTestSupport.h:
     26        * testing/InternalSettings.cpp:
     27        (WebCore::InternalSettings::setAllowsAnySSLCertificate): Added.
     28        * testing/InternalSettings.h:
     29
    1302016-05-25  Jer Noble  <jer.noble@apple.com>
    231
  • trunk/Source/WebCore/page/Settings.cpp

    r200534 r201404  
    102102bool Settings::gLowPowerVideoAudioBufferSizeEnabled = false;
    103103bool Settings::gResourceLoadStatisticsEnabledEnabled = false;
     104bool Settings::gAllowsAnySSLCertificate = false;
    104105
    105106#if PLATFORM(IOS)
     
    755756}
    756757
     758void Settings::setAllowsAnySSLCertificate(bool allowAnySSLCertificate)
     759{
     760    gAllowsAnySSLCertificate = allowAnySSLCertificate;
     761}
     762
     763bool Settings::allowsAnySSLCertificate()
     764{
     765    return gAllowsAnySSLCertificate;
     766}
     767
    757768} // namespace WebCore
  • trunk/Source/WebCore/page/Settings.h

    r200534 r201404  
    11/*
    2  * Copyright (C) 2003, 2006, 2007, 2008, 2009, 2011, 2012, 2013 Apple Inc. All rights reserved.
     2 * Copyright (C) 2003-2016 Apple Inc. All rights reserved.
    33 *           (C) 2006 Graham Dennis (graham.dennis@gmail.com)
    44 *
     
    286286    WEBCORE_EXPORT void setForcePendingWebGLPolicy(bool);
    287287    bool isForcePendingWebGLPolicy() const { return m_forcePendingWebGLPolicy; }
    288    
     288
     289    WEBCORE_EXPORT static void setAllowsAnySSLCertificate(bool);
     290    static bool allowsAnySSLCertificate();
     291
    289292#if USE(APPLE_INTERNAL_SDK)
    290293#import <WebKitAdditions/SettingsGettersAndSetters.h>
     
    382385    static bool gLowPowerVideoAudioBufferSizeEnabled;
    383386    static bool gResourceLoadStatisticsEnabledEnabled;
     387    static bool gAllowsAnySSLCertificate;
    384388
    385389#if USE(APPLE_INTERNAL_SDK)
  • trunk/Source/WebCore/platform/network/cf/SocketStreamHandleCFNet.cpp

    r199039 r201404  
    11/*
    2  * Copyright (C) 2009 Apple Inc.  All rights reserved.
     2 * Copyright (C) 2009-2016 Apple Inc.  All rights reserved.
    33 * Copyright (C) 2009 Google Inc.  All rights reserved.
    44 *
     
    3838#include "NetworkingContext.h"
    3939#include "ProtectionSpace.h"
     40#include "Settings.h"
    4041#include "SocketStreamError.h"
    4142#include "SocketStreamHandleClient.h"
     
    341342
    342343    if (shouldUseSSL()) {
    343         const void* keys[] = { kCFStreamSSLPeerName, kCFStreamSSLLevel };
    344         const void* values[] = { host.get(), kCFStreamSocketSecurityLevelNegotiatedSSL };
     344        CFBooleanRef validateCertificateChain = Settings::allowsAnySSLCertificate() ? kCFBooleanFalse : kCFBooleanTrue;
     345        const void* keys[] = { kCFStreamSSLPeerName, kCFStreamSSLLevel, kCFStreamSSLValidatesCertificateChain };
     346        const void* values[] = { host.get(), kCFStreamSocketSecurityLevelNegotiatedSSL, validateCertificateChain };
    345347        RetainPtr<CFDictionaryRef> settings = adoptCF(CFDictionaryCreate(0, keys, values, WTF_ARRAY_LENGTH(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
    346348        CFReadStreamSetProperty(m_readStream.get(), kCFStreamPropertySSLSettings, settings.get());
  • trunk/Source/WebCore/testing/InternalSettings.cpp

    r201187 r201404  
    185185    settings.setUserInterfaceDirectionPolicy(m_userInterfaceDirectionPolicy);
    186186    settings.setSystemLayoutDirection(m_systemLayoutDirection);
     187    Settings::setAllowsAnySSLCertificate(false);
    187188}
    188189
     
    619620}
    620621
     622void InternalSettings::setAllowsAnySSLCertificate(bool allowsAnyCertificate)
     623{
     624    Settings::setAllowsAnySSLCertificate(allowsAnyCertificate);
     625}
     626
    621627// If you add to this list, make sure that you update the Backup class for test reproducability!
    622628
  • trunk/Source/WebCore/testing/InternalSettings.h

    r201187 r201404  
    11/*
    22 * Copyright (C) 2012 Google Inc. All rights reserved.
    3  * Copyright (C) 2013, 2014, 2015, 2016 Apple Inc. All rights reserved.
     3 * Copyright (C) 2013-2016 Apple Inc. All rights reserved.
    44 *
    55 * Redistribution and use in source and binary forms, with or without
     
    164164    void setSystemLayoutDirection(const String& direction, ExceptionCode&);
    165165
     166    static void setAllowsAnySSLCertificate(bool);
     167
    166168private:
    167169    explicit InternalSettings(Page*);
  • trunk/Source/WebCore/testing/js/WebCoreTestSupport.cpp

    r201239 r201404  
    11/*
    22 * Copyright (C) 2011, 2015 Google Inc. All rights reserved.
     3 * Copyright (C) 2016 Apple Inc. All rights reserved.
    34 *
    45 * Redistribution and use in source and binary forms, with or without
     
    114115}
    115116
     117void setAllowsAnySSLCertificate(bool allowAnySSLCertificate)
     118{
     119    InternalSettings::setAllowsAnySSLCertificate(allowAnySSLCertificate);
    116120}
     121
     122}
  • trunk/Source/WebCore/testing/js/WebCoreTestSupport.h

    r200346 r201404  
    11/*
    22 * Copyright (C) 2011, 2015 Google Inc. All rights reserved.
     3 * Copyright (C) 2016 Apple Inc. All rights reserved.
    34 *
    45 * Redistribution and use in source and binary forms, with or without
     
    5455void setLogChannelToAccumulate(const WTF::String& name) TEST_SUPPORT_EXPORT;
    5556void initializeLoggingChannelsIfNecessary() TEST_SUPPORT_EXPORT;
     57void setAllowsAnySSLCertificate(bool) TEST_SUPPORT_EXPORT;
    5658
    5759} // namespace WebCore
  • trunk/Tools/ChangeLog

    r201391 r201404  
     12016-05-25  Daniel Bates  <dabates@apple.com> and Brent Fulgham  <bfulgham@apple.com>
     2
     3        [WebSockets] No infrastructure for testing secure web sockets (wss)
     4        https://bugs.webkit.org/show_bug.cgi?id=157884
     5        <rdar://problem/26477197>
     6
     7        Reviewed by Andy Estes.
     8
     9        Add support to webkitpy to start and stop a secure Web Socket server running on port 9323
     10        using the certificate, private-key from file LayoutTests/http/conf/webkit-httpd.pem. Also
     11        teaches run-webkit-httpd to start and stop the Web Socket servers.
     12
     13        Modify DumpRenderTree and WebKitTestRunner to understand a new testRunner method,
     14        'setAllowsAnySSLCertificate', which allows us to use the same self-signed test certificate
     15        we do for our HTTPS tests.
     16       
     17        * DumpRenderTree/TestRunner.cpp:
     18        (setAllowsAnySSLCertificateCallback):
     19        (TestRunner::setAllowsAnySSLCertificate):
     20        * DumpRenderTree/TestRunner.h:
     21        * DumpRenderTree/mac/DumpRenderTree.mm:
     22        (resetWebViewToConsistentStateBeforeTesting): Make sure we turn off the new flag between tests.
     23        * Scripts/run-webkit-httpd:
     24        (main): Start the websocket server at launch.
     25        * Scripts/webkitpy/layout_tests/controllers/manager.py:
     26        (Manager.__init__): Remove dead code.
     27        * Scripts/webkitpy/layout_tests/servers/websocket_server.py:
     28        (PyWebSocket.__init__): Cleanup code.
     29        (PyWebSocket): Pass '--tls-client-ca' to start command.
     30        (PyWebSocket._prepare_config): Cleanups.
     31        * Scripts/webkitpy/port/base.py:
     32        (Port.to.start_http_server):
     33        (Port.to):
     34        (Port.to._extract_certificate_from_pem): Added.
     35        (Port.to._extract_private_key_from_pem): Added.
     36        (Port.to.start_websocket_server): Start secure socket server.
     37        (Port.to.stop_websocket_server): Stop secure socket server.
     38        * WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl: Add new API.
     39        * WebKitTestRunner/InjectedBundle/InjectedBundle.cpp:
     40        (WTR::InjectedBundle::setAllowsAnySSLCertificate): Added.
     41        * WebKitTestRunner/InjectedBundle/InjectedBundle.h:
     42        * WebKitTestRunner/InjectedBundle/TestRunner.cpp:
     43        (WTR::TestRunner::setAllowsAnySSLCertificate): Added.
     44        * WebKitTestRunner/InjectedBundle/TestRunner.h:
     45
    1462016-05-25  Keith Miller  <keith_miller@apple.com>
    247
  • trunk/Tools/DumpRenderTree/TestRunner.cpp

    r200945 r201404  
    11841184}
    11851185
     1186static JSValueRef setAllowsAnySSLCertificateCallback(JSContextRef context, JSObjectRef, JSObjectRef, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
     1187{
     1188    bool allowAnyCertificate = false;
     1189    if (argumentCount == 1)
     1190        allowAnyCertificate = JSValueToBoolean(context, arguments[0]);
     1191   
     1192    TestRunner::setAllowsAnySSLCertificate(allowAnyCertificate);
     1193    return JSValueMakeUndefined(context);
     1194}
    11861195
    11871196static JSValueRef setAllowUniversalAccessFromFileURLsCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
     
    21022111        { "setAllowUniversalAccessFromFileURLs", setAllowUniversalAccessFromFileURLsCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
    21032112        { "setAllowFileAccessFromFileURLs", setAllowFileAccessFromFileURLsCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
     2113        { "setAllowsAnySSLCertificate", setAllowsAnySSLCertificateCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
    21042114        { "setAlwaysAcceptCookies", setAlwaysAcceptCookiesCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
    21052115        { "setAppCacheMaximumSize", setAppCacheMaximumSizeCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
     
    22812291    WebCoreTestSupport::setLogChannelToAccumulate({ buffer.get() });
    22822292}
     2293
     2294void TestRunner::setAllowsAnySSLCertificate(bool allowsAnySSLCertificate)
     2295{
     2296    WebCoreTestSupport::setAllowsAnySSLCertificate(allowsAnySSLCertificate);
     2297}
  • trunk/Tools/DumpRenderTree/TestRunner.h

    r200945 r201404  
    128128    void resetPageVisibility();
    129129
     130    static void setAllowsAnySSLCertificate(bool);
     131
    130132    void waitForPolicyDelegate();
    131133    size_t webHistoryItemCount();
  • trunk/Tools/DumpRenderTree/mac/DumpRenderTree.mm

    r201090 r201404  
    18471847
    18481848    TestRunner::setSerializeHTTPLoads(false);
     1849    TestRunner::setAllowsAnySSLCertificate(false);
    18491850
    18501851    setlocale(LC_ALL, "");
  • trunk/Tools/Scripts/run-webkit-httpd

    r196778 r201404  
    6767
    6868    port.start_http_server()
     69    port.start_websocket_server()
    6970
    7071    try:
     
    7374            sys.stdout.write(tail.stdout.readline())
    7475    except KeyboardInterrupt:
     76        port.stop_websocket_server()
    7577        port.stop_http_server()
    7678
  • trunk/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py

    r195557 r201404  
    7979        self._printer = printer
    8080        self._expectations = None
    81 
    8281        self.HTTP_SUBDIR = 'http' + port.TEST_PATH_SEPARATOR
    8382        self.WEBSOCKET_SUBDIR = 'websocket' + port.TEST_PATH_SEPARATOR
    8483        self.web_platform_test_subdir = self._port.web_platform_test_server_doc_root()
    8584        self.LAYOUT_TESTS_DIRECTORY = 'LayoutTests'
    86 
    87         # disable wss server. need to install pyOpenSSL on buildbots.
    88         # self._websocket_secure_server = websocket_server.PyWebSocket(
    89         #        options.results_directory, use_tls=True, port=9323)
    90 
    9185        self._results_directory = self._port.results_directory()
    9286        self._finder = LayoutTestFinder(self._port, self._options)
  • trunk/Tools/Scripts/webkitpy/layout_tests/servers/websocket_server.py

    r136545 r201404  
    11# Copyright (C) 2011 Google Inc. All rights reserved.
     2# Copyright (C) 2016 Apple Inc. All rights reserved.
    23#
    34# Redistribution and use in source and binary forms, with or without
     
    4041
    4142
    42 _WS_LOG_PREFIX = 'pywebsocket.ws.log-'
    43 _WSS_LOG_PREFIX = 'pywebsocket.wss.log-'
     43_WS_LOG_NAME = 'pywebsocket.ws.log'
     44_WSS_LOG_NAME = 'pywebsocket.wss.log'
    4445
    4546
     
    103104
    104105        if self._use_tls:
    105             self._log_prefix = _WSS_LOG_PREFIX
     106            self._log_prefix = _WSS_LOG_NAME
    106107        else:
    107             self._log_prefix = _WS_LOG_PREFIX
     108            self._log_prefix = _WS_LOG_NAME
    108109
    109110    def _prepare_config(self):
    110         time_str = time.strftime('%d%b%Y-%H%M%S')
    111         log_file_name = self._log_prefix + time_str
     111        log_file_name = self._log_prefix
    112112        # FIXME: Doesn't Executive have a devnull, so that we don't have to use os.devnull directly?
    113113        self._wsin = open(os.devnull, 'r')
     
    146146                              '-c', self._certificate])
    147147            if self._ca_certificate:
    148                 start_cmd.append('--ca-certificate')
     148                start_cmd.append('--tls-client-ca')
    149149                start_cmd.append(self._ca_certificate)
    150150
  • trunk/Tools/Scripts/webkitpy/port/base.py

    r199036 r201404  
    919919        self._http_server = server
    920920
     921    def _extract_certificate_from_pem(self, pem_file, destination_certificate_file):
     922        return self._executive.run_command(['openssl', 'x509', '-outform', 'pem', '-in', pem_file, '-out', destination_certificate_file], return_exit_code=True) == 0
     923
     924    def _extract_private_key_from_pem(self, pem_file, destination_private_key_file):
     925        return self._executive.run_command(['openssl', 'rsa', '-in', pem_file, '-out', destination_private_key_file], return_exit_code=True) == 0
     926
    921927    def start_websocket_server(self):
    922928        """Start a web server. Raise an error if it can't start or is already running.
     
    928934        server.start()
    929935        self._websocket_server = server
     936
     937        pem_file = self._filesystem.join(self.layout_tests_dir(), "http", "conf", "webkit-httpd.pem")
     938        websocket_server_temporary_directory = self._filesystem.mkdtemp(prefix='webkitpy-websocket-server')
     939        certificate_file = self._filesystem.join(str(websocket_server_temporary_directory), 'webkit-httpd.crt')
     940        private_key_file = self._filesystem.join(str(websocket_server_temporary_directory), 'webkit-httpd.key')
     941        self._websocket_server_temporary_directory = websocket_server_temporary_directory
     942        if self._extract_certificate_from_pem(pem_file, certificate_file) and self._extract_private_key_from_pem(pem_file, private_key_file):
     943            secure_server = self._websocket_secure_server = websocket_server.PyWebSocket(self, self.results_directory(),
     944                                use_tls=True, port=9323, private_key=private_key_file, certificate=certificate_file)
     945            secure_server.start()
     946            self._websocket_secure_server = secure_server
    930947
    931948    def start_web_platform_test_server(self, additional_dirs=None, number_of_servers=None):
     
    965982            self._websocket_server.stop()
    966983            self._websocket_server = None
     984        if self._websocket_secure_server:
     985            self._websocket_secure_server.stop()
     986            self._websocket_secure_server = None
     987        if self._websocket_server_temporary_directory:
     988            self._filesystem.rmtree(str(self._websocket_server_temporary_directory))
    967989
    968990    def stop_web_platform_test_server(self):
  • trunk/Tools/WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl

    r200945 r201404  
    203203    void setAuthenticationPassword(DOMString password);
    204204
     205    void setAllowsAnySSLCertificate(boolean value);
     206
    205207    // Secure text input mode (Mac only)
    206208    readonly attribute boolean secureEventInputIsEnabled;
  • trunk/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.cpp

    r201333 r201404  
    3030#include "InjectedBundlePage.h"
    3131#include "StringFunctions.h"
     32#include "WebCoreTestSupport.h"
    3233#include <WebKit/WKBundle.h>
    3334#include <WebKit/WKBundlePage.h>
     
    737738}
    738739
     740void InjectedBundle::setAllowsAnySSLCertificate(bool allowsAnySSLCertificate)
     741{
     742    WebCoreTestSupport::setAllowsAnySSLCertificate(allowsAnySSLCertificate);
     743}
     744
    739745} // namespace WTR
  • trunk/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.h

    r200945 r201404  
    124124    unsigned imageCountInGeneralPasteboard() const;
    125125
     126    void setAllowsAnySSLCertificate(bool);
     127   
    126128private:
    127129    InjectedBundle();
  • trunk/Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp

    r201081 r201404  
    370370}
    371371
     372void TestRunner::setAllowsAnySSLCertificate(bool enabled)
     373{
     374    InjectedBundle::singleton().setAllowsAnySSLCertificate(enabled);
     375}
     376
    372377void TestRunner::setAllowUniversalAccessFromFileURLs(bool enabled)
    373378{
  • trunk/Tools/WebKitTestRunner/InjectedBundle/TestRunner.h

    r200945 r201404  
    124124    void setAsynchronousSpellCheckingEnabled(bool);
    125125    void setDownloadAttributeEnabled(bool);
     126    void setAllowsAnySSLCertificate(bool);
    126127
    127128    // Special DOM functions.
Note: See TracChangeset for help on using the changeset viewer.