1*16Svbart@nginx.com 2*16Svbart@nginx.com /* 3*16Svbart@nginx.com * Copyright (C) NGINX, Inc. 4*16Svbart@nginx.com * Copyright (C) Valentin V. Bartenev 5*16Svbart@nginx.com */ 6*16Svbart@nginx.com 7*16Svbart@nginx.com #include <nxt_main.h> 8*16Svbart@nginx.com 9*16Svbart@nginx.com #ifdef __SSE4_2__ 10*16Svbart@nginx.com #include <x86intrin.h> 11*16Svbart@nginx.com #endif 12*16Svbart@nginx.com 13*16Svbart@nginx.com 14*16Svbart@nginx.com typedef struct { 15*16Svbart@nginx.com nxt_http_field_handler_t handler; 16*16Svbart@nginx.com uintptr_t data; 17*16Svbart@nginx.com union { 18*16Svbart@nginx.com uint8_t str[8]; 19*16Svbart@nginx.com uint64_t ui64; 20*16Svbart@nginx.com } key[]; 21*16Svbart@nginx.com } nxt_http_fields_hash_entry_t; 22*16Svbart@nginx.com 23*16Svbart@nginx.com 24*16Svbart@nginx.com #define nxt_http_fields_hash_next_entry(entry, n) \ 25*16Svbart@nginx.com ((nxt_http_fields_hash_entry_t *) ((u_char *) (entry) \ 26*16Svbart@nginx.com + sizeof(nxt_http_fields_hash_entry_t) \ 27*16Svbart@nginx.com + n * 8)) 28*16Svbart@nginx.com 29*16Svbart@nginx.com 30*16Svbart@nginx.com struct nxt_http_fields_hash_s { 31*16Svbart@nginx.com size_t min_length; 32*16Svbart@nginx.com size_t max_length; 33*16Svbart@nginx.com void *long_fields; 34*16Svbart@nginx.com nxt_http_fields_hash_entry_t *entries[]; 35*16Svbart@nginx.com }; 36*16Svbart@nginx.com 37*16Svbart@nginx.com 38*16Svbart@nginx.com static nxt_int_t nxt_http_parse_unusual_target(nxt_http_request_parse_t *rp, 39*16Svbart@nginx.com u_char **pos, u_char *end); 40*16Svbart@nginx.com static nxt_int_t nxt_http_parse_request_line(nxt_http_request_parse_t *rp, 41*16Svbart@nginx.com u_char **pos, u_char *end); 42*16Svbart@nginx.com static nxt_int_t nxt_http_parse_field_name(nxt_http_request_parse_t *rp, 43*16Svbart@nginx.com u_char **pos, u_char *end); 44*16Svbart@nginx.com static nxt_int_t nxt_http_parse_field_value(nxt_http_request_parse_t *rp, 45*16Svbart@nginx.com u_char **pos, u_char *end); 46*16Svbart@nginx.com static u_char *nxt_http_lookup_field_end(u_char *p, u_char *end); 47*16Svbart@nginx.com static nxt_int_t nxt_http_parse_field_end(nxt_http_request_parse_t *rp, 48*16Svbart@nginx.com u_char **pos, u_char *end); 49*16Svbart@nginx.com 50*16Svbart@nginx.com static nxt_http_fields_hash_entry_t *nxt_http_fields_hash_lookup( 51*16Svbart@nginx.com nxt_http_fields_hash_t *hash, uint64_t *key, nxt_str_t *value); 52*16Svbart@nginx.com static nxt_http_fields_hash_entry_t *nxt_http_header_fields_hash_lookup_long( 53*16Svbart@nginx.com nxt_http_fields_hash_t *hash, nxt_str_t *value); 54*16Svbart@nginx.com 55*16Svbart@nginx.com 56*16Svbart@nginx.com typedef enum { 57*16Svbart@nginx.com NXT_HTTP_TARGET_SPACE = 1, /* \s */ 58*16Svbart@nginx.com NXT_HTTP_TARGET_HASH, /* # */ 59*16Svbart@nginx.com NXT_HTTP_TARGET_AGAIN, 60*16Svbart@nginx.com NXT_HTTP_TARGET_BAD, /* \0\r\n */ 61*16Svbart@nginx.com 62*16Svbart@nginx.com /* traps below are used for extended check only */ 63*16Svbart@nginx.com 64*16Svbart@nginx.com NXT_HTTP_TARGET_SLASH = 5, /* / */ 65*16Svbart@nginx.com NXT_HTTP_TARGET_DOT, /* . */ 66*16Svbart@nginx.com NXT_HTTP_TARGET_ARGS_MARK, /* ? */ 67*16Svbart@nginx.com NXT_HTTP_TARGET_QUOTE_MARK, /* % */ 68*16Svbart@nginx.com NXT_HTTP_TARGET_PLUS, /* + */ 69*16Svbart@nginx.com } nxt_http_target_traps_e; 70*16Svbart@nginx.com 71*16Svbart@nginx.com 72*16Svbart@nginx.com static const uint8_t nxt_http_target_chars[256] nxt_aligned(64) = { 73*16Svbart@nginx.com /* \0 \n \r */ 74*16Svbart@nginx.com 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 4, 0, 0, 75*16Svbart@nginx.com 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 76*16Svbart@nginx.com /* 77*16Svbart@nginx.com * \s ! " # $ % & ' ( ) * + , - . / 78*16Svbart@nginx.com * 0 1 2 3 4 5 6 7 8 9 : ; < = > ? 79*16Svbart@nginx.com */ 80*16Svbart@nginx.com 1, 0, 0, 2, 0, 8, 0, 0, 0, 0, 0, 9, 0, 0, 6, 5, 81*16Svbart@nginx.com 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 82*16Svbart@nginx.com }; 83*16Svbart@nginx.com 84*16Svbart@nginx.com 85*16Svbart@nginx.com nxt_inline nxt_http_target_traps_e 86*16Svbart@nginx.com nxt_http_parse_target(u_char **pos, u_char *end) 87*16Svbart@nginx.com { 88*16Svbart@nginx.com u_char *p; 89*16Svbart@nginx.com nxt_uint_t trap; 90*16Svbart@nginx.com 91*16Svbart@nginx.com p = *pos; 92*16Svbart@nginx.com 93*16Svbart@nginx.com for ( ;; ) { 94*16Svbart@nginx.com if (nxt_slow_path(end - p < 10)) { 95*16Svbart@nginx.com return NXT_HTTP_TARGET_AGAIN; 96*16Svbart@nginx.com } 97*16Svbart@nginx.com 98*16Svbart@nginx.com #define nxt_http_parse_target_step \ 99*16Svbart@nginx.com { \ 100*16Svbart@nginx.com trap = nxt_http_target_chars[*p]; \ 101*16Svbart@nginx.com \ 102*16Svbart@nginx.com if (nxt_slow_path(trap != 0)) { \ 103*16Svbart@nginx.com break; \ 104*16Svbart@nginx.com } \ 105*16Svbart@nginx.com \ 106*16Svbart@nginx.com p++; \ 107*16Svbart@nginx.com } 108*16Svbart@nginx.com 109*16Svbart@nginx.com nxt_http_parse_target_step 110*16Svbart@nginx.com nxt_http_parse_target_step 111*16Svbart@nginx.com nxt_http_parse_target_step 112*16Svbart@nginx.com nxt_http_parse_target_step 113*16Svbart@nginx.com 114*16Svbart@nginx.com nxt_http_parse_target_step 115*16Svbart@nginx.com nxt_http_parse_target_step 116*16Svbart@nginx.com nxt_http_parse_target_step 117*16Svbart@nginx.com nxt_http_parse_target_step 118*16Svbart@nginx.com 119*16Svbart@nginx.com nxt_http_parse_target_step 120*16Svbart@nginx.com nxt_http_parse_target_step 121*16Svbart@nginx.com 122*16Svbart@nginx.com #undef nxt_http_parse_target_step 123*16Svbart@nginx.com } 124*16Svbart@nginx.com 125*16Svbart@nginx.com *pos = p; 126*16Svbart@nginx.com 127*16Svbart@nginx.com return trap; 128*16Svbart@nginx.com } 129*16Svbart@nginx.com 130*16Svbart@nginx.com 131*16Svbart@nginx.com #ifdef __SSE4_2__ 132*16Svbart@nginx.com 133*16Svbart@nginx.com nxt_inline nxt_http_target_traps_e 134*16Svbart@nginx.com nxt_http_parse_target_rest(u_char **pos, u_char *end) 135*16Svbart@nginx.com { 136*16Svbart@nginx.com int n; 137*16Svbart@nginx.com u_char *p; 138*16Svbart@nginx.com nxt_uint_t i; 139*16Svbart@nginx.com 140*16Svbart@nginx.com static const u_char stop_chars[16] nxt_aligned(16) = " #\r\n"; 141*16Svbart@nginx.com 142*16Svbart@nginx.com __m128i pattern = _mm_load_si128((__m128i *) stop_chars); 143*16Svbart@nginx.com 144*16Svbart@nginx.com p = *pos; 145*16Svbart@nginx.com 146*16Svbart@nginx.com for (n = (end - p) / 16; nxt_fast_path(n != 0); n--) { 147*16Svbart@nginx.com 148*16Svbart@nginx.com __m128i test = _mm_loadu_si128((__m128i *) p); 149*16Svbart@nginx.com 150*16Svbart@nginx.com i = _mm_cmpistri(pattern, test, _SIDD_LEAST_SIGNIFICANT 151*16Svbart@nginx.com | _SIDD_CMP_EQUAL_ANY 152*16Svbart@nginx.com | _SIDD_UBYTE_OPS); 153*16Svbart@nginx.com 154*16Svbart@nginx.com p += i; 155*16Svbart@nginx.com 156*16Svbart@nginx.com if (i != 16) { 157*16Svbart@nginx.com *pos = p; 158*16Svbart@nginx.com return nxt_http_target_chars[*p]; 159*16Svbart@nginx.com } 160*16Svbart@nginx.com } 161*16Svbart@nginx.com 162*16Svbart@nginx.com *pos = p; 163*16Svbart@nginx.com 164*16Svbart@nginx.com return nxt_http_parse_target(pos, end); 165*16Svbart@nginx.com } 166*16Svbart@nginx.com 167*16Svbart@nginx.com #else 168*16Svbart@nginx.com #define nxt_http_parse_target_rest nxt_http_parse_target 169*16Svbart@nginx.com #endif 170*16Svbart@nginx.com 171*16Svbart@nginx.com 172*16Svbart@nginx.com nxt_int_t 173*16Svbart@nginx.com nxt_http_parse_request(nxt_http_request_parse_t *rp, nxt_buf_mem_t *b) 174*16Svbart@nginx.com { 175*16Svbart@nginx.com nxt_int_t rc; 176*16Svbart@nginx.com 177*16Svbart@nginx.com if (rp->handler == NULL) { 178*16Svbart@nginx.com rp->handler = &nxt_http_parse_request_line; 179*16Svbart@nginx.com } 180*16Svbart@nginx.com 181*16Svbart@nginx.com do { 182*16Svbart@nginx.com rc = rp->handler(rp, &b->pos, b->free); 183*16Svbart@nginx.com } while (rc == NXT_OK); 184*16Svbart@nginx.com 185*16Svbart@nginx.com return rc; 186*16Svbart@nginx.com } 187*16Svbart@nginx.com 188*16Svbart@nginx.com 189*16Svbart@nginx.com static nxt_int_t 190*16Svbart@nginx.com nxt_http_parse_request_line(nxt_http_request_parse_t *rp, u_char **pos, 191*16Svbart@nginx.com u_char *end) 192*16Svbart@nginx.com { 193*16Svbart@nginx.com u_char *p, ch, *after_slash; 194*16Svbart@nginx.com nxt_int_t rc; 195*16Svbart@nginx.com nxt_http_ver_t version; 196*16Svbart@nginx.com nxt_http_target_traps_e trap; 197*16Svbart@nginx.com 198*16Svbart@nginx.com static const nxt_http_ver_t http11 = { "HTTP/1.1" }; 199*16Svbart@nginx.com static const nxt_http_ver_t http10 = { "HTTP/1.0" }; 200*16Svbart@nginx.com 201*16Svbart@nginx.com p = *pos; 202*16Svbart@nginx.com 203*16Svbart@nginx.com rp->method.start = p; 204*16Svbart@nginx.com 205*16Svbart@nginx.com for ( ;; p++) { 206*16Svbart@nginx.com 207*16Svbart@nginx.com for ( ;; ) { 208*16Svbart@nginx.com if (nxt_slow_path(end - p < 12)) { 209*16Svbart@nginx.com return NXT_AGAIN; 210*16Svbart@nginx.com } 211*16Svbart@nginx.com 212*16Svbart@nginx.com #define nxt_http_parse_request_line_step \ 213*16Svbart@nginx.com { \ 214*16Svbart@nginx.com ch = *p; \ 215*16Svbart@nginx.com \ 216*16Svbart@nginx.com if (nxt_slow_path(ch < 'A' || ch > 'Z')) { \ 217*16Svbart@nginx.com break; \ 218*16Svbart@nginx.com } \ 219*16Svbart@nginx.com \ 220*16Svbart@nginx.com p++; \ 221*16Svbart@nginx.com } 222*16Svbart@nginx.com 223*16Svbart@nginx.com nxt_http_parse_request_line_step 224*16Svbart@nginx.com nxt_http_parse_request_line_step 225*16Svbart@nginx.com nxt_http_parse_request_line_step 226*16Svbart@nginx.com nxt_http_parse_request_line_step 227*16Svbart@nginx.com 228*16Svbart@nginx.com nxt_http_parse_request_line_step 229*16Svbart@nginx.com nxt_http_parse_request_line_step 230*16Svbart@nginx.com nxt_http_parse_request_line_step 231*16Svbart@nginx.com nxt_http_parse_request_line_step 232*16Svbart@nginx.com 233*16Svbart@nginx.com #undef nxt_http_parse_request_line_step 234*16Svbart@nginx.com } 235*16Svbart@nginx.com 236*16Svbart@nginx.com if (nxt_fast_path(ch == ' ')) { 237*16Svbart@nginx.com rp->method.length = p - rp->method.start; 238*16Svbart@nginx.com break; 239*16Svbart@nginx.com } 240*16Svbart@nginx.com 241*16Svbart@nginx.com if (ch == '_' || ch == '-') { 242*16Svbart@nginx.com continue; 243*16Svbart@nginx.com } 244*16Svbart@nginx.com 245*16Svbart@nginx.com if (rp->method.start == p && (ch == NXT_CR || ch == NXT_LF)) { 246*16Svbart@nginx.com rp->method.start++; 247*16Svbart@nginx.com continue; 248*16Svbart@nginx.com } 249*16Svbart@nginx.com 250*16Svbart@nginx.com return NXT_ERROR; 251*16Svbart@nginx.com } 252*16Svbart@nginx.com 253*16Svbart@nginx.com p++; 254*16Svbart@nginx.com 255*16Svbart@nginx.com if (nxt_slow_path(p == end)) { 256*16Svbart@nginx.com return NXT_AGAIN; 257*16Svbart@nginx.com } 258*16Svbart@nginx.com 259*16Svbart@nginx.com /* target */ 260*16Svbart@nginx.com 261*16Svbart@nginx.com nxt_prefetch(&nxt_http_target_chars[' ']); 262*16Svbart@nginx.com nxt_prefetch(&nxt_http_target_chars['@']); 263*16Svbart@nginx.com nxt_prefetch(&nxt_http_target_chars['`']); 264*16Svbart@nginx.com 265*16Svbart@nginx.com ch = *p; 266*16Svbart@nginx.com 267*16Svbart@nginx.com if (nxt_slow_path(ch != '/')) { 268*16Svbart@nginx.com rc = nxt_http_parse_unusual_target(rp, &p, end); 269*16Svbart@nginx.com 270*16Svbart@nginx.com if (nxt_slow_path(rc != NXT_OK)) { 271*16Svbart@nginx.com return rc; 272*16Svbart@nginx.com } 273*16Svbart@nginx.com } 274*16Svbart@nginx.com 275*16Svbart@nginx.com rp->target_start = p; 276*16Svbart@nginx.com 277*16Svbart@nginx.com after_slash = p + 1; 278*16Svbart@nginx.com 279*16Svbart@nginx.com for ( ;; ) { 280*16Svbart@nginx.com p++; 281*16Svbart@nginx.com 282*16Svbart@nginx.com trap = nxt_http_parse_target(&p, end); 283*16Svbart@nginx.com 284*16Svbart@nginx.com switch (trap) { 285*16Svbart@nginx.com case NXT_HTTP_TARGET_SLASH: 286*16Svbart@nginx.com if (nxt_slow_path(after_slash == p)) { 287*16Svbart@nginx.com rp->complex_target = 1; 288*16Svbart@nginx.com goto rest_of_target; 289*16Svbart@nginx.com } 290*16Svbart@nginx.com 291*16Svbart@nginx.com after_slash = p + 1; 292*16Svbart@nginx.com 293*16Svbart@nginx.com rp->exten_start = NULL; 294*16Svbart@nginx.com continue; 295*16Svbart@nginx.com 296*16Svbart@nginx.com case NXT_HTTP_TARGET_DOT: 297*16Svbart@nginx.com if (nxt_slow_path(after_slash == p)) { 298*16Svbart@nginx.com rp->complex_target = 1; 299*16Svbart@nginx.com goto rest_of_target; 300*16Svbart@nginx.com } 301*16Svbart@nginx.com 302*16Svbart@nginx.com rp->exten_start = p + 1; 303*16Svbart@nginx.com continue; 304*16Svbart@nginx.com 305*16Svbart@nginx.com case NXT_HTTP_TARGET_ARGS_MARK: 306*16Svbart@nginx.com rp->args_start = p + 1; 307*16Svbart@nginx.com goto rest_of_target; 308*16Svbart@nginx.com 309*16Svbart@nginx.com case NXT_HTTP_TARGET_SPACE: 310*16Svbart@nginx.com rp->target_end = p; 311*16Svbart@nginx.com goto space_after_target; 312*16Svbart@nginx.com 313*16Svbart@nginx.com case NXT_HTTP_TARGET_QUOTE_MARK: 314*16Svbart@nginx.com rp->quoted_target = 1; 315*16Svbart@nginx.com goto rest_of_target; 316*16Svbart@nginx.com 317*16Svbart@nginx.com case NXT_HTTP_TARGET_PLUS: 318*16Svbart@nginx.com rp->plus_in_target = 1; 319*16Svbart@nginx.com continue; 320*16Svbart@nginx.com 321*16Svbart@nginx.com case NXT_HTTP_TARGET_HASH: 322*16Svbart@nginx.com rp->complex_target = 1; 323*16Svbart@nginx.com goto rest_of_target; 324*16Svbart@nginx.com 325*16Svbart@nginx.com case NXT_HTTP_TARGET_AGAIN: 326*16Svbart@nginx.com return NXT_AGAIN; 327*16Svbart@nginx.com 328*16Svbart@nginx.com case NXT_HTTP_TARGET_BAD: 329*16Svbart@nginx.com return NXT_ERROR; 330*16Svbart@nginx.com } 331*16Svbart@nginx.com 332*16Svbart@nginx.com nxt_unreachable(); 333*16Svbart@nginx.com } 334*16Svbart@nginx.com 335*16Svbart@nginx.com rest_of_target: 336*16Svbart@nginx.com 337*16Svbart@nginx.com for ( ;; ) { 338*16Svbart@nginx.com p++; 339*16Svbart@nginx.com 340*16Svbart@nginx.com trap = nxt_http_parse_target_rest(&p, end); 341*16Svbart@nginx.com 342*16Svbart@nginx.com switch (trap) { 343*16Svbart@nginx.com case NXT_HTTP_TARGET_SPACE: 344*16Svbart@nginx.com rp->target_end = p; 345*16Svbart@nginx.com goto space_after_target; 346*16Svbart@nginx.com 347*16Svbart@nginx.com case NXT_HTTP_TARGET_HASH: 348*16Svbart@nginx.com rp->complex_target = 1; 349*16Svbart@nginx.com continue; 350*16Svbart@nginx.com 351*16Svbart@nginx.com case NXT_HTTP_TARGET_AGAIN: 352*16Svbart@nginx.com return NXT_AGAIN; 353*16Svbart@nginx.com 354*16Svbart@nginx.com case NXT_HTTP_TARGET_BAD: 355*16Svbart@nginx.com return NXT_ERROR; 356*16Svbart@nginx.com 357*16Svbart@nginx.com default: 358*16Svbart@nginx.com continue; 359*16Svbart@nginx.com } 360*16Svbart@nginx.com 361*16Svbart@nginx.com nxt_unreachable(); 362*16Svbart@nginx.com } 363*16Svbart@nginx.com 364*16Svbart@nginx.com space_after_target: 365*16Svbart@nginx.com 366*16Svbart@nginx.com if (nxt_slow_path(end - p < 10)) { 367*16Svbart@nginx.com return NXT_AGAIN; 368*16Svbart@nginx.com } 369*16Svbart@nginx.com 370*16Svbart@nginx.com /* " HTTP/1.1\r\n" or " HTTP/1.1\n" */ 371*16Svbart@nginx.com 372*16Svbart@nginx.com nxt_memcpy(version.str, &p[1], 8); 373*16Svbart@nginx.com 374*16Svbart@nginx.com if (nxt_fast_path((version.ui64 == http11.ui64 375*16Svbart@nginx.com || version.ui64 == http10.ui64 376*16Svbart@nginx.com || (p[1] == 'H' 377*16Svbart@nginx.com && p[2] == 'T' 378*16Svbart@nginx.com && p[3] == 'T' 379*16Svbart@nginx.com && p[4] == 'P' 380*16Svbart@nginx.com && p[5] == '/' 381*16Svbart@nginx.com && p[6] >= '0' && p[6] <= '9' 382*16Svbart@nginx.com && p[7] == '.' 383*16Svbart@nginx.com && p[8] >= '0' && p[8] <= '9')) 384*16Svbart@nginx.com && (p[9] == '\r' || p[9] == '\n'))) 385*16Svbart@nginx.com { 386*16Svbart@nginx.com rp->version.ui64 = version.ui64; 387*16Svbart@nginx.com 388*16Svbart@nginx.com if (nxt_fast_path(p[9] == '\r')) { 389*16Svbart@nginx.com p += 10; 390*16Svbart@nginx.com 391*16Svbart@nginx.com if (nxt_slow_path(p == end)) { 392*16Svbart@nginx.com return NXT_AGAIN; 393*16Svbart@nginx.com } 394*16Svbart@nginx.com 395*16Svbart@nginx.com if (nxt_slow_path(*p != '\n')) { 396*16Svbart@nginx.com return NXT_ERROR; 397*16Svbart@nginx.com } 398*16Svbart@nginx.com 399*16Svbart@nginx.com *pos = p + 1; 400*16Svbart@nginx.com return nxt_http_parse_field_name(rp, pos, end); 401*16Svbart@nginx.com } 402*16Svbart@nginx.com 403*16Svbart@nginx.com *pos = p + 10; 404*16Svbart@nginx.com return nxt_http_parse_field_name(rp, pos, end); 405*16Svbart@nginx.com } 406*16Svbart@nginx.com 407*16Svbart@nginx.com if (p[1] == ' ') { 408*16Svbart@nginx.com /* surplus space after tartet */ 409*16Svbart@nginx.com p++; 410*16Svbart@nginx.com goto space_after_target; 411*16Svbart@nginx.com } 412*16Svbart@nginx.com 413*16Svbart@nginx.com rp->space_in_target = 1; 414*16Svbart@nginx.com goto rest_of_target; 415*16Svbart@nginx.com } 416*16Svbart@nginx.com 417*16Svbart@nginx.com 418*16Svbart@nginx.com static nxt_int_t 419*16Svbart@nginx.com nxt_http_parse_unusual_target(nxt_http_request_parse_t *rp, u_char **pos, 420*16Svbart@nginx.com u_char *end) 421*16Svbart@nginx.com { 422*16Svbart@nginx.com u_char *p, ch; 423*16Svbart@nginx.com 424*16Svbart@nginx.com p = *pos; 425*16Svbart@nginx.com 426*16Svbart@nginx.com ch = *p; 427*16Svbart@nginx.com 428*16Svbart@nginx.com if (ch == ' ') { 429*16Svbart@nginx.com /* skip surplus spaces before target */ 430*16Svbart@nginx.com 431*16Svbart@nginx.com do { 432*16Svbart@nginx.com p++; 433*16Svbart@nginx.com 434*16Svbart@nginx.com if (nxt_slow_path(p == end)) { 435*16Svbart@nginx.com return NXT_AGAIN; 436*16Svbart@nginx.com } 437*16Svbart@nginx.com 438*16Svbart@nginx.com ch = *p; 439*16Svbart@nginx.com 440*16Svbart@nginx.com } while (ch == ' '); 441*16Svbart@nginx.com 442*16Svbart@nginx.com if (ch == '/') { 443*16Svbart@nginx.com *pos = p; 444*16Svbart@nginx.com return NXT_OK; 445*16Svbart@nginx.com } 446*16Svbart@nginx.com } 447*16Svbart@nginx.com 448*16Svbart@nginx.com /* absolute path or '*' */ 449*16Svbart@nginx.com 450*16Svbart@nginx.com /* TODO */ 451*16Svbart@nginx.com 452*16Svbart@nginx.com return NXT_ERROR; 453*16Svbart@nginx.com } 454*16Svbart@nginx.com 455*16Svbart@nginx.com 456*16Svbart@nginx.com static nxt_int_t 457*16Svbart@nginx.com nxt_http_parse_field_name(nxt_http_request_parse_t *rp, u_char **pos, 458*16Svbart@nginx.com u_char *end) 459*16Svbart@nginx.com { 460*16Svbart@nginx.com u_char *p, ch, c; 461*16Svbart@nginx.com size_t i, size; 462*16Svbart@nginx.com 463*16Svbart@nginx.com static const u_char normal[256] nxt_aligned(64) = 464*16Svbart@nginx.com "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" 465*16Svbart@nginx.com "\0\0\0\0\0\0\0\0\0\0\0\0\0-\0\0" "0123456789\0\0\0\0\0\0" 466*16Svbart@nginx.com 467*16Svbart@nginx.com /* These 64 bytes should reside in one cache line. */ 468*16Svbart@nginx.com "\0abcdefghijklmnopqrstuvwxyz\0\0\0\0\0" 469*16Svbart@nginx.com "\0abcdefghijklmnopqrstuvwxyz\0\0\0\0\0" 470*16Svbart@nginx.com 471*16Svbart@nginx.com "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" 472*16Svbart@nginx.com "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" 473*16Svbart@nginx.com "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" 474*16Svbart@nginx.com "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; 475*16Svbart@nginx.com 476*16Svbart@nginx.com p = *pos; 477*16Svbart@nginx.com 478*16Svbart@nginx.com size = end - p; 479*16Svbart@nginx.com 480*16Svbart@nginx.com for (i = rp->offset; i != size; i++) { 481*16Svbart@nginx.com 482*16Svbart@nginx.com ch = p[i]; 483*16Svbart@nginx.com 484*16Svbart@nginx.com c = normal[ch]; 485*16Svbart@nginx.com 486*16Svbart@nginx.com if (nxt_fast_path(c != '\0')) { 487*16Svbart@nginx.com rp->field_name_key.str[i % 32] = c; 488*16Svbart@nginx.com continue; 489*16Svbart@nginx.com } 490*16Svbart@nginx.com 491*16Svbart@nginx.com if (nxt_fast_path(ch == ':')) { 492*16Svbart@nginx.com if (nxt_slow_path(i == 0)) { 493*16Svbart@nginx.com return NXT_ERROR; 494*16Svbart@nginx.com } 495*16Svbart@nginx.com 496*16Svbart@nginx.com *pos = &p[i] + 1; 497*16Svbart@nginx.com 498*16Svbart@nginx.com rp->field_name.start = p; 499*16Svbart@nginx.com rp->field_name.length = i; 500*16Svbart@nginx.com 501*16Svbart@nginx.com rp->offset = 0; 502*16Svbart@nginx.com 503*16Svbart@nginx.com return nxt_http_parse_field_value(rp, pos, end); 504*16Svbart@nginx.com } 505*16Svbart@nginx.com 506*16Svbart@nginx.com *pos = &p[i]; 507*16Svbart@nginx.com 508*16Svbart@nginx.com rp->field_name.length = 0; 509*16Svbart@nginx.com 510*16Svbart@nginx.com return nxt_http_parse_field_end(rp, pos, end); 511*16Svbart@nginx.com } 512*16Svbart@nginx.com 513*16Svbart@nginx.com rp->offset = i; 514*16Svbart@nginx.com rp->handler = &nxt_http_parse_field_name; 515*16Svbart@nginx.com 516*16Svbart@nginx.com return NXT_AGAIN; 517*16Svbart@nginx.com } 518*16Svbart@nginx.com 519*16Svbart@nginx.com 520*16Svbart@nginx.com static nxt_int_t 521*16Svbart@nginx.com nxt_http_parse_field_value(nxt_http_request_parse_t *rp, u_char **pos, 522*16Svbart@nginx.com u_char *end) 523*16Svbart@nginx.com { 524*16Svbart@nginx.com u_char *p, ch; 525*16Svbart@nginx.com 526*16Svbart@nginx.com p = *pos; 527*16Svbart@nginx.com 528*16Svbart@nginx.com for ( ;; ) { 529*16Svbart@nginx.com if (nxt_slow_path(p == end)) { 530*16Svbart@nginx.com *pos = p; 531*16Svbart@nginx.com rp->handler = &nxt_http_parse_field_value; 532*16Svbart@nginx.com return NXT_AGAIN; 533*16Svbart@nginx.com } 534*16Svbart@nginx.com 535*16Svbart@nginx.com if (*p != ' ') { 536*16Svbart@nginx.com break; 537*16Svbart@nginx.com } 538*16Svbart@nginx.com 539*16Svbart@nginx.com p++; 540*16Svbart@nginx.com } 541*16Svbart@nginx.com 542*16Svbart@nginx.com *pos = p; 543*16Svbart@nginx.com 544*16Svbart@nginx.com p += rp->offset; 545*16Svbart@nginx.com 546*16Svbart@nginx.com for ( ;; ) { 547*16Svbart@nginx.com p = nxt_http_lookup_field_end(p, end); 548*16Svbart@nginx.com 549*16Svbart@nginx.com if (nxt_slow_path(p == end)) { 550*16Svbart@nginx.com rp->offset = p - *pos; 551*16Svbart@nginx.com rp->handler = &nxt_http_parse_field_value; 552*16Svbart@nginx.com return NXT_AGAIN; 553*16Svbart@nginx.com } 554*16Svbart@nginx.com 555*16Svbart@nginx.com ch = *p; 556*16Svbart@nginx.com 557*16Svbart@nginx.com if (nxt_fast_path(ch == '\r' || ch == '\n')) { 558*16Svbart@nginx.com break; 559*16Svbart@nginx.com } 560*16Svbart@nginx.com 561*16Svbart@nginx.com if (ch == '\0') { 562*16Svbart@nginx.com return NXT_ERROR; 563*16Svbart@nginx.com } 564*16Svbart@nginx.com } 565*16Svbart@nginx.com 566*16Svbart@nginx.com if (nxt_fast_path(p != *pos)) { 567*16Svbart@nginx.com while (p[-1] == ' ') { 568*16Svbart@nginx.com p--; 569*16Svbart@nginx.com } 570*16Svbart@nginx.com } 571*16Svbart@nginx.com 572*16Svbart@nginx.com rp->offset = 0; 573*16Svbart@nginx.com 574*16Svbart@nginx.com rp->field_value.start = *pos; 575*16Svbart@nginx.com rp->field_value.length = p - *pos; 576*16Svbart@nginx.com 577*16Svbart@nginx.com *pos = p; 578*16Svbart@nginx.com 579*16Svbart@nginx.com return nxt_http_parse_field_end(rp, pos, end); 580*16Svbart@nginx.com } 581*16Svbart@nginx.com 582*16Svbart@nginx.com 583*16Svbart@nginx.com static u_char * 584*16Svbart@nginx.com nxt_http_lookup_field_end(u_char *p, u_char *end) 585*16Svbart@nginx.com { 586*16Svbart@nginx.com nxt_uint_t n; 587*16Svbart@nginx.com #ifdef __SSE4_2__ 588*16Svbart@nginx.com nxt_uint_t i; 589*16Svbart@nginx.com 590*16Svbart@nginx.com static const u_char end_chars[16] nxt_aligned(16) = "\r\n"; 591*16Svbart@nginx.com 592*16Svbart@nginx.com __m128i pattern = _mm_load_si128((__m128i *) end_chars); 593*16Svbart@nginx.com 594*16Svbart@nginx.com for (n = (end - p) / 16; nxt_fast_path(n != 0); n--) { 595*16Svbart@nginx.com 596*16Svbart@nginx.com __m128i test = _mm_loadu_si128((__m128i *) p); 597*16Svbart@nginx.com 598*16Svbart@nginx.com i = _mm_cmpistri(pattern, test, _SIDD_LEAST_SIGNIFICANT 599*16Svbart@nginx.com | _SIDD_CMP_EQUAL_ANY 600*16Svbart@nginx.com | _SIDD_UBYTE_OPS); 601*16Svbart@nginx.com 602*16Svbart@nginx.com p += i; 603*16Svbart@nginx.com 604*16Svbart@nginx.com if (i != 16) { 605*16Svbart@nginx.com return p; 606*16Svbart@nginx.com } 607*16Svbart@nginx.com } 608*16Svbart@nginx.com #endif 609*16Svbart@nginx.com 610*16Svbart@nginx.com #define nxt_http_lookup_field_end_step \ 611*16Svbart@nginx.com { \ 612*16Svbart@nginx.com if (nxt_slow_path(*p <= '\r')) { \ 613*16Svbart@nginx.com return p; \ 614*16Svbart@nginx.com } \ 615*16Svbart@nginx.com \ 616*16Svbart@nginx.com p++; \ 617*16Svbart@nginx.com } 618*16Svbart@nginx.com 619*16Svbart@nginx.com for (n = (end - p) / 8; nxt_fast_path(n != 0); n--) { 620*16Svbart@nginx.com nxt_http_lookup_field_end_step 621*16Svbart@nginx.com nxt_http_lookup_field_end_step 622*16Svbart@nginx.com nxt_http_lookup_field_end_step 623*16Svbart@nginx.com nxt_http_lookup_field_end_step 624*16Svbart@nginx.com 625*16Svbart@nginx.com nxt_http_lookup_field_end_step 626*16Svbart@nginx.com nxt_http_lookup_field_end_step 627*16Svbart@nginx.com nxt_http_lookup_field_end_step 628*16Svbart@nginx.com nxt_http_lookup_field_end_step 629*16Svbart@nginx.com } 630*16Svbart@nginx.com 631*16Svbart@nginx.com switch (end - p) { 632*16Svbart@nginx.com case 7: 633*16Svbart@nginx.com nxt_http_lookup_field_end_step 634*16Svbart@nginx.com case 6: 635*16Svbart@nginx.com nxt_http_lookup_field_end_step 636*16Svbart@nginx.com case 5: 637*16Svbart@nginx.com nxt_http_lookup_field_end_step 638*16Svbart@nginx.com case 4: 639*16Svbart@nginx.com nxt_http_lookup_field_end_step 640*16Svbart@nginx.com case 3: 641*16Svbart@nginx.com nxt_http_lookup_field_end_step 642*16Svbart@nginx.com case 2: 643*16Svbart@nginx.com nxt_http_lookup_field_end_step 644*16Svbart@nginx.com case 1: 645*16Svbart@nginx.com nxt_http_lookup_field_end_step 646*16Svbart@nginx.com case 0: 647*16Svbart@nginx.com break; 648*16Svbart@nginx.com default: 649*16Svbart@nginx.com nxt_unreachable(); 650*16Svbart@nginx.com } 651*16Svbart@nginx.com 652*16Svbart@nginx.com #undef nxt_http_lookup_field_end_step 653*16Svbart@nginx.com 654*16Svbart@nginx.com return p; 655*16Svbart@nginx.com } 656*16Svbart@nginx.com 657*16Svbart@nginx.com 658*16Svbart@nginx.com static nxt_int_t 659*16Svbart@nginx.com nxt_http_parse_field_end(nxt_http_request_parse_t *rp, u_char **pos, 660*16Svbart@nginx.com u_char *end) 661*16Svbart@nginx.com { 662*16Svbart@nginx.com u_char *p; 663*16Svbart@nginx.com nxt_int_t rc; 664*16Svbart@nginx.com nxt_http_fields_hash_entry_t *entry; 665*16Svbart@nginx.com 666*16Svbart@nginx.com p = *pos; 667*16Svbart@nginx.com 668*16Svbart@nginx.com if (nxt_fast_path(*p == '\r')) { 669*16Svbart@nginx.com p++; 670*16Svbart@nginx.com 671*16Svbart@nginx.com if (nxt_slow_path(p == end)) { 672*16Svbart@nginx.com rp->handler = &nxt_http_parse_field_end; 673*16Svbart@nginx.com return NXT_AGAIN; 674*16Svbart@nginx.com } 675*16Svbart@nginx.com } 676*16Svbart@nginx.com 677*16Svbart@nginx.com if (nxt_fast_path(*p == '\n')) { 678*16Svbart@nginx.com *pos = p + 1; 679*16Svbart@nginx.com 680*16Svbart@nginx.com if (rp->field_name.length != 0) { 681*16Svbart@nginx.com entry = nxt_http_fields_hash_lookup(rp->hash, 682*16Svbart@nginx.com rp->field_name_key.ui64, 683*16Svbart@nginx.com &rp->field_name); 684*16Svbart@nginx.com 685*16Svbart@nginx.com if (entry != NULL) { 686*16Svbart@nginx.com rc = entry->handler(rp->ctx, &rp->field_name, &rp->field_value, 687*16Svbart@nginx.com entry->data); 688*16Svbart@nginx.com 689*16Svbart@nginx.com if (nxt_slow_path(rc != NXT_OK)) { 690*16Svbart@nginx.com return NXT_ERROR; 691*16Svbart@nginx.com } 692*16Svbart@nginx.com } 693*16Svbart@nginx.com 694*16Svbart@nginx.com nxt_memzero(rp->field_name_key.str, 32); 695*16Svbart@nginx.com 696*16Svbart@nginx.com rp->handler = &nxt_http_parse_field_name; 697*16Svbart@nginx.com return NXT_OK; 698*16Svbart@nginx.com } 699*16Svbart@nginx.com 700*16Svbart@nginx.com return NXT_DONE; 701*16Svbart@nginx.com } 702*16Svbart@nginx.com 703*16Svbart@nginx.com return NXT_ERROR; 704*16Svbart@nginx.com } 705*16Svbart@nginx.com 706*16Svbart@nginx.com 707*16Svbart@nginx.com static nxt_http_fields_hash_entry_t * 708*16Svbart@nginx.com nxt_http_fields_hash_lookup(nxt_http_fields_hash_t *hash, uint64_t *key, 709*16Svbart@nginx.com nxt_str_t *value) 710*16Svbart@nginx.com { 711*16Svbart@nginx.com nxt_http_fields_hash_entry_t *entry; 712*16Svbart@nginx.com 713*16Svbart@nginx.com if (hash == NULL || value->length < hash->min_length) { 714*16Svbart@nginx.com return NULL; 715*16Svbart@nginx.com } 716*16Svbart@nginx.com 717*16Svbart@nginx.com if (value->length > hash->max_length) { 718*16Svbart@nginx.com if (value->length > 32 && hash->long_fields != NULL) { 719*16Svbart@nginx.com return nxt_http_header_fields_hash_lookup_long(hash, value); 720*16Svbart@nginx.com } 721*16Svbart@nginx.com 722*16Svbart@nginx.com return NULL; 723*16Svbart@nginx.com } 724*16Svbart@nginx.com 725*16Svbart@nginx.com entry = hash->entries[value->length - hash->min_length]; 726*16Svbart@nginx.com 727*16Svbart@nginx.com if (entry == NULL) { 728*16Svbart@nginx.com return NULL; 729*16Svbart@nginx.com } 730*16Svbart@nginx.com 731*16Svbart@nginx.com switch ((value->length + 7) / 8) { 732*16Svbart@nginx.com case 1: 733*16Svbart@nginx.com do { 734*16Svbart@nginx.com if (entry->key[0].ui64 == key[0]) { 735*16Svbart@nginx.com return entry; 736*16Svbart@nginx.com } 737*16Svbart@nginx.com 738*16Svbart@nginx.com entry = nxt_http_fields_hash_next_entry(entry, 1); 739*16Svbart@nginx.com 740*16Svbart@nginx.com } while (entry->handler != NULL); 741*16Svbart@nginx.com 742*16Svbart@nginx.com break; 743*16Svbart@nginx.com 744*16Svbart@nginx.com case 2: 745*16Svbart@nginx.com do { 746*16Svbart@nginx.com if (entry->key[0].ui64 == key[0] 747*16Svbart@nginx.com && entry->key[1].ui64 == key[1]) 748*16Svbart@nginx.com { 749*16Svbart@nginx.com return entry; 750*16Svbart@nginx.com } 751*16Svbart@nginx.com 752*16Svbart@nginx.com entry = nxt_http_fields_hash_next_entry(entry, 2); 753*16Svbart@nginx.com 754*16Svbart@nginx.com } while (entry->handler != NULL); 755*16Svbart@nginx.com 756*16Svbart@nginx.com break; 757*16Svbart@nginx.com 758*16Svbart@nginx.com case 3: 759*16Svbart@nginx.com do { 760*16Svbart@nginx.com if (entry->key[0].ui64 == key[0] 761*16Svbart@nginx.com && entry->key[1].ui64 == key[1] 762*16Svbart@nginx.com && entry->key[2].ui64 == key[2]) 763*16Svbart@nginx.com { 764*16Svbart@nginx.com return entry; 765*16Svbart@nginx.com } 766*16Svbart@nginx.com 767*16Svbart@nginx.com entry = nxt_http_fields_hash_next_entry(entry, 3); 768*16Svbart@nginx.com 769*16Svbart@nginx.com } while (entry->handler != NULL); 770*16Svbart@nginx.com 771*16Svbart@nginx.com break; 772*16Svbart@nginx.com 773*16Svbart@nginx.com case 4: 774*16Svbart@nginx.com do { 775*16Svbart@nginx.com if (entry->key[0].ui64 == key[0] 776*16Svbart@nginx.com && entry->key[1].ui64 == key[1] 777*16Svbart@nginx.com && entry->key[2].ui64 == key[2] 778*16Svbart@nginx.com && entry->key[3].ui64 == key[3]) 779*16Svbart@nginx.com { 780*16Svbart@nginx.com return entry; 781*16Svbart@nginx.com } 782*16Svbart@nginx.com 783*16Svbart@nginx.com entry = nxt_http_fields_hash_next_entry(entry, 4); 784*16Svbart@nginx.com 785*16Svbart@nginx.com } while (entry->handler != NULL); 786*16Svbart@nginx.com 787*16Svbart@nginx.com break; 788*16Svbart@nginx.com 789*16Svbart@nginx.com default: 790*16Svbart@nginx.com nxt_unreachable(); 791*16Svbart@nginx.com } 792*16Svbart@nginx.com 793*16Svbart@nginx.com return NULL; 794*16Svbart@nginx.com } 795*16Svbart@nginx.com 796*16Svbart@nginx.com 797*16Svbart@nginx.com static nxt_http_fields_hash_entry_t * 798*16Svbart@nginx.com nxt_http_header_fields_hash_lookup_long(nxt_http_fields_hash_t *hash, 799*16Svbart@nginx.com nxt_str_t *value) 800*16Svbart@nginx.com { 801*16Svbart@nginx.com /* TODO */ 802*16Svbart@nginx.com return NULL; 803*16Svbart@nginx.com } 804*16Svbart@nginx.com 805*16Svbart@nginx.com 806*16Svbart@nginx.com nxt_http_fields_hash_t * 807*16Svbart@nginx.com nxt_http_fields_hash(nxt_http_fields_t *fields, nxt_mem_pool_t *mp) 808*16Svbart@nginx.com { 809*16Svbart@nginx.com size_t min_length, max_length, length, size; 810*16Svbart@nginx.com nxt_uint_t i, j, n; 811*16Svbart@nginx.com nxt_http_fields_hash_t *hash; 812*16Svbart@nginx.com nxt_http_fields_hash_entry_t *entry; 813*16Svbart@nginx.com 814*16Svbart@nginx.com min_length = 0; 815*16Svbart@nginx.com max_length = 0; 816*16Svbart@nginx.com 817*16Svbart@nginx.com for (i = 0; fields[i].handler != NULL; i++) { 818*16Svbart@nginx.com length = fields[i].name.length; 819*16Svbart@nginx.com 820*16Svbart@nginx.com if (length > 32) { 821*16Svbart@nginx.com /* TODO */ 822*16Svbart@nginx.com return NULL; 823*16Svbart@nginx.com } 824*16Svbart@nginx.com 825*16Svbart@nginx.com min_length = nxt_min(length, min_length); 826*16Svbart@nginx.com max_length = nxt_max(length, max_length); 827*16Svbart@nginx.com } 828*16Svbart@nginx.com 829*16Svbart@nginx.com size = (max_length - min_length + 1) 830*16Svbart@nginx.com * sizeof(nxt_http_fields_hash_entry_t *); 831*16Svbart@nginx.com 832*16Svbart@nginx.com hash = nxt_mem_zalloc(mp, sizeof(nxt_http_fields_hash_t) + size); 833*16Svbart@nginx.com 834*16Svbart@nginx.com if (nxt_slow_path(hash == NULL)) { 835*16Svbart@nginx.com return NULL; 836*16Svbart@nginx.com } 837*16Svbart@nginx.com 838*16Svbart@nginx.com hash->min_length = min_length; 839*16Svbart@nginx.com hash->max_length = max_length; 840*16Svbart@nginx.com 841*16Svbart@nginx.com for (i = 0; fields[i].handler != NULL; i++) { 842*16Svbart@nginx.com length = fields[i].name.length; 843*16Svbart@nginx.com entry = hash->entries[length - min_length]; 844*16Svbart@nginx.com 845*16Svbart@nginx.com if (entry != NULL) { 846*16Svbart@nginx.com continue; 847*16Svbart@nginx.com } 848*16Svbart@nginx.com 849*16Svbart@nginx.com n = 1; 850*16Svbart@nginx.com 851*16Svbart@nginx.com for (j = i + 1; fields[j].handler != NULL; j++) { 852*16Svbart@nginx.com if (length == fields[j].name.length) { 853*16Svbart@nginx.com n++; 854*16Svbart@nginx.com } 855*16Svbart@nginx.com } 856*16Svbart@nginx.com 857*16Svbart@nginx.com size = sizeof(nxt_http_fields_hash_entry_t) + nxt_align_size(length, 8); 858*16Svbart@nginx.com 859*16Svbart@nginx.com entry = nxt_mem_zalloc(mp, n * size 860*16Svbart@nginx.com + sizeof(nxt_http_fields_hash_entry_t)); 861*16Svbart@nginx.com 862*16Svbart@nginx.com if (nxt_slow_path(entry == NULL)) { 863*16Svbart@nginx.com return NULL; 864*16Svbart@nginx.com } 865*16Svbart@nginx.com 866*16Svbart@nginx.com hash->entries[length - min_length] = entry; 867*16Svbart@nginx.com 868*16Svbart@nginx.com for (j = i; fields[j].handler != NULL; j++) { 869*16Svbart@nginx.com if (length != fields[j].name.length) { 870*16Svbart@nginx.com continue; 871*16Svbart@nginx.com } 872*16Svbart@nginx.com 873*16Svbart@nginx.com entry->handler = fields[j].handler; 874*16Svbart@nginx.com entry->data = fields[j].data; 875*16Svbart@nginx.com 876*16Svbart@nginx.com nxt_memcpy_lowcase(entry->key->str, fields[j].name.start, length); 877*16Svbart@nginx.com 878*16Svbart@nginx.com n--; 879*16Svbart@nginx.com 880*16Svbart@nginx.com if (n == 0) { 881*16Svbart@nginx.com break; 882*16Svbart@nginx.com } 883*16Svbart@nginx.com 884*16Svbart@nginx.com entry = (nxt_http_fields_hash_entry_t *) ((u_char *) entry + size); 885*16Svbart@nginx.com } 886*16Svbart@nginx.com } 887*16Svbart@nginx.com 888*16Svbart@nginx.com return hash; 889*16Svbart@nginx.com } 890