miniwget.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  1. /* $Id: miniwget.c,v 1.78 2018/03/13 23:22:18 nanard Exp $ */
  2. /* Project : miniupnp
  3. * Website : http://miniupnp.free.fr/
  4. * Author : Thomas Bernard
  5. * Copyright (c) 2005-2018 Thomas Bernard
  6. * This software is subject to the conditions detailed in the
  7. * LICENCE file provided in this distribution. */
  8. #include <stdio.h>
  9. #include <stdlib.h>
  10. #include <string.h>
  11. #include <ctype.h>
  12. #ifdef _WIN32
  13. #include <winsock2.h>
  14. #include <ws2tcpip.h>
  15. #include <io.h>
  16. #define MAXHOSTNAMELEN 64
  17. #define snprintf _snprintf
  18. #define socklen_t int
  19. #ifndef strncasecmp
  20. #if defined(_MSC_VER) && (_MSC_VER >= 1400)
  21. #define strncasecmp _memicmp
  22. #else /* defined(_MSC_VER) && (_MSC_VER >= 1400) */
  23. #define strncasecmp memicmp
  24. #endif /* defined(_MSC_VER) && (_MSC_VER >= 1400) */
  25. #endif /* #ifndef strncasecmp */
  26. #else /* #ifdef _WIN32 */
  27. #include <unistd.h>
  28. #include <sys/param.h>
  29. #if defined(__amigaos__) && !defined(__amigaos4__)
  30. #define socklen_t int
  31. #else /* #if defined(__amigaos__) && !defined(__amigaos4__) */
  32. #include <sys/select.h>
  33. #endif /* #else defined(__amigaos__) && !defined(__amigaos4__) */
  34. #include <sys/socket.h>
  35. #include <netinet/in.h>
  36. #include <arpa/inet.h>
  37. #include <net/if.h>
  38. #include <netdb.h>
  39. #define closesocket close
  40. #include <strings.h>
  41. #endif /* #else _WIN32 */
  42. #ifdef __GNU__
  43. #define MAXHOSTNAMELEN 64
  44. #endif /* __GNU__ */
  45. #ifndef MIN
  46. #define MIN(x,y) (((x)<(y))?(x):(y))
  47. #endif /* MIN */
  48. #include "miniupnpcstrings.h"
  49. #include "miniwget.h"
  50. #include "connecthostport.h"
  51. #include "receivedata.h"
  52. #ifndef MAXHOSTNAMELEN
  53. #define MAXHOSTNAMELEN 64
  54. #endif
  55. /*
  56. * Read a HTTP response from a socket.
  57. * Process Content-Length and Transfer-encoding headers.
  58. * return a pointer to the content buffer, which length is saved
  59. * to the length parameter.
  60. */
  61. void *
  62. getHTTPResponse(SOCKET s, int * size, int * status_code)
  63. {
  64. char buf[2048];
  65. int n;
  66. int endofheaders = 0;
  67. int chunked = 0;
  68. int content_length = -1;
  69. unsigned int chunksize = 0;
  70. unsigned int bytestocopy = 0;
  71. /* buffers : */
  72. char * header_buf;
  73. unsigned int header_buf_len = 2048;
  74. unsigned int header_buf_used = 0;
  75. char * content_buf;
  76. unsigned int content_buf_len = 2048;
  77. unsigned int content_buf_used = 0;
  78. char chunksize_buf[32];
  79. unsigned int chunksize_buf_index;
  80. #ifdef DEBUG
  81. char * reason_phrase = NULL;
  82. int reason_phrase_len = 0;
  83. #endif
  84. if(status_code) *status_code = -1;
  85. header_buf = malloc(header_buf_len);
  86. if(header_buf == NULL)
  87. {
  88. #ifdef DEBUG
  89. fprintf(stderr, "%s: Memory allocation error\n", "getHTTPResponse");
  90. #endif /* DEBUG */
  91. *size = -1;
  92. return NULL;
  93. }
  94. content_buf = malloc(content_buf_len);
  95. if(content_buf == NULL)
  96. {
  97. free(header_buf);
  98. #ifdef DEBUG
  99. fprintf(stderr, "%s: Memory allocation error\n", "getHTTPResponse");
  100. #endif /* DEBUG */
  101. *size = -1;
  102. return NULL;
  103. }
  104. chunksize_buf[0] = '\0';
  105. chunksize_buf_index = 0;
  106. while((n = receivedata(s, buf, sizeof(buf), 5000, NULL)) > 0)
  107. {
  108. if(endofheaders == 0)
  109. {
  110. int i;
  111. int linestart=0;
  112. int colon=0;
  113. int valuestart=0;
  114. if(header_buf_used + n > header_buf_len) {
  115. char * tmp = realloc(header_buf, header_buf_used + n);
  116. if(tmp == NULL) {
  117. /* memory allocation error */
  118. free(header_buf);
  119. free(content_buf);
  120. *size = -1;
  121. return NULL;
  122. }
  123. header_buf = tmp;
  124. header_buf_len = header_buf_used + n;
  125. }
  126. memcpy(header_buf + header_buf_used, buf, n);
  127. header_buf_used += n;
  128. /* search for CR LF CR LF (end of headers)
  129. * recognize also LF LF */
  130. i = 0;
  131. while(i < ((int)header_buf_used-1) && (endofheaders == 0)) {
  132. if(header_buf[i] == '\r') {
  133. i++;
  134. if(header_buf[i] == '\n') {
  135. i++;
  136. if(i < (int)header_buf_used && header_buf[i] == '\r') {
  137. i++;
  138. if(i < (int)header_buf_used && header_buf[i] == '\n') {
  139. endofheaders = i+1;
  140. }
  141. }
  142. }
  143. } else if(header_buf[i] == '\n') {
  144. i++;
  145. if(header_buf[i] == '\n') {
  146. endofheaders = i+1;
  147. }
  148. }
  149. i++;
  150. }
  151. if(endofheaders == 0)
  152. continue;
  153. /* parse header lines */
  154. for(i = 0; i < endofheaders - 1; i++) {
  155. if(linestart > 0 && colon <= linestart && header_buf[i]==':')
  156. {
  157. colon = i;
  158. while(i < (endofheaders-1)
  159. && (header_buf[i+1] == ' ' || header_buf[i+1] == '\t'))
  160. i++;
  161. valuestart = i + 1;
  162. }
  163. /* detecting end of line */
  164. else if(header_buf[i]=='\r' || header_buf[i]=='\n')
  165. {
  166. if(linestart == 0 && status_code)
  167. {
  168. /* Status line
  169. * HTTP-Version SP Status-Code SP Reason-Phrase CRLF */
  170. int sp;
  171. for(sp = 0; sp < i; sp++)
  172. if(header_buf[sp] == ' ')
  173. {
  174. if(*status_code < 0)
  175. *status_code = atoi(header_buf + sp + 1);
  176. else
  177. {
  178. #ifdef DEBUG
  179. reason_phrase = header_buf + sp + 1;
  180. reason_phrase_len = i - sp - 1;
  181. #endif
  182. break;
  183. }
  184. }
  185. #ifdef DEBUG
  186. printf("HTTP status code = %d, Reason phrase = %.*s\n",
  187. *status_code, reason_phrase_len, reason_phrase);
  188. #endif
  189. }
  190. else if(colon > linestart && valuestart > colon)
  191. {
  192. #ifdef DEBUG
  193. printf("header='%.*s', value='%.*s'\n",
  194. colon-linestart, header_buf+linestart,
  195. i-valuestart, header_buf+valuestart);
  196. #endif
  197. if(0==strncasecmp(header_buf+linestart, "content-length", colon-linestart))
  198. {
  199. content_length = atoi(header_buf+valuestart);
  200. #ifdef DEBUG
  201. printf("Content-Length: %d\n", content_length);
  202. #endif
  203. }
  204. else if(0==strncasecmp(header_buf+linestart, "transfer-encoding", colon-linestart)
  205. && 0==strncasecmp(header_buf+valuestart, "chunked", 7))
  206. {
  207. #ifdef DEBUG
  208. printf("chunked transfer-encoding!\n");
  209. #endif
  210. chunked = 1;
  211. }
  212. }
  213. while((i < (int)header_buf_used) && (header_buf[i]=='\r' || header_buf[i] == '\n'))
  214. i++;
  215. linestart = i;
  216. colon = linestart;
  217. valuestart = 0;
  218. }
  219. }
  220. /* copy the remaining of the received data back to buf */
  221. n = header_buf_used - endofheaders;
  222. memcpy(buf, header_buf + endofheaders, n);
  223. /* if(headers) */
  224. }
  225. /* if we get there, endofheaders != 0.
  226. * In the other case, there was a continue above */
  227. /* content */
  228. if(chunked)
  229. {
  230. int i = 0;
  231. while(i < n)
  232. {
  233. if(chunksize == 0)
  234. {
  235. /* reading chunk size */
  236. if(chunksize_buf_index == 0) {
  237. /* skipping any leading CR LF */
  238. if(i<n && buf[i] == '\r') i++;
  239. if(i<n && buf[i] == '\n') i++;
  240. }
  241. while(i<n && isxdigit(buf[i])
  242. && chunksize_buf_index < (sizeof(chunksize_buf)-1))
  243. {
  244. chunksize_buf[chunksize_buf_index++] = buf[i];
  245. chunksize_buf[chunksize_buf_index] = '\0';
  246. i++;
  247. }
  248. while(i<n && buf[i] != '\r' && buf[i] != '\n')
  249. i++; /* discarding chunk-extension */
  250. if(i<n && buf[i] == '\r') i++;
  251. if(i<n && buf[i] == '\n') {
  252. unsigned int j;
  253. for(j = 0; j < chunksize_buf_index; j++) {
  254. if(chunksize_buf[j] >= '0'
  255. && chunksize_buf[j] <= '9')
  256. chunksize = (chunksize << 4) + (chunksize_buf[j] - '0');
  257. else
  258. chunksize = (chunksize << 4) + ((chunksize_buf[j] | 32) - 'a' + 10);
  259. }
  260. chunksize_buf[0] = '\0';
  261. chunksize_buf_index = 0;
  262. i++;
  263. } else {
  264. /* not finished to get chunksize */
  265. continue;
  266. }
  267. #ifdef DEBUG
  268. printf("chunksize = %u (%x)\n", chunksize, chunksize);
  269. #endif
  270. if(chunksize == 0)
  271. {
  272. #ifdef DEBUG
  273. printf("end of HTTP content - %d %d\n", i, n);
  274. /*printf("'%.*s'\n", n-i, buf+i);*/
  275. #endif
  276. goto end_of_stream;
  277. }
  278. }
  279. /* it is guaranteed that (n >= i) */
  280. bytestocopy = (chunksize < (unsigned int)(n - i))?chunksize:(unsigned int)(n - i);
  281. if((content_buf_used + bytestocopy) > content_buf_len)
  282. {
  283. char * tmp;
  284. if((content_length >= 0) && ((unsigned int)content_length >= (content_buf_used + bytestocopy))) {
  285. content_buf_len = content_length;
  286. } else {
  287. content_buf_len = content_buf_used + bytestocopy;
  288. }
  289. tmp = realloc(content_buf, content_buf_len);
  290. if(tmp == NULL) {
  291. /* memory allocation error */
  292. free(content_buf);
  293. free(header_buf);
  294. *size = -1;
  295. return NULL;
  296. }
  297. content_buf = tmp;
  298. }
  299. memcpy(content_buf + content_buf_used, buf + i, bytestocopy);
  300. content_buf_used += bytestocopy;
  301. i += bytestocopy;
  302. chunksize -= bytestocopy;
  303. }
  304. }
  305. else
  306. {
  307. /* not chunked */
  308. if(content_length > 0
  309. && (content_buf_used + n) > (unsigned int)content_length) {
  310. /* skipping additional bytes */
  311. n = content_length - content_buf_used;
  312. }
  313. if(content_buf_used + n > content_buf_len)
  314. {
  315. char * tmp;
  316. if(content_length >= 0
  317. && (unsigned int)content_length >= (content_buf_used + n)) {
  318. content_buf_len = content_length;
  319. } else {
  320. content_buf_len = content_buf_used + n;
  321. }
  322. tmp = realloc(content_buf, content_buf_len);
  323. if(tmp == NULL) {
  324. /* memory allocation error */
  325. free(content_buf);
  326. free(header_buf);
  327. *size = -1;
  328. return NULL;
  329. }
  330. content_buf = tmp;
  331. }
  332. memcpy(content_buf + content_buf_used, buf, n);
  333. content_buf_used += n;
  334. }
  335. /* use the Content-Length header value if available */
  336. if(content_length > 0 && content_buf_used >= (unsigned int)content_length)
  337. {
  338. #ifdef DEBUG
  339. printf("End of HTTP content\n");
  340. #endif
  341. break;
  342. }
  343. }
  344. end_of_stream:
  345. free(header_buf); header_buf = NULL;
  346. *size = content_buf_used;
  347. if(content_buf_used == 0)
  348. {
  349. free(content_buf);
  350. content_buf = NULL;
  351. }
  352. return content_buf;
  353. }
  354. /* miniwget3() :
  355. * do all the work.
  356. * Return NULL if something failed. */
  357. static void *
  358. miniwget3(const char * host,
  359. unsigned short port, const char * path,
  360. int * size, char * addr_str, int addr_str_len,
  361. const char * httpversion, unsigned int scope_id,
  362. int * status_code)
  363. {
  364. char buf[2048];
  365. SOCKET s;
  366. int n;
  367. int len;
  368. int sent;
  369. void * content;
  370. *size = 0;
  371. s = connecthostport(host, port, scope_id);
  372. if(ISINVALID(s))
  373. return NULL;
  374. /* get address for caller ! */
  375. if(addr_str)
  376. {
  377. struct sockaddr_storage saddr;
  378. socklen_t saddrlen;
  379. saddrlen = sizeof(saddr);
  380. if(getsockname(s, (struct sockaddr *)&saddr, &saddrlen) < 0)
  381. {
  382. perror("getsockname");
  383. }
  384. else
  385. {
  386. #if defined(__amigaos__) && !defined(__amigaos4__)
  387. /* using INT WINAPI WSAAddressToStringA(LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFOA, LPSTR, LPDWORD);
  388. * But his function make a string with the port : nn.nn.nn.nn:port */
  389. /* if(WSAAddressToStringA((SOCKADDR *)&saddr, sizeof(saddr),
  390. NULL, addr_str, (DWORD *)&addr_str_len))
  391. {
  392. printf("WSAAddressToStringA() failed : %d\n", WSAGetLastError());
  393. }*/
  394. /* the following code is only compatible with ip v4 addresses */
  395. strncpy(addr_str, inet_ntoa(((struct sockaddr_in *)&saddr)->sin_addr), addr_str_len);
  396. #else
  397. #if 0
  398. if(saddr.sa_family == AF_INET6) {
  399. inet_ntop(AF_INET6,
  400. &(((struct sockaddr_in6 *)&saddr)->sin6_addr),
  401. addr_str, addr_str_len);
  402. } else {
  403. inet_ntop(AF_INET,
  404. &(((struct sockaddr_in *)&saddr)->sin_addr),
  405. addr_str, addr_str_len);
  406. }
  407. #endif
  408. /* getnameinfo return ip v6 address with the scope identifier
  409. * such as : 2a01:e35:8b2b:7330::%4281128194 */
  410. n = getnameinfo((const struct sockaddr *)&saddr, saddrlen,
  411. addr_str, addr_str_len,
  412. NULL, 0,
  413. NI_NUMERICHOST | NI_NUMERICSERV);
  414. if(n != 0) {
  415. #ifdef _WIN32
  416. fprintf(stderr, "getnameinfo() failed : %d\n", n);
  417. #else
  418. fprintf(stderr, "getnameinfo() failed : %s\n", gai_strerror(n));
  419. #endif
  420. }
  421. #endif
  422. }
  423. #ifdef DEBUG
  424. printf("address miniwget : %s\n", addr_str);
  425. #endif
  426. }
  427. len = snprintf(buf, sizeof(buf),
  428. "GET %s HTTP/%s\r\n"
  429. "Host: %s:%d\r\n"
  430. "Connection: Close\r\n"
  431. "User-Agent: " OS_STRING ", " UPNP_VERSION_STRING ", MiniUPnPc/" MINIUPNPC_VERSION_STRING "\r\n"
  432. "\r\n",
  433. path, httpversion, host, port);
  434. if ((unsigned int)len >= sizeof(buf))
  435. {
  436. closesocket(s);
  437. return NULL;
  438. }
  439. sent = 0;
  440. /* sending the HTTP request */
  441. while(sent < len)
  442. {
  443. n = send(s, buf+sent, len-sent, 0);
  444. if(n < 0)
  445. {
  446. perror("send");
  447. closesocket(s);
  448. return NULL;
  449. }
  450. else
  451. {
  452. sent += n;
  453. }
  454. }
  455. content = getHTTPResponse(s, size, status_code);
  456. closesocket(s);
  457. return content;
  458. }
  459. /* miniwget2() :
  460. * Call miniwget3(); retry with HTTP/1.1 if 1.0 fails. */
  461. static void *
  462. miniwget2(const char * host,
  463. unsigned short port, const char * path,
  464. int * size, char * addr_str, int addr_str_len,
  465. unsigned int scope_id, int * status_code)
  466. {
  467. char * respbuffer;
  468. #if 1
  469. respbuffer = miniwget3(host, port, path, size,
  470. addr_str, addr_str_len, "1.1",
  471. scope_id, status_code);
  472. #else
  473. respbuffer = miniwget3(host, port, path, size,
  474. addr_str, addr_str_len, "1.0",
  475. scope_id, status_code);
  476. if (*size == 0)
  477. {
  478. #ifdef DEBUG
  479. printf("Retrying with HTTP/1.1\n");
  480. #endif
  481. free(respbuffer);
  482. respbuffer = miniwget3(host, port, path, size,
  483. addr_str, addr_str_len, "1.1",
  484. scope_id, status_code);
  485. }
  486. #endif
  487. return respbuffer;
  488. }
  489. /* parseURL()
  490. * arguments :
  491. * url : source string not modified
  492. * hostname : hostname destination string (size of MAXHOSTNAMELEN+1)
  493. * port : port (destination)
  494. * path : pointer to the path part of the URL
  495. *
  496. * Return values :
  497. * 0 - Failure
  498. * 1 - Success */
  499. int
  500. parseURL(const char * url,
  501. char * hostname, unsigned short * port,
  502. char * * path, unsigned int * scope_id)
  503. {
  504. char * p1, *p2, *p3;
  505. if(!url)
  506. return 0;
  507. p1 = strstr(url, "://");
  508. if(!p1)
  509. return 0;
  510. p1 += 3;
  511. if( (url[0]!='h') || (url[1]!='t')
  512. ||(url[2]!='t') || (url[3]!='p'))
  513. return 0;
  514. memset(hostname, 0, MAXHOSTNAMELEN + 1);
  515. if(*p1 == '[')
  516. {
  517. /* IP v6 : http://[2a00:1450:8002::6a]/path/abc */
  518. char * scope;
  519. scope = strchr(p1, '%');
  520. p2 = strchr(p1, ']');
  521. if(p2 && scope && scope < p2 && scope_id) {
  522. /* parse scope */
  523. #ifdef IF_NAMESIZE
  524. char tmp[IF_NAMESIZE];
  525. int l;
  526. scope++;
  527. /* "%25" is just '%' in URL encoding */
  528. if(scope[0] == '2' && scope[1] == '5')
  529. scope += 2; /* skip "25" */
  530. l = p2 - scope;
  531. if(l >= IF_NAMESIZE)
  532. l = IF_NAMESIZE - 1;
  533. memcpy(tmp, scope, l);
  534. tmp[l] = '\0';
  535. *scope_id = if_nametoindex(tmp);
  536. if(*scope_id == 0) {
  537. *scope_id = (unsigned int)strtoul(tmp, NULL, 10);
  538. }
  539. #else
  540. /* under windows, scope is numerical */
  541. char tmp[8];
  542. int l;
  543. scope++;
  544. /* "%25" is just '%' in URL encoding */
  545. if(scope[0] == '2' && scope[1] == '5')
  546. scope += 2; /* skip "25" */
  547. l = p2 - scope;
  548. if(l >= sizeof(tmp))
  549. l = sizeof(tmp) - 1;
  550. memcpy(tmp, scope, l);
  551. tmp[l] = '\0';
  552. *scope_id = (unsigned int)strtoul(tmp, NULL, 10);
  553. #endif
  554. }
  555. p3 = strchr(p1, '/');
  556. if(p2 && p3)
  557. {
  558. p2++;
  559. strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1)));
  560. if(*p2 == ':')
  561. {
  562. *port = 0;
  563. p2++;
  564. while( (*p2 >= '0') && (*p2 <= '9'))
  565. {
  566. *port *= 10;
  567. *port += (unsigned short)(*p2 - '0');
  568. p2++;
  569. }
  570. }
  571. else
  572. {
  573. *port = 80;
  574. }
  575. *path = p3;
  576. return 1;
  577. }
  578. }
  579. p2 = strchr(p1, ':');
  580. p3 = strchr(p1, '/');
  581. if(!p3)
  582. return 0;
  583. if(!p2 || (p2>p3))
  584. {
  585. strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p3-p1)));
  586. *port = 80;
  587. }
  588. else
  589. {
  590. strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1)));
  591. *port = 0;
  592. p2++;
  593. while( (*p2 >= '0') && (*p2 <= '9'))
  594. {
  595. *port *= 10;
  596. *port += (unsigned short)(*p2 - '0');
  597. p2++;
  598. }
  599. }
  600. *path = p3;
  601. return 1;
  602. }
  603. void *
  604. miniwget(const char * url, int * size,
  605. unsigned int scope_id, int * status_code)
  606. {
  607. unsigned short port;
  608. char * path;
  609. /* protocol://host:port/chemin */
  610. char hostname[MAXHOSTNAMELEN+1];
  611. *size = 0;
  612. if(!parseURL(url, hostname, &port, &path, &scope_id))
  613. return NULL;
  614. #ifdef DEBUG
  615. printf("parsed url : hostname='%s' port=%hu path='%s' scope_id=%u\n",
  616. hostname, port, path, scope_id);
  617. #endif
  618. return miniwget2(hostname, port, path, size, 0, 0, scope_id, status_code);
  619. }
  620. void *
  621. miniwget_getaddr(const char * url, int * size,
  622. char * addr, int addrlen, unsigned int scope_id,
  623. int * status_code)
  624. {
  625. unsigned short port;
  626. char * path;
  627. /* protocol://host:port/path */
  628. char hostname[MAXHOSTNAMELEN+1];
  629. *size = 0;
  630. if(addr)
  631. addr[0] = '\0';
  632. if(!parseURL(url, hostname, &port, &path, &scope_id))
  633. return NULL;
  634. #ifdef DEBUG
  635. printf("parsed url : hostname='%s' port=%hu path='%s' scope_id=%u\n",
  636. hostname, port, path, scope_id);
  637. #endif
  638. return miniwget2(hostname, port, path, size, addr, addrlen, scope_id, status_code);
  639. }