From d006d6aa460bf9e11985cf2379484034d66aeb4e Mon Sep 17 00:00:00 2001 From: James M Snell Date: Wed, 28 Oct 2015 11:01:02 -0700 Subject: [PATCH] deps: update http-parser to 2.6.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit significant updates: * [[`777ba4eded`](https://github.com/nodejs/node/commit/777ba4eded)] - **src**: introduce `http_parser_url_init` (Fedor Indutny) [nodejs/http-parser#225](https://github.com/nodejs/http-parser/pull/225) * [[`e557b62744`](https://github.com/nodejs/node/commit/e557b62744)] - **src**: support LINK/UNLINK (RFC 2068, draft-snell-link-method) (Olivier Mengué) [nodejs/http-parser#267](https://github.com/nodejs/http-parser/pull/267) * [[`eb5e9928b4`](https://github.com/nodejs/node/commit/eb5e9928b4)] - **src**: support ACL (WebDAV, RFC3744, Section 8.1). (Ivan Enderlin) [joyent/http-parser#260](https://github.com/joyent/http-parser/pull/260) * [[`8b1d652322`](https://github.com/nodejs/node/commit/8b1d652322)] - **src**: support BIND/REBIND/UNBIND (WebDAV, RFC5842) (Ivan Enderlin) [joyent/http-parser#242](https://github.com/joyent/http-parser/pull/242) * [[`7d75dd7325`](https://github.com/nodejs/node/commit/7d75dd7325)] - **src**: support IPv6 Zone ID as per RFC 6874 (Tatsuhiro Tsujikawa) [joyent/http-parser#253](https://github.com/joyent/http-parser/pull/253) PR-URL: https://github.com/nodejs/node/pull/3569 Reviewed-By: Brian White Reviewed-By: Johan Bergström --- deps/http_parser/AUTHORS | 1 + deps/http_parser/Makefile | 31 +++++-- deps/http_parser/README.md | 69 +++++++++++++++- deps/http_parser/contrib/url_parser.c | 3 +- deps/http_parser/http_parser.c | 99 +++++++++++++++++----- deps/http_parser/http_parser.h | 41 +++++---- deps/http_parser/test.c | 114 ++++++++++++++++++++++++++ 7 files changed, 309 insertions(+), 49 deletions(-) diff --git a/deps/http_parser/AUTHORS b/deps/http_parser/AUTHORS index 8e2df1d06e..5323b685ca 100644 --- a/deps/http_parser/AUTHORS +++ b/deps/http_parser/AUTHORS @@ -65,3 +65,4 @@ Romain Giraud Jay Satiro Arne Steen Kjell Schubert +Olivier Mengué diff --git a/deps/http_parser/Makefile b/deps/http_parser/Makefile index 373709c667..33c8ba0239 100644 --- a/deps/http_parser/Makefile +++ b/deps/http_parser/Makefile @@ -19,7 +19,19 @@ # IN THE SOFTWARE. PLATFORM ?= $(shell sh -c 'uname -s | tr "[A-Z]" "[a-z]"') -SONAME ?= libhttp_parser.so.2.5.0 +HELPER ?= +BINEXT ?= +ifeq (darwin,$(PLATFORM)) +SONAME ?= libhttp_parser.2.6.0.dylib +SOEXT ?= dylib +else ifeq (wine,$(PLATFORM)) +CC = winegcc +BINEXT = .exe.so +HELPER = wine +else +SONAME ?= libhttp_parser.so.2.6.0 +SOEXT ?= so +endif CC?=gcc AR?=ar @@ -53,8 +65,8 @@ LDFLAGS_LIB += -Wl,-soname=$(SONAME) endif test: test_g test_fast - ./test_g - ./test_fast + $(HELPER) ./test_g$(BINEXT) + $(HELPER) ./test_fast$(BINEXT) test_g: http_parser_g.o test_g.o $(CC) $(CFLAGS_DEBUG) $(LDFLAGS) http_parser_g.o test_g.o -o $@ @@ -81,7 +93,7 @@ http_parser.o: http_parser.c http_parser.h Makefile $(CC) $(CPPFLAGS_FAST) $(CFLAGS_FAST) -c http_parser.c test-run-timed: test_fast - while(true) do time ./test_fast > /dev/null; done + while(true) do time $(HELPER) ./test_fast$(BINEXT) > /dev/null; done test-valgrind: test_g valgrind ./test_g @@ -102,10 +114,10 @@ url_parser_g: http_parser_g.o contrib/url_parser.c $(CC) $(CPPFLAGS_DEBUG) $(CFLAGS_DEBUG) $^ -o $@ parsertrace: http_parser.o contrib/parsertrace.c - $(CC) $(CPPFLAGS_FAST) $(CFLAGS_FAST) $^ -o parsertrace + $(CC) $(CPPFLAGS_FAST) $(CFLAGS_FAST) $^ -o parsertrace$(BINEXT) parsertrace_g: http_parser_g.o contrib/parsertrace.c - $(CC) $(CPPFLAGS_DEBUG) $(CFLAGS_DEBUG) $^ -o parsertrace_g + $(CC) $(CPPFLAGS_DEBUG) $(CFLAGS_DEBUG) $^ -o parsertrace_g$(BINEXT) tags: http_parser.c http_parser.h test.c ctags $^ @@ -113,12 +125,12 @@ tags: http_parser.c http_parser.h test.c install: library $(INSTALL) -D http_parser.h $(INCLUDEDIR)/http_parser.h $(INSTALL) -D $(SONAME) $(LIBDIR)/$(SONAME) - ln -s $(LIBDIR)/$(SONAME) $(LIBDIR)/libhttp_parser.so + ln -s $(LIBDIR)/$(SONAME) $(LIBDIR)/libhttp_parser.$(SOEXT) install-strip: library $(INSTALL) -D http_parser.h $(INCLUDEDIR)/http_parser.h $(INSTALL) -D -s $(SONAME) $(LIBDIR)/$(SONAME) - ln -s $(LIBDIR)/$(SONAME) $(LIBDIR)/libhttp_parser.so + ln -s $(LIBDIR)/$(SONAME) $(LIBDIR)/libhttp_parser.$(SOEXT) uninstall: rm $(INCLUDEDIR)/http_parser.h @@ -128,7 +140,8 @@ uninstall: clean: rm -f *.o *.a tags test test_fast test_g \ http_parser.tar libhttp_parser.so.* \ - url_parser url_parser_g parsertrace parsertrace_g + url_parser url_parser_g parsertrace parsertrace_g \ + *.exe *.exe.so contrib/url_parser.c: http_parser.h contrib/parsertrace.c: http_parser.h diff --git a/deps/http_parser/README.md b/deps/http_parser/README.md index 7c54dd42d0..abee946933 100644 --- a/deps/http_parser/README.md +++ b/deps/http_parser/README.md @@ -94,7 +94,7 @@ The Special Problem of Upgrade ------------------------------ HTTP supports upgrading the connection to a different protocol. An -increasingly common example of this is the Web Socket protocol which sends +increasingly common example of this is the WebSocket protocol which sends a request like GET /demo HTTP/1.1 @@ -106,8 +106,8 @@ a request like followed by non-HTTP data. -(See http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 for more -information the Web Socket protocol.) +(See [RFC6455](https://tools.ietf.org/html/rfc6455) for more information the +WebSocket protocol.) To support this, the parser will treat this as a normal HTTP message without a body, issuing both on_headers_complete and on_message_complete callbacks. However @@ -137,6 +137,69 @@ There are two types of callbacks: Callbacks must return 0 on success. Returning a non-zero value indicates error to the parser, making it exit immediately. +For cases where it is necessary to pass local information to/from a callback, +the `http_parser` object's `data` field can be used. +An example of such a case is when using threads to handle a socket connection, +parse a request, and then give a response over that socket. By instantiation +of a thread-local struct containing relevant data (e.g. accepted socket, +allocated memory for callbacks to write into, etc), a parser's callbacks are +able to communicate data between the scope of the thread and the scope of the +callback in a threadsafe manner. This allows http-parser to be used in +multi-threaded contexts. + +Example: +``` + typedef struct { + socket_t sock; + void* buffer; + int buf_len; + } custom_data_t; + + +int my_url_callback(http_parser* parser, const char *at, size_t length) { + /* access to thread local custom_data_t struct. + Use this access save parsed data for later use into thread local + buffer, or communicate over socket + */ + parser->data; + ... + return 0; +} + +... + +void http_parser_thread(socket_t sock) { + int nparsed = 0; + /* allocate memory for user data */ + custom_data_t *my_data = malloc(sizeof(custom_data_t)); + + /* some information for use by callbacks. + * achieves thread -> callback information flow */ + my_data->sock = sock; + + /* instantiate a thread-local parser */ + http_parser *parser = malloc(sizeof(http_parser)); + http_parser_init(parser, HTTP_REQUEST); /* initialise parser */ + /* this custom data reference is accessible through the reference to the + parser supplied to callback functions */ + parser->data = my_data; + + http_parser_settings settings; / * set up callbacks */ + settings.on_url = my_url_callback; + + /* execute parser */ + nparsed = http_parser_execute(parser, &settings, buf, recved); + + ... + /* parsed information copied from callback. + can now perform action on data copied into thread-local memory from callbacks. + achieves callback -> thread information flow */ + my_data->buffer; + ... +} + +``` + In case you parse HTTP message in chunks (i.e. `read()` request line from socket, parse, read half headers, parse, etc) your data callbacks may be called more than once. Http-parser guarantees that data pointer is only diff --git a/deps/http_parser/contrib/url_parser.c b/deps/http_parser/contrib/url_parser.c index 6650b414af..f235bed9e4 100644 --- a/deps/http_parser/contrib/url_parser.c +++ b/deps/http_parser/contrib/url_parser.c @@ -35,6 +35,7 @@ int main(int argc, char ** argv) { connect = strcmp("connect", argv[1]) == 0 ? 1 : 0; printf("Parsing %s, connect %d\n", argv[2], connect); + http_parser_url_init(&u); result = http_parser_parse_url(argv[2], len, connect, &u); if (result != 0) { printf("Parse error : %d\n", result); @@ -43,4 +44,4 @@ int main(int argc, char ** argv) { printf("Parse ok, result : \n"); dump_url(argv[2], &u); return 0; -} \ No newline at end of file +} diff --git a/deps/http_parser/http_parser.c b/deps/http_parser/http_parser.c index 0fa1c36272..b3037d7e3d 100644 --- a/deps/http_parser/http_parser.c +++ b/deps/http_parser/http_parser.c @@ -400,6 +400,8 @@ enum http_host_state , s_http_host , s_http_host_v6 , s_http_host_v6_end + , s_http_host_v6_zone_start + , s_http_host_v6_zone , s_http_host_port_start , s_http_host_port }; @@ -957,21 +959,23 @@ reexecute: parser->method = (enum http_method) 0; parser->index = 1; switch (ch) { + case 'A': parser->method = HTTP_ACL; break; + case 'B': parser->method = HTTP_BIND; break; case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break; case 'D': parser->method = HTTP_DELETE; break; case 'G': parser->method = HTTP_GET; break; case 'H': parser->method = HTTP_HEAD; break; - case 'L': parser->method = HTTP_LOCK; break; + case 'L': parser->method = HTTP_LOCK; /* or LINK */ break; case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break; case 'N': parser->method = HTTP_NOTIFY; break; case 'O': parser->method = HTTP_OPTIONS; break; case 'P': parser->method = HTTP_POST; /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ break; - case 'R': parser->method = HTTP_REPORT; break; + case 'R': parser->method = HTTP_REPORT; /* or REBIND */ break; case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break; case 'T': parser->method = HTTP_TRACE; break; - case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break; + case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE, UNBIND, UNLINK */ break; default: SET_ERRNO(HPE_INVALID_METHOD); goto error; @@ -1027,16 +1031,32 @@ reexecute: SET_ERRNO(HPE_INVALID_METHOD); goto error; } - } else if (parser->index == 1 && parser->method == HTTP_POST) { - if (ch == 'R') { - parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */ - } else if (ch == 'U') { - parser->method = HTTP_PUT; /* or HTTP_PURGE */ - } else if (ch == 'A') { - parser->method = HTTP_PATCH; - } else { - SET_ERRNO(HPE_INVALID_METHOD); - goto error; + } else if (parser->method == HTTP_REPORT) { + if (parser->index == 2 && ch == 'B') { + parser->method = HTTP_REBIND; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + } else if (parser->index == 1) { + if (parser->method == HTTP_POST) { + if (ch == 'R') { + parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */ + } else if (ch == 'U') { + parser->method = HTTP_PUT; /* or HTTP_PURGE */ + } else if (ch == 'A') { + parser->method = HTTP_PATCH; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + } else if (parser->method == HTTP_LOCK) { + if (ch == 'I') { + parser->method = HTTP_LINK; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } } } else if (parser->index == 2) { if (parser->method == HTTP_PUT) { @@ -1049,6 +1069,8 @@ reexecute: } else if (parser->method == HTTP_UNLOCK) { if (ch == 'S') { parser->method = HTTP_UNSUBSCRIBE; + } else if(ch == 'B') { + parser->method = HTTP_UNBIND; } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; @@ -1059,6 +1081,8 @@ reexecute: } } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') { parser->method = HTTP_PROPPATCH; + } else if (parser->index == 3 && parser->method == HTTP_UNLOCK && ch == 'I') { + parser->method = HTTP_UNLINK; } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; @@ -1828,11 +1852,12 @@ reexecute: case s_headers_done: { + int hasBody; STRICT_CHECK(ch != LF); parser->nread = 0; - int hasBody = parser->flags & F_CHUNKED || + hasBody = parser->flags & F_CHUNKED || (parser->content_length > 0 && parser->content_length != ULLONG_MAX); if (parser->upgrade && (parser->method == HTTP_CONNECT || (parser->flags & F_SKIPBODY) || !hasBody)) { @@ -1857,8 +1882,7 @@ reexecute: /* Content-Length header given and non-zero */ UPDATE_STATE(s_body_identity); } else { - if (parser->type == HTTP_REQUEST || - !http_message_needs_eof(parser)) { + if (!http_message_needs_eof(parser)) { /* Assume content-length 0 - read the next */ UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); @@ -2153,15 +2177,13 @@ http_parser_settings_init(http_parser_settings *settings) const char * http_errno_name(enum http_errno err) { - assert(((size_t) err) < - (sizeof(http_strerror_tab) / sizeof(http_strerror_tab[0]))); + assert(((size_t) err) < ARRAY_SIZE(http_strerror_tab)); return http_strerror_tab[err].name; } const char * http_errno_description(enum http_errno err) { - assert(((size_t) err) < - (sizeof(http_strerror_tab) / sizeof(http_strerror_tab[0]))); + assert(((size_t) err) < ARRAY_SIZE(http_strerror_tab)); return http_strerror_tab[err].description; } @@ -2214,6 +2236,23 @@ http_parse_host_char(enum http_host_state s, const char ch) { return s_http_host_v6; } + if (s == s_http_host_v6 && ch == '%') { + return s_http_host_v6_zone_start; + } + break; + + case s_http_host_v6_zone: + if (ch == ']') { + return s_http_host_v6_end; + } + + /* FALLTHROUGH */ + case s_http_host_v6_zone_start: + /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */ + if (IS_ALPHANUM(ch) || ch == '%' || ch == '.' || ch == '-' || ch == '_' || + ch == '~') { + return s_http_host_v6_zone; + } break; case s_http_host_port: @@ -2232,6 +2271,7 @@ http_parse_host_char(enum http_host_state s, const char ch) { static int http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { + assert(u->field_set & (1 << UF_HOST)); enum http_host_state s; const char *p; @@ -2263,6 +2303,11 @@ http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { u->field_data[UF_HOST].len++; break; + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: + u->field_data[UF_HOST].len++; + break; + case s_http_host_port: if (s != s_http_host_port) { u->field_data[UF_PORT].off = p - buf; @@ -2292,6 +2337,8 @@ http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { case s_http_host_start: case s_http_host_v6_start: case s_http_host_v6: + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: case s_http_host_port_start: case s_http_userinfo: case s_http_userinfo_start: @@ -2303,6 +2350,11 @@ http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { return 0; } +void +http_parser_url_init(struct http_parser_url *u) { + memset(u, 0, sizeof(*u)); +} + int http_parser_parse_url(const char *buf, size_t buflen, int is_connect, struct http_parser_url *u) @@ -2376,7 +2428,12 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect, /* host must be present if there is a schema */ /* parsing http:///toto will fail */ - if ((u->field_set & ((1 << UF_SCHEMA) | (1 << UF_HOST))) != 0) { + if ((u->field_set & (1 << UF_SCHEMA)) && + (u->field_set & (1 << UF_HOST)) == 0) { + return 1; + } + + if (u->field_set & (1 << UF_HOST)) { if (http_parse_host(buf, u, found_at) != 0) { return 1; } diff --git a/deps/http_parser/http_parser.h b/deps/http_parser/http_parser.h index eb71bf9921..34bebf3d36 100644 --- a/deps/http_parser/http_parser.h +++ b/deps/http_parser/http_parser.h @@ -26,11 +26,12 @@ extern "C" { /* Also update SONAME in the Makefile whenever you change these. */ #define HTTP_PARSER_VERSION_MAJOR 2 -#define HTTP_PARSER_VERSION_MINOR 5 +#define HTTP_PARSER_VERSION_MINOR 6 #define HTTP_PARSER_VERSION_PATCH 0 #include -#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600) +#if defined(_WIN32) && !defined(__MINGW32__) && \ + (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__) #include #include typedef __int8 int8_t; @@ -95,7 +96,7 @@ typedef int (*http_cb) (http_parser*); XX(5, CONNECT, CONNECT) \ XX(6, OPTIONS, OPTIONS) \ XX(7, TRACE, TRACE) \ - /* webdav */ \ + /* WebDAV */ \ XX(8, COPY, COPY) \ XX(9, LOCK, LOCK) \ XX(10, MKCOL, MKCOL) \ @@ -104,21 +105,28 @@ typedef int (*http_cb) (http_parser*); XX(13, PROPPATCH, PROPPATCH) \ XX(14, SEARCH, SEARCH) \ XX(15, UNLOCK, UNLOCK) \ + XX(16, BIND, BIND) \ + XX(17, REBIND, REBIND) \ + XX(18, UNBIND, UNBIND) \ + XX(19, ACL, ACL) \ /* subversion */ \ - XX(16, REPORT, REPORT) \ - XX(17, MKACTIVITY, MKACTIVITY) \ - XX(18, CHECKOUT, CHECKOUT) \ - XX(19, MERGE, MERGE) \ + XX(20, REPORT, REPORT) \ + XX(21, MKACTIVITY, MKACTIVITY) \ + XX(22, CHECKOUT, CHECKOUT) \ + XX(23, MERGE, MERGE) \ /* upnp */ \ - XX(20, MSEARCH, M-SEARCH) \ - XX(21, NOTIFY, NOTIFY) \ - XX(22, SUBSCRIBE, SUBSCRIBE) \ - XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \ + XX(24, MSEARCH, M-SEARCH) \ + XX(25, NOTIFY, NOTIFY) \ + XX(26, SUBSCRIBE, SUBSCRIBE) \ + XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ /* RFC-5789 */ \ - XX(24, PATCH, PATCH) \ - XX(25, PURGE, PURGE) \ + XX(28, PATCH, PATCH) \ + XX(29, PURGE, PURGE) \ /* CalDAV */ \ - XX(26, MKCALENDAR, MKCALENDAR) \ + XX(30, MKCALENDAR, MKCALENDAR) \ + /* RFC-2068, section 19.6.1.2 */ \ + XX(31, LINK, LINK) \ + XX(32, UNLINK, UNLINK) \ enum http_method { @@ -144,7 +152,7 @@ enum flags /* Map for errno-related constants - * + * * The provided argument should be a macro that takes 2 arguments. */ #define HTTP_ERRNO_MAP(XX) \ @@ -325,6 +333,9 @@ const char *http_errno_name(enum http_errno err); /* Return a string description of the given error */ const char *http_errno_description(enum http_errno err); +/* Initialize all http_parser_url members to 0 */ +void http_parser_url_init(struct http_parser_url *u); + /* Parse a URL; return nonzero on failure */ int http_parser_parse_url(const char *buf, size_t buflen, int is_connect, diff --git a/deps/http_parser/test.c b/deps/http_parser/test.c index 4c00571eba..c63b8f0b0b 100644 --- a/deps/http_parser/test.c +++ b/deps/http_parser/test.c @@ -1101,6 +1101,58 @@ const struct message requests[] = ,.body= "" } +/* Examples from the Internet draft for LINK/UNLINK methods: + * https://tools.ietf.org/id/draft-snell-link-method-01.html#rfc.section.5 + */ + +#define LINK_REQUEST 40 +, {.name = "link request" + ,.type= HTTP_REQUEST + ,.raw= "LINK /images/my_dog.jpg HTTP/1.1\r\n" + "Host: example.com\r\n" + "Link: ; rel=\"tag\"\r\n" + "Link: ; rel=\"tag\"\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_LINK + ,.request_path= "/images/my_dog.jpg" + ,.request_url= "/images/my_dog.jpg" + ,.query_string= "" + ,.fragment= "" + ,.num_headers= 3 + ,.headers= { { "Host", "example.com" } + , { "Link", "; rel=\"tag\"" } + , { "Link", "; rel=\"tag\"" } + } + ,.body= "" + } + +#define UNLINK_REQUEST 41 +, {.name = "link request" + ,.type= HTTP_REQUEST + ,.raw= "UNLINK /images/my_dog.jpg HTTP/1.1\r\n" + "Host: example.com\r\n" + "Link: ; rel=\"tag\"\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_UNLINK + ,.request_path= "/images/my_dog.jpg" + ,.request_url= "/images/my_dog.jpg" + ,.query_string= "" + ,.fragment= "" + ,.num_headers= 2 + ,.headers= { { "Host", "example.com" } + , { "Link", "; rel=\"tag\"" } + } + ,.body= "" + } + , {.name= NULL } /* sentinel */ }; @@ -2918,6 +2970,59 @@ const struct url_test url_tests[] = ,.rv=1 /* s_dead */ } +, {.name="ipv6 address with Zone ID" + ,.url="http://[fe80::a%25eth0]/" + ,.is_connect=0 + ,.u= + {.field_set= (1<