From: Daniel Stenberg Date: Tue, 29 Oct 2024 15:53:32 +0000 (+0100) Subject: getinfo: provide info which auth was used for HTTP and proxy X-Git-Tag: curl-8_12_0~276 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9d5ecc961397d1d6b855cd2d9b0db10a552306c7;p=thirdparty%2Fcurl.git getinfo: provide info which auth was used for HTTP and proxy CURLINFO_HTTPAUTH_USED and CURLINFO_PROXYAUTH_USED Tested in 590 and 694 Ref: #12668 Idea-by: Ganesh Viswanathan Closes #15450 --- diff --git a/docs/libcurl/curl_easy_getinfo.md b/docs/libcurl/curl_easy_getinfo.md index 31efc31656..184b8d588a 100644 --- a/docs/libcurl/curl_easy_getinfo.md +++ b/docs/libcurl/curl_easy_getinfo.md @@ -146,6 +146,10 @@ Number of bytes of all headers received. See CURLINFO_HEADER_SIZE(3) Available HTTP authentication methods. See CURLINFO_HTTPAUTH_AVAIL(3) +## CURLINFO_HTTPAUTH_USED + +Used HTTP authentication method. See CURLINFO_HTTPAUTH_USED(3) + ## CURLINFO_HTTP_CONNECTCODE Last proxy CONNECT response code. See CURLINFO_HTTP_CONNECTCODE(3) @@ -225,6 +229,10 @@ CURLINFO_PROTOCOL(3) Available HTTP proxy authentication methods. See CURLINFO_PROXYAUTH_AVAIL(3) +## CURLINFO_PROXYAUTH_USED + +Used HTTP proxy authentication methods. See CURLINFO_PROXYAUTH_USED(3) + ## CURLINFO_PROXY_ERROR Detailed proxy error. See CURLINFO_PROXY_ERROR(3) diff --git a/docs/libcurl/opts/CURLINFO_HTTPAUTH_USED.md b/docs/libcurl/opts/CURLINFO_HTTPAUTH_USED.md new file mode 100644 index 0000000000..29c5067756 --- /dev/null +++ b/docs/libcurl/opts/CURLINFO_HTTPAUTH_USED.md @@ -0,0 +1,76 @@ +--- +c: Copyright (C) Daniel Stenberg, , et al. +SPDX-License-Identifier: curl +Title: CURLINFO_HTTPAUTH_USED +Section: 3 +Source: libcurl +See-also: + - CURLINFO_PROXYAUTH_USED (3) + - CURLINFO_HTTPAUTH_AVAIL (3) + - CURLOPT_HTTPAUTH (3) +Protocol: + - HTTP +Added-in: 8.12.0 +--- + +# NAME + +CURLINFO_HTTPAUTH_USED - get used HTTP authentication method + +# SYNOPSIS + +~~~c +#include + +CURLcode curl_easy_getinfo(CURL *handle, CURLINFO_HTTPAUTH_USED, long *authp); +~~~ + +# DESCRIPTION + +Pass a pointer to a long to receive a bitmask indicating the authentication +method that was used in the previous HTTP request. The meaning of the possible +bits is explained in the CURLOPT_HTTPAUTH(3) option for curl_easy_setopt(3). + +The returned value has zero or one bit set. + +# %PROTOCOLS% + +# EXAMPLE + +~~~c +int main(void) +{ + CURL *curl = curl_easy_init(); + if(curl) { + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC | CURLAUTH_DIGEST); + curl_easy_setopt(curl, CURLOPT_USERNAME, "shrek"); + curl_easy_setopt(curl, CURLOPT_PASSWORD, "swamp"); + + res = curl_easy_perform(curl); + + if(!res) { + long auth; + res = curl_easy_getinfo(curl, CURLINFO_HTTPAUTH_USED, &auth); + if(!res) { + if(!auth) + printf("No auth used\n"); + else { + if(auth == CURLAUTH_DIGEST) + printf("Used Digest authentication\n"); + else + printf("Used Basic authentication\n"); + } + } + } + curl_easy_cleanup(curl); + } +} +~~~ + +# %AVAILABILITY% + +# RETURN VALUE + +Returns CURLE_OK if the option is supported, and CURLE_UNKNOWN_OPTION if not. diff --git a/docs/libcurl/opts/CURLINFO_PROXYAUTH_USED.md b/docs/libcurl/opts/CURLINFO_PROXYAUTH_USED.md new file mode 100644 index 0000000000..45d488bd4d --- /dev/null +++ b/docs/libcurl/opts/CURLINFO_PROXYAUTH_USED.md @@ -0,0 +1,79 @@ +--- +c: Copyright (C) Daniel Stenberg, , et al. +SPDX-License-Identifier: curl +Title: CURLINFO_PROXYAUTH_USED +Section: 3 +Source: libcurl +See-also: + - CURLINFO_HTTPAUTH_USED (3) + - CURLINFO_PROXYAUTH_AVAIL (3) + - CURLOPT_HTTPAUTH (3) +Protocol: + - HTTP +Added-in: 8.12.0 +--- + +# NAME + +CURLINFO_PROXYAUTH_USED - get used HTTP proxy authentication method + +# SYNOPSIS + +~~~c +#include + +CURLcode curl_easy_getinfo(CURL *handle, CURLINFO_PROXYAUTH_USED, long *authp); +~~~ + +# DESCRIPTION + +Pass a pointer to a long to receive a bitmask indicating the authentication +method that was used in the previous request done over an HTTP proxy. The +meaning of the possible bits is explained in the CURLOPT_HTTPAUTH(3) option +for curl_easy_setopt(3). + +The returned value has zero or one bit set. + +# %PROTOCOLS% + +# EXAMPLE + +~~~c +int main(void) +{ + CURL *curl = curl_easy_init(); + if(curl) { + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + curl_easy_setopt(curl, CURLOPT_PROXY, "http://proxy.example.com"); + curl_easy_setopt(curl, CURLOPT_PROXYAUTH, + CURLAUTH_BASIC | CURLAUTH_DIGEST); + curl_easy_setopt(curl, CURLOPT_PROXYUSERNAME, "shrek"); + curl_easy_setopt(curl, CURLOPT_PROXYPASSWORD, "swamp"); + + res = curl_easy_perform(curl); + + if(!res) { + long auth; + res = curl_easy_getinfo(curl, CURLINFO_PROXYAUTH_USED, &auth); + if(!res) { + if(!auth) + printf("No auth used\n"); + else { + if(auth == CURLAUTH_DIGEST) + printf("Used Digest proxy authentication\n"); + else + printf("Used Basic proxy authentication\n"); + } + } + } + curl_easy_cleanup(curl); + } +} +~~~ + +# %AVAILABILITY% + +# RETURN VALUE + +Returns CURLE_OK if the option is supported, and CURLE_UNKNOWN_OPTION if not. diff --git a/docs/libcurl/opts/Makefile.inc b/docs/libcurl/opts/Makefile.inc index 8591f47b55..9d8606dd06 100644 --- a/docs/libcurl/opts/Makefile.inc +++ b/docs/libcurl/opts/Makefile.inc @@ -50,6 +50,7 @@ man_MANS = \ CURLINFO_HTTP_CONNECTCODE.3 \ CURLINFO_HTTP_VERSION.3 \ CURLINFO_HTTPAUTH_AVAIL.3 \ + CURLINFO_HTTPAUTH_USED.3 \ CURLINFO_LASTSOCKET.3 \ CURLINFO_LOCAL_IP.3 \ CURLINFO_LOCAL_PORT.3 \ @@ -67,6 +68,7 @@ man_MANS = \ CURLINFO_PROXY_ERROR.3 \ CURLINFO_PROXY_SSL_VERIFYRESULT.3 \ CURLINFO_PROXYAUTH_AVAIL.3 \ + CURLINFO_PROXYAUTH_USED.3 \ CURLINFO_QUEUE_TIME_T.3 \ CURLINFO_REDIRECT_COUNT.3 \ CURLINFO_REDIRECT_TIME.3 \ diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions index ddda26d832..0fd02ff092 100644 --- a/docs/libcurl/symbols-in-versions +++ b/docs/libcurl/symbols-in-versions @@ -449,6 +449,7 @@ CURLINFO_HTTP_CODE 7.4.1 7.10.8 CURLINFO_HTTP_CONNECTCODE 7.10.7 CURLINFO_HTTP_VERSION 7.50.0 CURLINFO_HTTPAUTH_AVAIL 7.10.8 +CURLINFO_HTTPAUTH_USED 8.12.0 CURLINFO_LASTONE 7.4.1 CURLINFO_LASTSOCKET 7.15.2 7.45.0 CURLINFO_LOCAL_IP 7.21.0 @@ -471,6 +472,7 @@ CURLINFO_PROTOCOL 7.52.0 7.85.0 CURLINFO_PROXY_ERROR 7.73.0 CURLINFO_PROXY_SSL_VERIFYRESULT 7.52.0 CURLINFO_PROXYAUTH_AVAIL 7.10.8 +CURLINFO_PROXYAUTH_USED 8.12.0 CURLINFO_PTR 7.54.1 CURLINFO_QUEUE_TIME_T 8.6.0 CURLINFO_REDIRECT_COUNT 7.9.7 diff --git a/include/curl/curl.h b/include/curl/curl.h index 18835586a1..fae168966d 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -2959,7 +2959,9 @@ typedef enum { CURLINFO_USED_PROXY = CURLINFO_LONG + 66, CURLINFO_POSTTRANSFER_TIME_T = CURLINFO_OFF_T + 67, CURLINFO_EARLYDATA_SENT_T = CURLINFO_OFF_T + 68, - CURLINFO_LASTONE = 68 + CURLINFO_HTTPAUTH_USED = CURLINFO_LONG + 69, + CURLINFO_PROXYAUTH_USED = CURLINFO_LONG + 70, + CURLINFO_LASTONE = 70 } CURLINFO; /* CURLINFO_RESPONSE_CODE is the new name for the option previously known as diff --git a/lib/getinfo.c b/lib/getinfo.c index d40bb3a3a8..ae6b3b8aa0 100644 --- a/lib/getinfo.c +++ b/lib/getinfo.c @@ -69,6 +69,8 @@ CURLcode Curl_initinfo(struct Curl_easy *data) info->request_size = 0; info->proxyauthavail = 0; info->httpauthavail = 0; + info->proxyauthpicked = 0; + info->httpauthpicked = 0; info->numconnects = 0; free(info->contenttype); @@ -272,6 +274,14 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, lptr.to_long = param_longp; *lptr.to_ulong = data->info.proxyauthavail; break; + case CURLINFO_HTTPAUTH_USED: + lptr.to_long = param_longp; + *lptr.to_ulong = data->info.httpauthpicked; + break; + case CURLINFO_PROXYAUTH_USED: + lptr.to_long = param_longp; + *lptr.to_ulong = data->info.proxyauthpicked; + break; case CURLINFO_OS_ERRNO: *param_longp = data->state.os_errno; break; diff --git a/lib/http.c b/lib/http.c index 83efb64679..2bfe8fc8f7 100644 --- a/lib/http.c +++ b/lib/http.c @@ -530,6 +530,8 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data) pickhost = pickoneauth(&data->state.authhost, authmask); if(!pickhost) data->state.authproblem = TRUE; + else + data->info.httpauthpicked = data->state.authhost.picked; if(data->state.authhost.picked == CURLAUTH_NTLM && conn->httpversion > 11) { infof(data, "Forcing HTTP/1.1 for NTLM"); @@ -545,6 +547,9 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data) authmask & ~CURLAUTH_BEARER); if(!pickproxy) data->state.authproblem = TRUE; + else + data->info.proxyauthpicked = data->state.authproxy.picked; + } #endif @@ -851,12 +856,12 @@ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, struct connectdata *conn = data->conn; #ifdef USE_SPNEGO curlnegotiate *negstate = proxy ? &conn->proxy_negotiate_state : - &conn->http_negotiate_state; + &conn->http_negotiate_state; #endif -#if defined(USE_SPNEGO) || \ - defined(USE_NTLM) || \ - !defined(CURL_DISABLE_DIGEST_AUTH) || \ - !defined(CURL_DISABLE_BASIC_AUTH) || \ +#if defined(USE_SPNEGO) || \ + defined(USE_NTLM) || \ + !defined(CURL_DISABLE_DIGEST_AUTH) || \ + !defined(CURL_DISABLE_BASIC_AUTH) || \ !defined(CURL_DISABLE_BEARER_AUTH) unsigned long *availp; @@ -987,7 +992,7 @@ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, authp->avail |= CURLAUTH_BEARER; if(authp->picked == CURLAUTH_BEARER) { /* We asked for Bearer authentication but got a 40X back - anyway, which basically means our token is not valid. */ + anyway, which basically means our token is not valid. */ authp->avail = CURLAUTH_NONE; infof(data, "Authentication problem. Ignoring this."); data->state.authproblem = TRUE; diff --git a/lib/http_ntlm.c b/lib/http_ntlm.c index 49230bc1bd..ab6f1dd921 100644 --- a/lib/http_ntlm.c +++ b/lib/http_ntlm.c @@ -252,6 +252,12 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) break; case NTLMSTATE_LAST: + /* since this is a little artificial in that this is used without any + outgoing auth headers being set, we need to set the bit by force */ + if(proxy) + data->info.proxyauthpicked = CURLAUTH_NTLM; + else + data->info.httpauthpicked = CURLAUTH_NTLM; Curl_safefree(*allocuserpwd); authp->done = TRUE; break; diff --git a/lib/urldata.h b/lib/urldata.h index ca6fc97714..fc09efad61 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -983,6 +983,8 @@ struct PureInfo { curl_off_t request_size; /* the amount of bytes sent in the request(s) */ unsigned long proxyauthavail; /* what proxy auth types were announced */ unsigned long httpauthavail; /* what host auth types were announced */ + unsigned long proxyauthpicked; /* selected proxy auth type */ + unsigned long httpauthpicked; /* selected host auth type */ long numconnects; /* how many new connection did libcurl created */ char *contenttype; /* the content type of the object */ char *wouldredirect; /* URL this would have been redirected to if asked to */ diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 5bef667fa1..ec30fdf31c 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -101,7 +101,7 @@ test652 test653 test654 test655 test656 test658 test659 test660 test661 \ test662 test663 test664 test665 test666 test667 test668 test669 test670 \ test671 test672 test673 test674 test675 test676 test677 test678 test679 \ test680 test681 test682 test683 test684 test685 test686 test687 test688 \ -test689 test690 test691 test692 test693 \ +test689 test690 test691 test692 test693 test694 \ \ test700 test701 test702 test703 test704 test705 test706 test707 test708 \ test709 test710 test711 test712 test713 test714 test715 test716 test717 \ diff --git a/tests/data/test694 b/tests/data/test694 new file mode 100644 index 0000000000..bdb3e0440d --- /dev/null +++ b/tests/data/test694 @@ -0,0 +1,127 @@ + + + +HTTP +HTTP POST +POST callback +HTTP proxy +HTTP proxy NTLM auth +NTLM + + +# Server-side + + + +HTTP/1.1 401 Authorization Required +Server: Apache/1.3.27 (Darwin) PHP/4.1.2 +WWW-Authenticate: Negotiate +WWW-Authenticate: NTLM +Content-Type: text/html; charset=iso-8859-1 +Content-Length: 26 + +This is not the real page + + +# this is returned first since we get no proxy-auth + +HTTP/1.1 401 Authorization Required +WWW-Authenticate: NTLM TlRMTVNTUAACAAAAAgACADAAAACGggEAc51AYVDgyNcAAAAAAAAAAG4AbgAyAAAAQ0MCAAQAQwBDAAEAEgBFAEwASQBTAEEAQgBFAFQASAAEABgAYwBjAC4AaQBjAGUAZABlAHYALgBuAHUAAwAsAGUAbABpAHMAYQBiAGUAdABoAC4AYwBjAC4AaQBjAGUAZABlAHYALgBuAHUAAAAAAA== +Content-Length: 34 + +Hey you, authenticate or go away! + + +# This is supposed to be returned when the server gets the second +# Authorization: NTLM line passed-in from the client + +HTTP/1.1 200 Things are fine +Server: Microsoft-IIS/5.0 +Content-Type: text/html; charset=iso-8859-1 +Content-Length: 42 + +Contents of that page you requested, sir. + + +# This is supposed to be returned when the server gets the second +# request. + +HTTP/1.1 200 Things are fine +Content-Type: yeah/maybe +Content-Length: 42 + +Contents of that second request. Differn. + + + +HTTP/1.1 401 Authorization Required +Server: Apache/1.3.27 (Darwin) PHP/4.1.2 +WWW-Authenticate: Negotiate +WWW-Authenticate: NTLM +Content-Type: text/html; charset=iso-8859-1 +Content-Length: 26 + +HTTP/1.1 401 Authorization Required +WWW-Authenticate: NTLM TlRMTVNTUAACAAAAAgACADAAAACGggEAc51AYVDgyNcAAAAAAAAAAG4AbgAyAAAAQ0MCAAQAQwBDAAEAEgBFAEwASQBTAEEAQgBFAFQASAAEABgAYwBjAC4AaQBjAGUAZABlAHYALgBuAHUAAwAsAGUAbABpAHMAYQBiAGUAdABoAC4AYwBjAC4AaQBjAGUAZABlAHYALgBuAHUAAAAAAA== +Content-Length: 34 + +HTTP/1.1 200 Things are fine +Server: Microsoft-IIS/5.0 +Content-Type: text/html; charset=iso-8859-1 +Content-Length: 42 + +Contents of that page you requested, sir. +HTTP/1.1 200 Things are fine +Content-Type: yeah/maybe +Content-Length: 42 + +Contents of that second request. Differn. + + + +# Client-side + + +http + +# tool to use + +lib%TESTNUMBER + + +NTLM +!SSPI + + +HTTP with NTLM twice, verify CURLINFO_HTTPAUTH_USED + + +http://%HOSTIP:%HTTPPORT/path/mine http://%HOSTIP:%HTTPPORT/path/%TESTNUMBER0010 + + + +# Verify data after the test has been "shot" + + + +GET /path/mine HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + +GET /path/mine HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Authorization: NTLM TlRMTVNTUAABAAAABoIIAAAAAAAAAAAAAAAAAAAAAAA= +Accept: */* + +GET /path/mine HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Authorization: NTLM TlRMTVNTUAADAAAAGAAYAEAAAAAYABgAWAAAAAAAAABwAAAAAgACAHAAAAALAAsAcgAAAAAAAAAAAAAAhoIBAAQt1KW5CgG4YdWWcfXyfXBz1ZMCzYp37xYjBiAizmw58O6eQS7yR66eqYGWeSwl9W1lV09SS1NUQVRJT04= +Accept: */* + +GET /path/6940010 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 7d41954df3..a84886f591 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -48,7 +48,7 @@ LIBTESTPROGS = libauthretry libntlmconnect libprereq \ lib599 \ lib643 lib645 lib650 lib651 lib652 lib653 lib654 lib655 lib658 \ lib659 lib661 lib666 lib667 lib668 \ - lib670 lib671 lib672 lib673 lib674 lib676 lib677 lib678 \ + lib670 lib671 lib672 lib673 lib674 lib676 lib677 lib678 lib694 \ lib1156 \ lib1301 \ lib1485 \ @@ -338,6 +338,8 @@ lib677_LDADD = $(TESTUTIL_LIBS) lib678_SOURCES = lib678.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS) $(MULTIBYTE) lib678_LDADD = $(TESTUTIL_LIBS) +lib694_SOURCES = lib694.c $(SUPPORTFILES) + lib1301_SOURCES = lib1301.c $(SUPPORTFILES) $(TESTUTIL) lib1301_LDADD = $(TESTUTIL_LIBS) diff --git a/tests/libtest/lib590.c b/tests/libtest/lib590.c index cda3b029e3..3d0390c457 100644 --- a/tests/libtest/lib590.c +++ b/tests/libtest/lib590.c @@ -42,6 +42,7 @@ CURLcode test(char *URL) { CURLcode res; CURL *curl; + long usedauth = 0; if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { fprintf(stderr, "curl_global_init() failed\n"); @@ -64,6 +65,11 @@ CURLcode test(char *URL) res = curl_easy_perform(curl); + res = curl_easy_getinfo(curl, CURLINFO_PROXYAUTH_USED, &usedauth); + if(CURLAUTH_NTLM != usedauth) { + printf("CURLINFO_PROXYAUTH_USED did not say NTLM\n"); + } + test_cleanup: curl_easy_cleanup(curl); diff --git a/tests/libtest/lib694.c b/tests/libtest/lib694.c new file mode 100644 index 0000000000..b1791e4dd7 --- /dev/null +++ b/tests/libtest/lib694.c @@ -0,0 +1,73 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "test.h" + +#include "memdebug.h" + +CURLcode test(char *URL) +{ + CURLcode res; + CURL *curl; + long usedauth = 0; + int count = 0; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + fprintf(stderr, "curl_global_init() failed\n"); + return TEST_ERR_MAJOR_BAD; + } + + curl = curl_easy_init(); + if(!curl) { + fprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + test_setopt(curl, CURLOPT_URL, URL); + test_setopt(curl, CURLOPT_HEADER, 1L); + test_setopt(curl, CURLOPT_VERBOSE, 1L); + test_setopt(curl, CURLOPT_HTTPAUTH, + (long) (CURLAUTH_BASIC | CURLAUTH_DIGEST | CURLAUTH_NTLM)); + test_setopt(curl, CURLOPT_USERPWD, "me:password"); + + do { + + res = curl_easy_perform(curl); + + res = curl_easy_getinfo(curl, CURLINFO_HTTPAUTH_USED, &usedauth); + if(CURLAUTH_NTLM != usedauth) { + printf("CURLINFO_HTTPAUTH_USED did not say NTLM\n"); + } + + /* set a new URL for the second, so that we don't restart NTLM */ + test_setopt(curl, CURLOPT_URL, libtest_arg2); + } while(!res && ++count < 2); + +test_cleanup: + + curl_easy_cleanup(curl); + curl_global_cleanup(); + + return res; +}