since-v37.diff.txt

text/plain

Filename: since-v37.diff.txt
Type: text/plain
Part: 0
Message: Re: [PoC] Federated Authn/z with OAUTHBEARER
1:  16f1b8fc02 ! 1:  785add8015 Add OAUTHBEARER SASL mechanism
    @@ .cirrus.tasks.yml: task:
              --buildtype=debug \
              -Dcassert=true -Dinjection_points=true \
              -Duuid=bsd -Dtcl_version=tcl86 -Ddtrace=auto \
    -+        -Dbuiltin_oauth=curl \
    ++        -Dlibcurl=enabled \
              -Dextra_lib_dirs=/usr/local/lib -Dextra_include_dirs=/usr/local/include/ \
              build
          EOF
     @@ .cirrus.tasks.yml: LINUX_CONFIGURE_FEATURES: &LINUX_CONFIGURE_FEATURES >-
    +   --with-gssapi
    +   --with-icu
    +   --with-ldap
    ++  --with-libcurl
    +   --with-libxml
        --with-libxslt
        --with-llvm
    -   --with-lz4
    -+  --with-builtin-oauth=curl
    -   --with-pam
    -   --with-perl
    -   --with-python
     @@ .cirrus.tasks.yml: LINUX_CONFIGURE_FEATURES: &LINUX_CONFIGURE_FEATURES >-
    +   --with-zstd
      
      LINUX_MESON_FEATURES: &LINUX_MESON_FEATURES >-
    ++  -Dlibcurl=enabled
        -Dllvm=enabled
    -+  -Dbuiltin_oauth=curl
        -Duuid=e2fs
      
    - 
     @@ .cirrus.tasks.yml: task:
          EOF
      
    @@ config/programs.m4: if test "$pgac_cv_ldap_safe" != yes; then
      *** also uses LDAP will crash on exit.])
      fi])
      
    -+# PGAC_CHECK_LIBCURL
    -+# ------------------
    -+# Check for libcurl 7.61.0 or higher (corresponding to RHEL8 and the ability to
    -+# explicitly set TLS 1.3 ciphersuites).
    - 
    -+AC_DEFUN([PGAC_CHECK_LIBCURL],
    -+[AC_CACHE_CHECK([for compatible libcurl], [pgac_cv_check_libcurl],
    -+[AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
    -+[#include <curl/curlver.h>
    -+#if LIBCURL_VERSION_MAJOR < 7 || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR < 61)
    -+choke me
    -+#endif], [])],
    -+[pgac_cv_check_libcurl=yes],
    -+[pgac_cv_check_libcurl=no])])
    -+
    -+if test "$pgac_cv_check_libcurl" != yes; then
    -+    AC_MSG_ERROR([
    -+*** The installed version of libcurl is too old to use with PostgreSQL.
    -+*** libcurl version 7.61.0 or later is required.])
    -+fi])
    - 
    +-
    +-
      # PGAC_CHECK_READLINE
      # -------------------
    + # Check for the readline library and dependent libraries, either
     
      ## configure ##
    -@@ configure: with_uuid
    +@@ configure: XML2_LIBS
    + XML2_CFLAGS
    + XML2_CONFIG
    + with_libxml
    ++LIBCURL_LIBS
    ++LIBCURL_CFLAGS
    ++with_libcurl
    + with_uuid
      with_readline
      with_systemd
    - with_selinux
    -+with_builtin_oauth
    - with_ldap
    - with_krb_srvnam
    - krb_srvtab
    -@@ configure: with_krb_srvnam
    - with_pam
    - with_bsd_auth
    - with_ldap
    -+with_builtin_oauth
    - with_bonjour
    - with_selinux
    - with_systemd
    +@@ configure: with_readline
    + with_libedit_preferred
    + with_uuid
    + with_ossp_uuid
    ++with_libcurl
    + with_libxml
    + with_libxslt
    + with_system_tzdata
    +@@ configure: PKG_CONFIG_PATH
    + PKG_CONFIG_LIBDIR
    + ICU_CFLAGS
    + ICU_LIBS
    ++LIBCURL_CFLAGS
    ++LIBCURL_LIBS
    + XML2_CONFIG
    + XML2_CFLAGS
    + XML2_LIBS
     @@ configure: Optional Packages:
    -   --with-pam              build with PAM support
    -   --with-bsd-auth         build with BSD Authentication support
    -   --with-ldap             build with LDAP support
    -+  --with-builtin-oauth=LIB
    -+                          use LIB for built-in OAuth 2.0 client flows (curl)
    -   --with-bonjour          build with Bonjour support
    -   --with-selinux          build with SELinux support
    -   --with-systemd          build with systemd support
    -@@ configure: $as_echo "$with_ldap" >&6; }
    +                           prefer BSD Libedit over GNU Readline
    +   --with-uuid=LIB         build contrib/uuid-ossp using LIB (bsd,e2fs,ossp)
    +   --with-ossp-uuid        obsolete spelling of --with-uuid=ossp
    ++  --with-libcurl          build with libcurl support for OAuth client flows
    +   --with-libxml           build with XML support
    +   --with-libxslt          use XSLT support when building contrib/xml2
    +   --with-system-tzdata=DIR
    +@@ configure: Some influential environment variables:
    +               path overriding pkg-config's built-in search path
    +   ICU_CFLAGS  C compiler flags for ICU, overriding pkg-config
    +   ICU_LIBS    linker flags for ICU, overriding pkg-config
    ++  LIBCURL_CFLAGS
    ++              C compiler flags for LIBCURL, overriding pkg-config
    ++  LIBCURL_LIBS
    ++              linker flags for LIBCURL, overriding pkg-config
    +   XML2_CONFIG path to xml2-config utility
    +   XML2_CFLAGS C compiler flags for XML2, overriding pkg-config
    +   XML2_LIBS   linker flags for XML2, overriding pkg-config
    +@@ configure: fi
      
      
      
     +#
    -+# OAuth 2.0
    ++# libcurl
     +#
    -+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with built-in OAuth client support" >&5
    -+$as_echo_n "checking whether to build with built-in OAuth client support... " >&6; }
    ++{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with libcurl support for OAuth client flows" >&5
    ++$as_echo_n "checking whether to build with libcurl support for OAuth client flows... " >&6; }
     +
     +
     +
    -+# Check whether --with-builtin-oauth was given.
    -+if test "${with_builtin_oauth+set}" = set; then :
    -+  withval=$with_builtin_oauth;
    ++# Check whether --with-libcurl was given.
    ++if test "${with_libcurl+set}" = set; then :
    ++  withval=$with_libcurl;
     +  case $withval in
     +    yes)
    -+      as_fn_error $? "argument required for --with-builtin-oauth option" "$LINENO" 5
    ++
    ++$as_echo "#define USE_LIBCURL 1" >>confdefs.h
    ++
     +      ;;
     +    no)
    -+      as_fn_error $? "argument required for --with-builtin-oauth option" "$LINENO" 5
    ++      :
     +      ;;
     +    *)
    -+
    ++      as_fn_error $? "no argument expected for --with-libcurl option" "$LINENO" 5
     +      ;;
     +  esac
     +
    ++else
    ++  with_libcurl=no
    ++
     +fi
     +
     +
    -+if test x"$with_builtin_oauth" = x"" ; then
    -+  with_builtin_oauth=no
    ++{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_libcurl" >&5
    ++$as_echo "$with_libcurl" >&6; }
    ++
    ++
    ++if test "$with_libcurl" = yes ; then
    ++  # Check for libcurl 7.61.0 or higher (corresponding to RHEL8 and the ability
    ++  # to explicitly set TLS 1.3 ciphersuites).
    ++
    ++pkg_failed=no
    ++{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for libcurl >= 7.61.0" >&5
    ++$as_echo_n "checking for libcurl >= 7.61.0... " >&6; }
    ++
    ++if test -n "$LIBCURL_CFLAGS"; then
    ++    pkg_cv_LIBCURL_CFLAGS="$LIBCURL_CFLAGS"
    ++ elif test -n "$PKG_CONFIG"; then
    ++    if test -n "$PKG_CONFIG" && \
    ++    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libcurl >= 7.61.0\""; } >&5
    ++  ($PKG_CONFIG --exists --print-errors "libcurl >= 7.61.0") 2>&5
    ++  ac_status=$?
    ++  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
    ++  test $ac_status = 0; }; then
    ++  pkg_cv_LIBCURL_CFLAGS=`$PKG_CONFIG --cflags "libcurl >= 7.61.0" 2>/dev/null`
    ++		      test "x$?" != "x0" && pkg_failed=yes
    ++else
    ++  pkg_failed=yes
    ++fi
    ++ else
    ++    pkg_failed=untried
    ++fi
    ++if test -n "$LIBCURL_LIBS"; then
    ++    pkg_cv_LIBCURL_LIBS="$LIBCURL_LIBS"
    ++ elif test -n "$PKG_CONFIG"; then
    ++    if test -n "$PKG_CONFIG" && \
    ++    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libcurl >= 7.61.0\""; } >&5
    ++  ($PKG_CONFIG --exists --print-errors "libcurl >= 7.61.0") 2>&5
    ++  ac_status=$?
    ++  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
    ++  test $ac_status = 0; }; then
    ++  pkg_cv_LIBCURL_LIBS=`$PKG_CONFIG --libs "libcurl >= 7.61.0" 2>/dev/null`
    ++		      test "x$?" != "x0" && pkg_failed=yes
    ++else
    ++  pkg_failed=yes
     +fi
    ++ else
    ++    pkg_failed=untried
    ++fi
    ++
     +
    -+if test x"$with_builtin_oauth" = x"curl"; then
     +
    -+$as_echo "#define USE_BUILTIN_OAUTH 1" >>confdefs.h
    ++if test $pkg_failed = yes; then
    ++        { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
    ++$as_echo "no" >&6; }
     +
    ++if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
    ++        _pkg_short_errors_supported=yes
    ++else
    ++        _pkg_short_errors_supported=no
    ++fi
    ++        if test $_pkg_short_errors_supported = yes; then
    ++	        LIBCURL_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libcurl >= 7.61.0" 2>&1`
    ++        else
    ++	        LIBCURL_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libcurl >= 7.61.0" 2>&1`
    ++        fi
    ++	# Put the nasty error message in config.log where it belongs
    ++	echo "$LIBCURL_PKG_ERRORS" >&5
    ++
    ++	as_fn_error $? "Package requirements (libcurl >= 7.61.0) were not met:
    ++
    ++$LIBCURL_PKG_ERRORS
    ++
    ++Consider adjusting the PKG_CONFIG_PATH environment variable if you
    ++installed software in a non-standard prefix.
    ++
    ++Alternatively, you may set the environment variables LIBCURL_CFLAGS
    ++and LIBCURL_LIBS to avoid the need to call pkg-config.
    ++See the pkg-config man page for more details." "$LINENO" 5
    ++elif test $pkg_failed = untried; then
    ++        { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
    ++$as_echo "no" >&6; }
    ++	{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
    ++$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
    ++as_fn_error $? "The pkg-config script could not be found or is too old.  Make sure it
    ++is in your PATH or set the PKG_CONFIG environment variable to the full
    ++path to pkg-config.
    ++
    ++Alternatively, you may set the environment variables LIBCURL_CFLAGS
    ++and LIBCURL_LIBS to avoid the need to call pkg-config.
    ++See the pkg-config man page for more details.
    ++
    ++To get pkg-config, see <http://pkg-config.freedesktop.org/>.
    ++See \`config.log' for more details" "$LINENO" 5; }
    ++else
    ++	LIBCURL_CFLAGS=$pkg_cv_LIBCURL_CFLAGS
    ++	LIBCURL_LIBS=$pkg_cv_LIBCURL_LIBS
    ++        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
    ++$as_echo "yes" >&6; }
     +
    -+$as_echo "#define USE_OAUTH_CURL 1" >>confdefs.h
    ++fi
     +
     +  # OAuth requires python for testing
     +  if test "$with_python" != yes; then
    -+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: *** OAuth support tests requires --with-python to run" >&5
    -+$as_echo "$as_me: WARNING: *** OAuth support tests requires --with-python to run" >&2;}
    ++    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: *** OAuth support tests require --with-python to run" >&5
    ++$as_echo "$as_me: WARNING: *** OAuth support tests require --with-python to run" >&2;}
     +  fi
    -+elif test x"$with_builtin_oauth" != x"no"; then
    -+  as_fn_error $? "--with-builtin-oauth must specify curl" "$LINENO" 5
     +fi
     +
    -+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_builtin_oauth" >&5
    -+$as_echo "$with_builtin_oauth" >&6; }
    -+
    -+
     +
      #
    - # Bonjour
    + # XML
      #
     @@ configure: fi
      
    @@ configure: fi
     +# XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
     +# during gss_acquire_cred(). This is possibly related to Curl's Heimdal
     +# dependency on that platform?
    -+if test "$with_builtin_oauth" = curl ; then
    ++if test "$with_libcurl" = yes ; then
     +  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for curl_multi_init in -lcurl" >&5
     +$as_echo_n "checking for curl_multi_init in -lcurl... " >&6; }
     +if ${ac_cv_lib_curl_curl_multi_init+:} false; then :
    @@ configure: fi
     +  LIBS="-lcurl $LIBS"
     +
     +else
    -+  as_fn_error $? "library 'curl' is required for --with-builtin-oauth=curl" "$LINENO" 5
    -+fi
    -+
    -+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for compatible libcurl" >&5
    -+$as_echo_n "checking for compatible libcurl... " >&6; }
    -+if ${pgac_cv_check_libcurl+:} false; then :
    -+  $as_echo_n "(cached) " >&6
    -+else
    -+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
    -+/* end confdefs.h.  */
    -+#include <curl/curlver.h>
    -+#if LIBCURL_VERSION_MAJOR < 7 || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR < 61)
    -+choke me
    -+#endif
    -+int
    -+main ()
    -+{
    -+
    -+  ;
    -+  return 0;
    -+}
    -+_ACEOF
    -+if ac_fn_c_try_compile "$LINENO"; then :
    -+  pgac_cv_check_libcurl=yes
    -+else
    -+  pgac_cv_check_libcurl=no
    -+fi
    -+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
    ++  as_fn_error $? "library 'curl' is required for --with-libcurl" "$LINENO" 5
     +fi
    -+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_check_libcurl" >&5
    -+$as_echo "$pgac_cv_check_libcurl" >&6; }
     +
    -+if test "$pgac_cv_check_libcurl" != yes; then
    -+    as_fn_error $? "
    -+*** The installed version of libcurl is too old to use with PostgreSQL.
    -+*** libcurl version 7.61.0 or later is required." "$LINENO" 5
    -+fi
     +fi
     +
      if test "$with_gssapi" = yes ; then
    @@ configure: fi
      
     +fi
     +
    -+if test "$with_builtin_oauth" = curl; then
    ++if test "$with_libcurl" = yes; then
     +  ac_fn_c_check_header_mongrel "$LINENO" "curl/curl.h" "ac_cv_header_curl_curl_h" "$ac_includes_default"
     +if test "x$ac_cv_header_curl_curl_h" = xyes; then :
     +
     +else
    -+  as_fn_error $? "header file <curl/curl.h> is required for --with-builtin-oauth=curl" "$LINENO" 5
    ++  as_fn_error $? "header file <curl/curl.h> is required for --with-libcurl" "$LINENO" 5
     +fi
     +
     +
    @@ configure: fi
      if test "$PORTNAME" = "win32" ; then
     
      ## configure.ac ##
    -@@ configure.ac: AC_MSG_RESULT([$with_ldap])
    - AC_SUBST(with_ldap)
    +@@ configure.ac: fi
    + AC_SUBST(with_uuid)
      
      
     +#
    -+# OAuth 2.0
    ++# libcurl
     +#
    -+AC_MSG_CHECKING([whether to build with built-in OAuth client support])
    -+PGAC_ARG_REQ(with, builtin-oauth, [LIB], [use LIB for built-in OAuth 2.0 client flows (curl)])
    -+if test x"$with_builtin_oauth" = x"" ; then
    -+  with_builtin_oauth=no
    -+fi
    ++AC_MSG_CHECKING([whether to build with libcurl support for OAuth client flows])
    ++PGAC_ARG_BOOL(with, libcurl, no, [build with libcurl support for OAuth client flows],
    ++              [AC_DEFINE([USE_LIBCURL], 1, [Define to 1 to build with libcurl support for OAuth client flows. (--with-libcurl)])])
    ++AC_MSG_RESULT([$with_libcurl])
    ++AC_SUBST(with_libcurl)
    ++
    ++if test "$with_libcurl" = yes ; then
    ++  # Check for libcurl 7.61.0 or higher (corresponding to RHEL8 and the ability
    ++  # to explicitly set TLS 1.3 ciphersuites).
    ++  PKG_CHECK_MODULES(LIBCURL, [libcurl >= 7.61.0])
     +
    -+if test x"$with_builtin_oauth" = x"curl"; then
    -+  AC_DEFINE([USE_BUILTIN_OAUTH], 1, [Define to 1 to build with OAuth 2.0 client flows. (--with-builtin-oauth)])
    -+  AC_DEFINE([USE_OAUTH_CURL], 1, [Define to 1 to use libcurl for OAuth client flows.])
     +  # OAuth requires python for testing
     +  if test "$with_python" != yes; then
    -+    AC_MSG_WARN([*** OAuth support tests requires --with-python to run])
    ++    AC_MSG_WARN([*** OAuth support tests require --with-python to run])
     +  fi
    -+elif test x"$with_builtin_oauth" != x"no"; then
    -+  AC_MSG_ERROR([--with-builtin-oauth must specify curl])
     +fi
     +
    -+AC_MSG_RESULT([$with_builtin_oauth])
    -+AC_SUBST(with_builtin_oauth)
    -+
     +
      #
    - # Bonjour
    + # XML
      #
     @@ configure.ac: failure.  It is possible the compiler isn't looking in the proper directory.
      Use --without-zlib to disable zlib support.])])
    @@ configure.ac: failure.  It is possible the compiler isn't looking in the proper
     +# XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
     +# during gss_acquire_cred(). This is possibly related to Curl's Heimdal
     +# dependency on that platform?
    -+if test "$with_builtin_oauth" = curl ; then
    -+  AC_CHECK_LIB(curl, curl_multi_init, [], [AC_MSG_ERROR([library 'curl' is required for --with-builtin-oauth=curl])])
    -+  PGAC_CHECK_LIBCURL
    ++if test "$with_libcurl" = yes ; then
    ++  AC_CHECK_LIB(curl, curl_multi_init, [], [AC_MSG_ERROR([library 'curl' is required for --with-libcurl])])
     +fi
     +
      if test "$with_gssapi" = yes ; then
    @@ configure.ac: elif test "$with_uuid" = ossp ; then
            [AC_MSG_ERROR([header file <ossp/uuid.h> or <uuid.h> is required for OSSP UUID])])])
      fi
      
    -+if test "$with_builtin_oauth" = curl; then
    -+  AC_CHECK_HEADER(curl/curl.h, [], [AC_MSG_ERROR([header file <curl/curl.h> is required for --with-builtin-oauth=curl])])
    ++if test "$with_libcurl" = yes; then
    ++  AC_CHECK_HEADER(curl/curl.h, [], [AC_MSG_ERROR([header file <curl/curl.h> is required for --with-libcurl])])
     +fi
     +
      if test "$PORTNAME" = "win32" ; then
    @@ doc/src/sgml/installation.sgml: build-postgresql:
             </listitem>
            </varlistentry>
      
    -+      <varlistentry id="configure-option-with-builtin-oauth">
    -+       <term><option>--with-builtin-oauth=<replaceable>LIBRARY</replaceable></option></term>
    ++      <varlistentry id="configure-option-with-libcurl">
    ++       <term><option>--with-libcurl</option></term>
     +       <listitem>
     +        <para>
    -+         Build with support for OAuth 2.0 client flows.  The only
    -+         <replaceable>LIBRARY</replaceable> supported is <option>curl</option>.
    ++         Build with libcurl support for OAuth 2.0 client flows.
     +         This requires the <productname>curl</productname> package to be
     +         installed.  Building with this will check for the required header files
     +         and libraries to make sure that your <productname>curl</productname>
    @@ doc/src/sgml/installation.sgml: build-postgresql:
     +       </listitem>
     +      </varlistentry>
     +
    -       <varlistentry id="configure-option-with-systemd">
    -        <term><option>--with-systemd</option></term>
    +       <varlistentry id="configure-option-with-libxml">
    +        <term><option>--with-libxml</option></term>
             <listitem>
     @@ doc/src/sgml/installation.sgml: ninja install
            </listitem>
           </varlistentry>
      
    -+     <varlistentry id="configure-with-builtin-oauth">
    -+      <term><option>-Dbuiltin_oauth={ auto | <replaceable>LIBRARY</replaceable> | disabled }</option></term>
    ++     <varlistentry id="configure-with-libcurl-meson">
    ++      <term><option>-Dlibcurl={ auto | enabled | disabled }</option></term>
     +      <listitem>
     +       <para>
    -+        Build with support for OAuth 2.0 client flows.  The only
    -+        <replaceable>LIBRARY</replaceable> supported is <option>curl</option>.
    ++        Build with libcurl support for OAuth 2.0 client flows.
     +        This requires the <productname>curl</productname> package to be
     +        installed.  Building with this will check for the required header files
     +        and libraries to make sure that your <productname>curl</productname>
    @@ doc/src/sgml/installation.sgml: ninja install
     +      </listitem>
     +     </varlistentry>
     +
    -      <varlistentry id="configure-with-systemd-meson">
    -       <term><option>-Dsystemd={ auto | enabled | disabled }</option></term>
    +      <varlistentry id="configure-with-libxml-meson">
    +       <term><option>-Dlibxml={ auto | enabled | disabled }</option></term>
            <listitem>
     
      ## doc/src/sgml/libpq.sgml ##
    @@ meson.build: endif
      
      
     +###############################################################
    -+# Library: OAuth (libcurl)
    ++# Library: libcurl
     +###############################################################
     +
    -+libcurl = not_found_dep
    -+oauth_library = 'none'
    -+oauthopt = get_option('builtin_oauth')
    -+
    -+if oauthopt == 'auto' and auto_features.disabled()
    -+  oauthopt = 'none'
    -+endif
    -+
    -+if oauthopt in ['auto', 'curl']
    ++libcurlopt = get_option('libcurl')
    ++if not libcurlopt.disabled()
     +  # Check for libcurl 7.61.0 or higher (corresponding to RHEL8 and the ability
     +  # to explicitly set TLS 1.3 ciphersuites).
    -+  libcurl = dependency('libcurl', version: '>= 7.61.0',
    -+                       required: (oauthopt == 'curl'))
    ++  libcurl = dependency('libcurl', version: '>= 7.61.0', required: libcurlopt)
     +  if libcurl.found()
    -+    oauth_library = 'curl'
    -+    cdata.set('USE_BUILTIN_OAUTH', 1)
    -+    cdata.set('USE_OAUTH_CURL', 1)
    ++    cdata.set('USE_LIBCURL', 1)
     +  endif
    ++else
    ++  libcurl = not_found_dep
     +endif
     +
    -+if oauthopt == 'auto' and auto_features.enabled() and not libcurl.found()
    -+  error('no OAuth implementation library found')
    -+endif
     +
     +
      ###############################################################
    - # Library: Tcl (for pltcl)
    - #
    + # Library: libxml
    + ###############################################################
     @@ meson.build: libpq_deps += [
      
        gssapi,
    @@ meson.build: if meson.version().version_compare('>=0.57')
            'llvm': llvm,
     
      ## meson_options.txt ##
    -@@ meson_options.txt: option('bonjour', type: 'feature', value: 'auto',
    - option('bsd_auth', type: 'feature', value: 'auto',
    -   description: 'BSD Authentication support')
    +@@ meson_options.txt: option('icu', type: 'feature', value: 'auto',
    + option('ldap', type: 'feature', value: 'auto',
    +   description: 'LDAP support')
      
    -+option('builtin_oauth', type : 'combo', choices : ['auto', 'none', 'curl'],
    -+  value: 'auto',
    -+  description: 'use LIB for built-in OAuth 2.0 client flows (curl)')
    ++option('libcurl', type : 'feature', value: 'auto',
    ++  description: 'libcurl support for OAuth client flows')
     +
    - option('docs', type: 'feature', value: 'auto',
    -   description: 'Documentation in HTML and man page format')
    + option('libedit_preferred', type: 'boolean', value: false,
    +   description: 'Prefer BSD Libedit over GNU Readline')
      
     
      ## src/Makefile.global.in ##
    -@@ src/Makefile.global.in: with_ldap	= @with_ldap@
    +@@ src/Makefile.global.in: with_systemd	= @with_systemd@
    + with_gssapi	= @with_gssapi@
    + with_krb_srvnam	= @with_krb_srvnam@
    + with_ldap	= @with_ldap@
    ++with_libcurl	= @with_libcurl@
      with_libxml	= @with_libxml@
      with_libxslt	= @with_libxslt@
      with_llvm	= @with_llvm@
    -+with_builtin_oauth = @with_builtin_oauth@
    - with_system_tzdata = @with_system_tzdata@
    - with_uuid	= @with_uuid@
    - with_zlib	= @with_zlib@
     
      ## src/backend/libpq/Makefile ##
     @@ src/backend/libpq/Makefile: include $(top_builddir)/src/Makefile.global
    @@ src/backend/libpq/auth-oauth.c (new)
     +
     +/* Mechanism declaration */
     +const pg_be_sasl_mech pg_be_oauth_mech = {
    -+	oauth_get_mechanisms,
    -+	oauth_init,
    -+	oauth_exchange,
    ++	.get_mechanisms = oauth_get_mechanisms,
    ++	.init = oauth_init,
    ++	.exchange = oauth_exchange,
     +
    -+	PG_MAX_AUTH_TOKEN_LENGTH,
    ++	.max_message_length = PG_MAX_AUTH_TOKEN_LENGTH,
     +};
     +
     +
    @@ src/backend/libpq/auth-oauth.c (new)
     +		value = sep + 1;
     +		validate_kvpair(key, value);
     +
    -+		if (!strcmp(key, AUTH_KEY))
    ++		if (strcmp(key, AUTH_KEY) == 0)
     +		{
     +			if (auth)
     +				ereport(ERROR,
    @@ src/backend/libpq/auth-oauth.c (new)
     +	if (!(token = validate_token_format(auth)))
     +		return false;
     +
    ++	/*
    ++	 * Ensure that we have a validation library loaded, this should always be
    ++	 * the case and an error here is indicative of a bug.
    ++	 */
    ++	if (!ValidatorCallbacks || !ValidatorCallbacks->validate_cb)
    ++		ereport(FATAL,
    ++				errcode(ERRCODE_INTERNAL_ERROR),
    ++				errmsg("validation of OAuth token requested without a validator loaded"));
    ++
     +	/* Call the validation function from the validator module */
     +	ret = ValidatorCallbacks->validate_cb(validator_module_state,
     +										  token, port->user_name);
    @@ src/backend/libpq/auth-oauth.c (new)
     +					   "OAuth validator", libname, "_PG_oauth_validator_module_init"));
     +
     +	ValidatorCallbacks = (*validator_init) ();
    ++	Assert(ValidatorCallbacks);
     +
     +	/* Allocate memory for validator library private state data */
     +	validator_module_state = (ValidatorModuleState *) palloc0(sizeof(ValidatorModuleState));
    @@ src/backend/libpq/auth-oauth.c (new)
     +	char	   *file_name = hbaline->sourcefile;
     +	char	   *rawstring;
     +	List	   *elemlist = NIL;
    -+	ListCell   *l;
     +
     +	*err_msg = NULL;
     +
    @@ src/backend/libpq/auth-oauth.c (new)
     +		goto done;
     +	}
     +
    -+	foreach(l, elemlist)
    ++	foreach_ptr(char, allowed, elemlist)
     +	{
    -+		char	   *allowed = lfirst(l);
    -+
     +		if (strcmp(allowed, hbaline->oauth_validator) == 0)
     +			goto done;
     +	}
    @@ src/include/pg_config.h.in
      #undef HAVE_LIBLDAP
      
     @@
    - /* Define to 1 to build with BSD Authentication support. (--with-bsd-auth) */
    - #undef USE_BSD_AUTH
    + /* Define to 1 to build with LDAP support. (--with-ldap) */
    + #undef USE_LDAP
      
    -+/* Define to 1 to build with OAuth 2.0 client flows. (--with-builtin-oauth) */
    -+#undef USE_BUILTIN_OAUTH
    ++/* Define to 1 to build with libcurl support for OAuth client flows.
    ++   (--with-libcurl) */
    ++#undef USE_LIBCURL
     +
    - /* Define to build with ICU support. (--with-icu) */
    - #undef USE_ICU
    - 
    -@@
    - /* Define to select named POSIX semaphores. */
    - #undef USE_NAMED_POSIX_SEMAPHORES
    - 
    -+/* Define to 1 to use libcurl for OAuth client flows. */
    -+#undef USE_OAUTH_CURL
    -+
    - /* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
    - #undef USE_OPENSSL
    + /* Define to 1 to build with XML support. (--with-libxml) */
    + #undef USE_LIBXML
      
     
      ## src/interfaces/libpq/Makefile ##
    @@ src/interfaces/libpq/Makefile: OBJS += \
      	fe-secure-gssapi.o
      endif
      
    -+ifeq ($(with_builtin_oauth),curl)
    ++ifeq ($(with_libcurl),yes)
     +OBJS += fe-auth-oauth-curl.o
     +endif
     +
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	{
     +		char	  **scalar;		/* for all scalar types */
     +		struct curl_slist **array;	/* for type == JSON_TOKEN_ARRAY_START */
    -+	};
    ++	}			target;
     +
     +	bool		required;		/* REQUIRED field, or just OPTIONAL? */
     +};
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +		}
     +	}
     +
    -+	free(name);
     +	return JSON_SUCCESS;
     +}
     +
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	struct oauth_parse *ctx = state;
     +
     +	--ctx->nested;
    ++	if (!ctx->nested)
    ++		Assert(!ctx->active);	/* all fields should be fully processed */
    ++
     +	return JSON_SUCCESS;
     +}
     +
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +oauth_json_scalar(void *state, char *token, JsonTokenType type)
     +{
     +	struct oauth_parse *ctx = state;
    -+	JsonParseErrorType result = JSON_SUCCESS;
     +
     +	if (!ctx->nested)
     +	{
     +		oauth_parse_set_error(ctx, "top-level element must be an object");
    -+		result = JSON_SEM_ACTION_FAILED;
    -+		goto cleanup;
    ++		return JSON_SEM_ACTION_FAILED;
     +	}
     +
     +	if (ctx->active)
     +	{
    -+		JsonTokenType expected;
    ++		const struct json_field *field = ctx->active;
    ++		JsonTokenType expected = field->type;
     +
    -+		/*
    -+		 * Make sure this matches what the active field expects. Arrays must
    -+		 * contain only strings with the current implementation.
    -+		 */
    -+		if (ctx->active->type == JSON_TOKEN_ARRAY_START)
    ++		/* Make sure this matches what the active field expects. */
    ++		if (expected == JSON_TOKEN_ARRAY_START)
    ++		{
    ++			/* Are we actually inside an array? */
    ++			if (ctx->nested < 2)
    ++			{
    ++				report_type_mismatch(ctx);
    ++				return JSON_SEM_ACTION_FAILED;
    ++			}
    ++
    ++			/* Currently, arrays can only contain strings. */
     +			expected = JSON_TOKEN_STRING;
    -+		else
    -+			expected = ctx->active->type;
    ++		}
     +
     +		if (type != expected)
     +		{
     +			report_type_mismatch(ctx);
    -+			result = JSON_SEM_ACTION_FAILED;
    -+			goto cleanup;
    ++			return JSON_SEM_ACTION_FAILED;
     +		}
     +
     +		/*
    -+		 * FIXME if the JSON field is duplicated, we'll leak the prior value.
    -+		 * Error out in that case instead.
    ++		 * We don't allow duplicate field names; error out if the target has
    ++		 * already been set.
     +		 */
    -+		if (ctx->active->type != JSON_TOKEN_ARRAY_START)
    ++		if ((field->type == JSON_TOKEN_ARRAY_START && *field->target.array)
    ++			|| (field->type != JSON_TOKEN_ARRAY_START && *field->target.scalar))
    ++		{
    ++			oauth_parse_set_error(ctx, "field \"%s\" is duplicated",
    ++								  field->name);
    ++			return JSON_SEM_ACTION_FAILED;
    ++		}
    ++
    ++		if (field->type != JSON_TOKEN_ARRAY_START)
     +		{
     +			Assert(ctx->nested == 1);
     +
    -+			*ctx->active->scalar = token;
    ++			*field->target.scalar = strdup(token);
    ++			if (!*field->target.scalar)
    ++				return JSON_OUT_OF_MEMORY;
    ++
     +			ctx->active = NULL;
     +
    -+			return JSON_SUCCESS;	/* don't free the token */
    ++			return JSON_SUCCESS;
     +		}
    -+		else					/* ctx->target_array */
    ++		else
     +		{
     +			struct curl_slist *temp;
     +
     +			Assert(ctx->nested == 2);
     +
    -+			temp = curl_slist_append(*ctx->active->array, token);
    ++			/* Note that curl_slist_append() makes a copy of the token. */
    ++			temp = curl_slist_append(*field->target.array, token);
     +			if (!temp)
    -+			{
    -+				oauth_parse_set_error(ctx, "out of memory");
    -+				result = JSON_SEM_ACTION_FAILED;
    -+				goto cleanup;
    -+			}
    ++				return JSON_OUT_OF_MEMORY;
     +
    -+			*ctx->active->array = temp;
    -+
    -+			/*
    -+			 * Note that curl_slist_append() makes a copy of the token, so we
    -+			 * can free it below.
    -+			 */
    ++			*field->target.array = temp;
     +		}
     +	}
     +	else
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +		/* otherwise we just ignore it */
     +	}
     +
    -+cleanup:
    -+	free(token);
    -+	return result;
    ++	return JSON_SUCCESS;
     +}
     +
     +/*
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +		return false;
     +	}
     +
    ++	/*
    ++	 * pg_parse_json doesn't validate the incoming UTF-8, so we have to check
    ++	 * that up front.
    ++	 */
    ++	if (pg_encoding_verifymbstr(PG_UTF8, resp->data, resp->len) != resp->len)
    ++	{
    ++		actx_error(actx, "response is not valid UTF-8");
    ++		return false;
    ++	}
    ++
     +	makeJsonLexContextCstringLen(&lex, resp->data, resp->len, PG_UTF8, true);
    ++	setJsonLexContextOwnsTokens(&lex, true);	/* must not leak on error */
     +
     +	ctx.errbuf = &actx->errbuf;
     +	ctx.fields = fields;
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	/* Check all required fields. */
     +	while (fields->name)
     +	{
    -+		if (fields->required && !*fields->scalar && !*fields->array)
    ++		if (fields->required
    ++			&& !*fields->target.scalar
    ++			&& !*fields->target.array)
     +		{
     +			actx_error(actx, "field \"%s\" is missing", fields->name);
     +			goto cleanup;
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
     +	escaped = curl_easy_escape(NULL, s, 0);
     +	if (!escaped)
     +	{
    -+		markPQExpBufferBroken(buf);
    ++		termPQExpBuffer(buf);	/* mark the buffer broken */
     +		return;
     +	}
     +
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +		}
     +	}
     +
    -+	free(name);
     +	return JSON_SUCCESS;
     +}
     +
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +	if (!ctx->nested)
     +	{
     +		ctx->errmsg = libpq_gettext("top-level element must be an object");
    ++		return JSON_SEM_ACTION_FAILED;
     +	}
    -+	else if (ctx->target_field)
    ++
    ++	if (ctx->target_field)
     +	{
     +		Assert(ctx->nested == 1);
     +
    -+		if (type == JSON_TOKEN_STRING)
    ++		/*
    ++		 * We don't allow duplicate field names; error out if the target has
    ++		 * already been set.
    ++		 */
    ++		if (*ctx->target_field)
     +		{
    -+			*ctx->target_field = token;
    -+
    -+			ctx->target_field = NULL;
    -+			ctx->target_field_name = NULL;
    ++			oauth_json_set_error(ctx,
    ++								 libpq_gettext("field \"%s\" is duplicated"),
    ++								 ctx->target_field_name);
    ++			return JSON_SEM_ACTION_FAILED;
    ++		}
     +
    -+			return JSON_SUCCESS;	/* don't free the token we're using */
    ++		/* The only fields we support are strings. */
    ++		if (type != JSON_TOKEN_STRING)
    ++		{
    ++			oauth_json_set_error(ctx,
    ++								 libpq_gettext("field \"%s\" must be a string"),
    ++								 ctx->target_field_name);
    ++			return JSON_SEM_ACTION_FAILED;
     +		}
     +
    -+		oauth_json_set_error(ctx,
    -+							 libpq_gettext("field \"%s\" must be a string"),
    -+							 ctx->target_field_name);
    ++		*ctx->target_field = strdup(token);
    ++		if (!*ctx->target_field)
    ++			return JSON_OUT_OF_MEMORY;
    ++
    ++		ctx->target_field = NULL;
    ++		ctx->target_field_name = NULL;
    ++	}
    ++	else
    ++	{
    ++		/* otherwise we just ignore it */
     +	}
     +
    -+	free(token);
    -+	return oauth_json_has_error(ctx) ? JSON_SEM_ACTION_FAILED : JSON_SUCCESS;
    ++	return JSON_SUCCESS;
     +}
     +
     +#define HTTPS_SCHEME "https://"
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +}
     +
     +static bool
    -+handle_oauth_sasl_error(PGconn *conn, char *msg, int msglen)
    ++handle_oauth_sasl_error(PGconn *conn, const char *msg, int msglen)
     +{
     +	JsonLexContext lex = {0};
     +	JsonSemAction sem = {0};
     +	JsonParseErrorType err;
     +	struct json_ctx ctx = {0};
     +	char	   *errmsg = NULL;
    ++	bool		success = false;
    ++
    ++	Assert(conn->oauth_issuer_id);	/* ensured by setup_oauth_parameters() */
     +
     +	/* Sanity check. */
     +	if (strlen(msg) != msglen)
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +		return false;
     +	}
     +
    ++	/*
    ++	 * pg_parse_json doesn't validate the incoming UTF-8, so we have to check
    ++	 * that up front.
    ++	 */
    ++	if (pg_encoding_verifymbstr(PG_UTF8, msg, msglen) != msglen)
    ++	{
    ++		libpq_append_conn_error(conn,
    ++								"server's error response is not valid UTF-8");
    ++		return false;
    ++	}
    ++
     +	makeJsonLexContextCstringLen(&lex, msg, msglen, PG_UTF8, true);
    ++	setJsonLexContextOwnsTokens(&lex, true);	/* must not leak on error */
     +
     +	initPQExpBuffer(&ctx.errbuf);
     +	sem.semstate = &ctx;
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +	freeJsonLexContext(&lex);
     +
     +	if (errmsg)
    -+		return false;
    ++		goto cleanup;
     +
     +	/* TODO: what if these override what the user already specified? */
     +	/* TODO: what if there's no discovery URI? */
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +		/* The URI must correspond to our existing issuer, to avoid mix-ups. */
     +		discovery_issuer = issuer_from_well_known_uri(conn, ctx.discovery_uri);
     +		if (!discovery_issuer)
    -+			return false;		/* error message already set */
    ++			goto cleanup;		/* error message already set */
     +
     +		if (strcmp(conn->oauth_issuer_id, discovery_issuer) != 0)
     +		{
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +									conn->oauth_issuer_id);
     +
     +			free(discovery_issuer);
    -+			return false;
    ++			goto cleanup;
     +		}
     +
     +		free(discovery_issuer);
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +			free(conn->oauth_discovery_uri);
     +
     +		conn->oauth_discovery_uri = ctx.discovery_uri;
    ++		ctx.discovery_uri = NULL;
     +	}
     +
     +	if (ctx.scope)
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +			free(conn->oauth_scope);
     +
     +		conn->oauth_scope = ctx.scope;
    ++		ctx.scope = NULL;
     +	}
     +	/* TODO: missing error scope should clear any existing connection scope */
     +
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +	{
     +		libpq_append_conn_error(conn,
     +								"server sent error response without a status");
    -+		return false;
    ++		goto cleanup;
     +	}
     +
     +	if (strcmp(ctx.status, "invalid_token") == 0)
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +	}
     +	/* TODO: include status in hard failure message */
     +
    -+	return true;
    ++	success = true;
    ++
    ++cleanup:
    ++	free(ctx.status);
    ++	free(ctx.scope);
    ++	free(ctx.discovery_uri);
    ++
    ++	return success;
     +}
     +
     +static void
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +	}
     +	else
     +	{
    -+#if USE_BUILTIN_OAUTH
    ++#if USE_LIBCURL
     +		/*
     +		 * Hand off to our built-in OAuth flow.
     +		 *
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +		conn->oauth_want_retry = PG_BOOL_NO;
     +
     +#else
    -+		libpq_append_conn_error(conn, "no custom OAuth flows are available, and libpq was not built using --with-builtin-oauth");
    ++		libpq_append_conn_error(conn, "no custom OAuth flows are available, and libpq was not built using --with-libcurl");
     +		goto fail;
     +
     +#endif
    @@ src/interfaces/libpq/fe-auth-oauth.c (new)
     +			 * Respond with the required dummy message (RFC 7628, sec. 3.2.3).
     +			 */
     +			*output = strdup(kvsep);
    ++			if (unlikely(!*output))
    ++			{
    ++				libpq_append_conn_error(conn, "out of memory");
    ++				return SASL_FAILED;
    ++			}
     +			*outputlen = strlen(*output);	/* == 1 */
     +
     +			state->state = FE_OAUTH_SERVER_ERROR;
    @@ src/interfaces/libpq/meson.build: if gssapi.found()
        )
      endif
      
    -+if oauth_library == 'curl'
    ++if libcurl.found()
     +  libpq_sources += files('fe-auth-oauth-curl.c')
     +endif
     +
    @@ src/interfaces/libpq/meson.build: if gssapi.found()
        kwargs: gen_export_kwargs,
      )
     
    - ## src/interfaces/libpq/pqexpbuffer.c ##
    -@@ src/interfaces/libpq/pqexpbuffer.c: static const char *const oom_buffer_ptr = oom_buffer;
    -  *
    -  * Put a PQExpBuffer in "broken" state if it isn't already.
    -  */
    --static void
    -+void
    - markPQExpBufferBroken(PQExpBuffer str)
    - {
    - 	if (str->data != oom_buffer)
    -
    - ## src/interfaces/libpq/pqexpbuffer.h ##
    -@@ src/interfaces/libpq/pqexpbuffer.h: extern void initPQExpBuffer(PQExpBuffer str);
    - extern void destroyPQExpBuffer(PQExpBuffer str);
    - extern void termPQExpBuffer(PQExpBuffer str);
    - 
    -+/*------------------------
    -+ * markPQExpBufferBroken
    -+ *		Put a PQExpBuffer in "broken" state if it isn't already.
    -+ */
    -+extern void markPQExpBufferBroken(PQExpBuffer str);
    -+
    - /*------------------------
    -  * resetPQExpBuffer
    -  *		Reset a PQExpBuffer to empty
    -
      ## src/makefiles/meson.build ##
    -@@ src/makefiles/meson.build: pgxs_kv = {
    -   'SUN_STUDIO_CC': 'no', # not supported so far
    - 
    -   # want the chosen option, rather than the library
    -+  'with_builtin_oauth' : oauth_library,
    -   'with_ssl' : ssl_library,
    -   'with_uuid': uuidopt,
    - 
     @@ src/makefiles/meson.build: pgxs_deps = {
        'gssapi': gssapi,
        'icu': icu,
    @@ src/test/modules/oauth_validator/Makefile (new)
     +include $(top_srcdir)/contrib/contrib-global.mk
     +
     +export PYTHON
    -+export with_builtin_oauth
    ++export with_libcurl
     +export with_python
     +
     +endif
    @@ src/test/modules/oauth_validator/fail_validator.c (new)
     +
     +#include "fmgr.h"
     +#include "libpq/oauth.h"
    -+#include "miscadmin.h"
    -+#include "utils/guc.h"
    -+#include "utils/memutils.h"
     +
     +PG_MODULE_MAGIC;
     +
    @@ src/test/modules/oauth_validator/meson.build (new)
     +    ],
     +    'env': {
     +      'PYTHON': python.path(),
    -+      'with_builtin_oauth': oauth_library,
    ++      'with_libcurl': libcurl.found() ? 'yes' : 'no',
     +      'with_python': 'yes',
     +    },
     +  },
    @@ src/test/modules/oauth_validator/t/001_server.pl (new)
     +	  'Potentially unsafe test oauth not enabled in PG_TEST_EXTRA';
     +}
     +
    -+if ($ENV{with_builtin_oauth} ne 'curl')
    ++if ($ENV{with_libcurl} ne 'yes')
     +{
     +	plan skip_all => 'client-side OAuth not supported by this build';
     +}
    @@ src/test/modules/oauth_validator/t/002_client.pl (new)
     +	$log_start,
     +	log_like => [ qr/oauth_validator: token="my-token", role="$user"/, ]);
     +
    -+if ($ENV{with_builtin_oauth} ne 'curl')
    ++if ($ENV{with_libcurl} ne 'yes')
     +{
     +	# libpq should help users out if no OAuth support is built in.
     +	test(
     +		"fails without custom hook installed",
     +		flags => ["--no-hook"],
     +		expected_stderr =>
    -+		  qr/no custom OAuth flows are available, and libpq was not built using --with-builtin-oauth/
    ++		  qr/no custom OAuth flows are available, and libpq was not built using --with-libcurl/
     +	);
     +}
     +
    @@ src/tools/pgindent/typedefs.list: explain_get_index_name_hook_type
      fe_scram_state
      fe_scram_state_enum
      fetch_range_request
    -@@ src/tools/pgindent/typedefs.list: normal_rand_fctx
    - nsphash_hash
    +@@ src/tools/pgindent/typedefs.list: nsphash_hash
      ntile_context
    + nullingrel_info
      numeric
     +oauth_state
      object_access_hook_type
2:  3d169848db ! 2:  28cc3463aa DO NOT MERGE: Add pytest suite for OAuth
    @@ src/test/python/client/test_oauth.py (new)
     +# The client tests need libpq to have been compiled with OAuth support; skip
     +# them otherwise.
     +pytestmark = pytest.mark.skipif(
    -+    os.getenv("with_builtin_oauth") == "none",
    -+    reason="OAuth client tests require --with-builtin-oauth support",
    ++    os.getenv("with_libcurl") != "yes",
    ++    reason="OAuth client tests require --with-libcurl support",
     +)
     +
     +if platform.system() == "Darwin":
    @@ src/test/python/client/test_oauth.py (new)
     +def fail_oauth_handshake(conn, sasl_resp, *, errmsg="doesn't matter"):
     +    """
     +    Sends a failure response via the OAUTHBEARER mechanism, consumes the
    -+    client's dummy reponse, and issues a FATAL error to end the exchange.
    ++    client's dummy response, and issues a FATAL error to end the exchange.
     +
     +    sasl_resp is a dictionary which will be serialized as the OAUTHBEARER JSON
     +    response. If provided, errmsg is used in the FATAL ErrorResponse.
    @@ src/test/python/client/test_oauth.py (new)
     +    pass
     +
     +
    ++class RawBytes(bytes):
    ++    """
    ++    Like RawResponse, but bypasses the UTF-8 encoding step as well, allowing
    ++    implementations to issue invalid encodings.
    ++    """
    ++
    ++    pass
    ++
    ++
     +class OpenIDProvider(threading.Thread):
     +    """
     +    A thread that runs a mock OpenID provider server on an SSL-enabled socket.
    @@ src/test/python/client/test_oauth.py (new)
     +            self.end_headers()
     +
     +            if resp is not None:
    -+                if not isinstance(resp, RawResponse):
    -+                    resp = json.dumps(resp)
    -+                resp = resp.encode("utf-8")
    ++                if not isinstance(resp, RawBytes):
    ++                    if not isinstance(resp, RawResponse):
    ++                        resp = json.dumps(resp)
    ++                    resp = resp.encode("utf-8")
     +                self.wfile.write(resp)
     +
     +            self.close_connection = True
    @@ src/test/python/client/test_oauth.py (new)
     +            id="bad JSON: invalid syntax",
     +        ),
     +        pytest.param(
    ++            b"\xFF\xFF\xFF\xFF",
    ++            "server's error response is not valid UTF-8",
    ++            id="bad JSON: invalid encoding",
    ++        ),
    ++        pytest.param(
     +            '"abcde"',
     +            "top-level element must be an object",
     +            id="bad JSON: top-level element is a string",
    @@ src/test/python/client/test_oauth.py (new)
     +            id="bad JSON: int openid-configuration member",
     +        ),
     +        pytest.param(
    ++            '{ "status": "invalid_token", "openid-configuration": "", "openid-configuration": "" }',
    ++            'field "openid-configuration" is duplicated',
    ++            id="bad JSON: duplicated field",
    ++        ),
    ++        pytest.param(
     +            '{ "status": "invalid_token", "scope": 1 }',
     +            'field "scope" must be a string',
     +            id="bad JSON: int scope member",
    @@ src/test/python/client/test_oauth.py (new)
     +        with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
     +            initial = start_oauth_handshake(conn)
     +
    ++            if isinstance(response, str):
    ++                response = response.encode("utf-8")
    ++
     +            # Fail the SASL exchange with an invalid JSON response.
     +            pq3.send(
     +                conn,
     +                pq3.types.AuthnRequest,
     +                type=pq3.authn.SASLContinue,
    -+                body=response.encode("utf-8"),
    ++                body=response,
     +            )
     +
     +            # The client should disconnect, so the socket is closed here. (If
    @@ src/test/python/client/test_oauth.py (new)
     +            id="NULL bytes in document",
     +        ),
     +        pytest.param(
    ++            (200, RawBytes(b"blah\xFFblah")),
    ++            r"failed to parse OpenID discovery document: response is not valid UTF-8",
    ++            id="document is not UTF-8",
    ++        ),
    ++        pytest.param(
     +            (200, 123),
     +            r"failed to parse OpenID discovery document: top-level element must be an object",
     +            id="scalar at top level",
    @@ src/test/python/client/test_oauth.py (new)
     +        pytest.param(
     +            (200, {"grant_types_supported": 123}),
     +            r'failed to parse OpenID discovery document: field "grant_types_supported" must be an array of strings',
    -+            id="scalar grant types field",
    ++            id="numeric grant types field",
    ++        ),
    ++        pytest.param(
    ++            (
    ++                200,
    ++                {
    ++                    "grant_types_supported": "urn:ietf:params:oauth:grant-type:device_code"
    ++                },
    ++            ),
    ++            r'failed to parse OpenID discovery document: field "grant_types_supported" must be an array of strings',
    ++            id="string grant types field",
     +        ),
     +        pytest.param(
     +            (200, {"grant_types_supported": {}}),
    @@ src/test/python/client/test_oauth.py (new)
     +            r"failed to parse OpenID discovery document: the issuer identifier \(https://.*/path\) does not match oauth_issuer \(https://.*\)",
     +            id="mismatched issuer identifier",
     +        ),
    ++        pytest.param(
    ++            (
    ++                200,
    ++                RawResponse(
    ++                    """{
    ++                        "issuer": "https://256.256.256.256/path",
    ++                        "token_endpoint": "https://256.256.256.256/token",
    ++                        "grant_types_supported": [
    ++                            "urn:ietf:params:oauth:grant-type:device_code"
    ++                        ],
    ++                        "device_authorization_endpoint": "https://256.256.256.256/dev",
    ++                        "device_authorization_endpoint": "https://256.256.256.256/dev"
    ++                    }"""
    ++                ),
    ++            ),
    ++            r'failed to parse OpenID discovery document: field "device_authorization_endpoint" is duplicated',
    ++            id="duplicated field",
    ++        ),
     +        #
     +        # Exercise HTTP-level failures by breaking the protocol. Note that the
     +        # error messages here are implementation-dependent.
    @@ src/test/python/meson.build (new)
     +subdir('server')
     +
     +pytest_env = {
    -+  'with_builtin_oauth': oauth_library,
    ++  'with_libcurl': libcurl.found() ? 'yes' : 'no',
     +
     +  # Point to the default database; the tests will create their own databases as
     +  # needed.
    @@ src/test/python/server/oauthtest.c (new)
     +	}
     +	else
     +	{
    -+		if (*expected_bearer && !strcmp(token, expected_bearer))
    ++		if (*expected_bearer && strcmp(token, expected_bearer) == 0)
     +			res->authorized = true;
     +		if (set_authn_id)
     +			res->authn_id = authn_id;