test_delete.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. /*
  2. This file is part of libmicrohttpd
  3. Copyright (C) 2007, 2016 Christian Grothoff
  4. Copyright (C) 2014-2022 Evgeny Grin (Karlson2k)
  5. libmicrohttpd is free software; you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published
  7. by the Free Software Foundation; either version 2, or (at your
  8. option) any later version.
  9. libmicrohttpd is distributed in the hope that it will be useful, but
  10. WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with libmicrohttpd; see the file COPYING. If not, write to the
  15. Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  16. Boston, MA 02110-1301, USA.
  17. */
  18. /**
  19. * @file daemontest_delete.c
  20. * @brief Testcase for libmicrohttpd DELETE operations
  21. * @author Christian Grothoff
  22. * @author Karlson2k (Evgeny Grin)
  23. */
  24. #include "MHD_config.h"
  25. #include "platform.h"
  26. #include <curl/curl.h>
  27. #include <microhttpd.h>
  28. #include <stdlib.h>
  29. #include <string.h>
  30. #include <time.h>
  31. #include <errno.h>
  32. #include "mhd_has_in_name.h"
  33. #ifndef WINDOWS
  34. #include <unistd.h>
  35. #endif
  36. #if defined(MHD_CPU_COUNT) && (MHD_CPU_COUNT + 0) < 2
  37. #undef MHD_CPU_COUNT
  38. #endif
  39. #if ! defined(MHD_CPU_COUNT)
  40. #define MHD_CPU_COUNT 2
  41. #endif
  42. static int oneone;
  43. struct CBC
  44. {
  45. char *buf;
  46. size_t pos;
  47. size_t size;
  48. };
  49. static size_t
  50. putBuffer (void *stream, size_t size, size_t nmemb, void *ptr)
  51. {
  52. size_t *pos = ptr;
  53. size_t wrt;
  54. wrt = size * nmemb;
  55. if (wrt > 8 - (*pos))
  56. wrt = 8 - (*pos);
  57. memcpy (stream, &("Hello123"[*pos]), wrt);
  58. (*pos) += wrt;
  59. return wrt;
  60. }
  61. static size_t
  62. copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
  63. {
  64. struct CBC *cbc = ctx;
  65. if (cbc->pos + size * nmemb > cbc->size)
  66. return 0; /* overflow */
  67. memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
  68. cbc->pos += size * nmemb;
  69. return size * nmemb;
  70. }
  71. static enum MHD_Result
  72. ahc_echo (void *cls,
  73. struct MHD_Connection *connection,
  74. const char *url,
  75. const char *method,
  76. const char *version,
  77. const char *upload_data, size_t *upload_data_size,
  78. void **req_cls)
  79. {
  80. int *done = cls;
  81. struct MHD_Response *response;
  82. enum MHD_Result ret;
  83. (void) version; (void) req_cls; /* Unused. Silent compiler warning. */
  84. if (0 != strcmp ("DELETE", method))
  85. return MHD_NO; /* unexpected method */
  86. if ((*done) == 0)
  87. {
  88. if (*upload_data_size != 8)
  89. return MHD_YES; /* not yet ready */
  90. if (0 == memcmp (upload_data, "Hello123", 8))
  91. {
  92. *upload_data_size = 0;
  93. }
  94. else
  95. {
  96. printf ("Invalid upload data `%8s'!\n", upload_data);
  97. return MHD_NO;
  98. }
  99. *done = 1;
  100. return MHD_YES;
  101. }
  102. response = MHD_create_response_from_buffer_copy (strlen (url),
  103. (const void *) url);
  104. ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
  105. MHD_destroy_response (response);
  106. return ret;
  107. }
  108. static unsigned int
  109. testInternalDelete (void)
  110. {
  111. struct MHD_Daemon *d;
  112. CURL *c;
  113. char buf[2048];
  114. struct CBC cbc;
  115. size_t pos = 0;
  116. int done_flag = 0;
  117. CURLcode errornum;
  118. uint16_t port;
  119. if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT))
  120. port = 0;
  121. else
  122. port = 1152;
  123. cbc.buf = buf;
  124. cbc.size = 2048;
  125. cbc.pos = 0;
  126. d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG,
  127. port,
  128. NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
  129. if (d == NULL)
  130. return 1;
  131. if (0 == port)
  132. {
  133. const union MHD_DaemonInfo *dinfo;
  134. dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT);
  135. if ((NULL == dinfo) || (0 == dinfo->port) )
  136. {
  137. MHD_stop_daemon (d); return 32;
  138. }
  139. port = dinfo->port;
  140. }
  141. c = curl_easy_init ();
  142. curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/hello_world");
  143. curl_easy_setopt (c, CURLOPT_PORT, (long) port);
  144. curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
  145. curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
  146. curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
  147. curl_easy_setopt (c, CURLOPT_READDATA, &pos);
  148. curl_easy_setopt (c, CURLOPT_CUSTOMREQUEST, "DELETE");
  149. curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
  150. curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
  151. curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L);
  152. curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
  153. if (oneone)
  154. curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  155. else
  156. curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
  157. curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
  158. /* NOTE: use of CONNECTTIMEOUT without also
  159. * setting NOSIGNAL results in really weird
  160. * crashes on my system! */
  161. curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L);
  162. if (CURLE_OK != (errornum = curl_easy_perform (c)))
  163. {
  164. fprintf (stderr,
  165. "curl_easy_perform failed: `%s'\n",
  166. curl_easy_strerror (errornum));
  167. curl_easy_cleanup (c);
  168. MHD_stop_daemon (d);
  169. return 2;
  170. }
  171. curl_easy_cleanup (c);
  172. MHD_stop_daemon (d);
  173. if (cbc.pos != strlen ("/hello_world"))
  174. return 4;
  175. if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
  176. return 8;
  177. return 0;
  178. }
  179. static unsigned int
  180. testMultithreadedDelete (void)
  181. {
  182. struct MHD_Daemon *d;
  183. CURL *c;
  184. char buf[2048];
  185. struct CBC cbc;
  186. size_t pos = 0;
  187. int done_flag = 0;
  188. CURLcode errornum;
  189. uint16_t port;
  190. if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT))
  191. port = 0;
  192. else
  193. port = 1153;
  194. cbc.buf = buf;
  195. cbc.size = 2048;
  196. cbc.pos = 0;
  197. d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION
  198. | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG,
  199. port,
  200. NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
  201. if (d == NULL)
  202. return 16;
  203. if (0 == port)
  204. {
  205. const union MHD_DaemonInfo *dinfo;
  206. dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT);
  207. if ((NULL == dinfo) || (0 == dinfo->port) )
  208. {
  209. MHD_stop_daemon (d); return 32;
  210. }
  211. port = dinfo->port;
  212. }
  213. c = curl_easy_init ();
  214. curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/hello_world");
  215. curl_easy_setopt (c, CURLOPT_PORT, (long) port);
  216. curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
  217. curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
  218. curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
  219. curl_easy_setopt (c, CURLOPT_READDATA, &pos);
  220. curl_easy_setopt (c, CURLOPT_CUSTOMREQUEST, "DELETE");
  221. curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
  222. curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
  223. curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L);
  224. curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
  225. if (oneone)
  226. curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  227. else
  228. curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
  229. curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
  230. /* NOTE: use of CONNECTTIMEOUT without also
  231. * setting NOSIGNAL results in really weird
  232. * crashes on my system! */
  233. curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L);
  234. if (CURLE_OK != (errornum = curl_easy_perform (c)))
  235. {
  236. fprintf (stderr,
  237. "curl_easy_perform failed: `%s'\n",
  238. curl_easy_strerror (errornum));
  239. curl_easy_cleanup (c);
  240. MHD_stop_daemon (d);
  241. return 32;
  242. }
  243. curl_easy_cleanup (c);
  244. MHD_stop_daemon (d);
  245. if (cbc.pos != strlen ("/hello_world"))
  246. return 64;
  247. if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
  248. return 128;
  249. return 0;
  250. }
  251. static unsigned int
  252. testMultithreadedPoolDelete (void)
  253. {
  254. struct MHD_Daemon *d;
  255. CURL *c;
  256. char buf[2048];
  257. struct CBC cbc;
  258. size_t pos = 0;
  259. int done_flag = 0;
  260. CURLcode errornum;
  261. uint16_t port;
  262. if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT))
  263. port = 0;
  264. else
  265. port = 1154;
  266. cbc.buf = buf;
  267. cbc.size = 2048;
  268. cbc.pos = 0;
  269. d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG,
  270. port,
  271. NULL, NULL, &ahc_echo, &done_flag,
  272. MHD_OPTION_THREAD_POOL_SIZE, MHD_CPU_COUNT,
  273. MHD_OPTION_END);
  274. if (d == NULL)
  275. return 16;
  276. if (0 == port)
  277. {
  278. const union MHD_DaemonInfo *dinfo;
  279. dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT);
  280. if ((NULL == dinfo) || (0 == dinfo->port) )
  281. {
  282. MHD_stop_daemon (d); return 32;
  283. }
  284. port = dinfo->port;
  285. }
  286. c = curl_easy_init ();
  287. curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/hello_world");
  288. curl_easy_setopt (c, CURLOPT_PORT, (long) port);
  289. curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
  290. curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
  291. curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
  292. curl_easy_setopt (c, CURLOPT_READDATA, &pos);
  293. curl_easy_setopt (c, CURLOPT_CUSTOMREQUEST, "DELETE");
  294. curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
  295. curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
  296. curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L);
  297. curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
  298. if (oneone)
  299. curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  300. else
  301. curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
  302. curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
  303. /* NOTE: use of CONNECTTIMEOUT without also
  304. * setting NOSIGNAL results in really weird
  305. * crashes on my system! */
  306. curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L);
  307. if (CURLE_OK != (errornum = curl_easy_perform (c)))
  308. {
  309. fprintf (stderr,
  310. "curl_easy_perform failed: `%s'\n",
  311. curl_easy_strerror (errornum));
  312. curl_easy_cleanup (c);
  313. MHD_stop_daemon (d);
  314. return 32;
  315. }
  316. curl_easy_cleanup (c);
  317. MHD_stop_daemon (d);
  318. if (cbc.pos != strlen ("/hello_world"))
  319. return 64;
  320. if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
  321. return 128;
  322. return 0;
  323. }
  324. static unsigned int
  325. testExternalDelete (void)
  326. {
  327. struct MHD_Daemon *d;
  328. CURL *c;
  329. char buf[2048];
  330. struct CBC cbc;
  331. CURLM *multi;
  332. CURLMcode mret;
  333. fd_set rs;
  334. fd_set ws;
  335. fd_set es;
  336. MHD_socket maxsock;
  337. #ifdef MHD_WINSOCK_SOCKETS
  338. int maxposixs; /* Max socket number unused on W32 */
  339. #else /* MHD_POSIX_SOCKETS */
  340. #define maxposixs maxsock
  341. #endif /* MHD_POSIX_SOCKETS */
  342. int running;
  343. struct CURLMsg *msg;
  344. time_t start;
  345. struct timeval tv;
  346. size_t pos = 0;
  347. int done_flag = 0;
  348. uint16_t port;
  349. if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT))
  350. port = 0;
  351. else
  352. port = 1154;
  353. multi = NULL;
  354. cbc.buf = buf;
  355. cbc.size = 2048;
  356. cbc.pos = 0;
  357. d = MHD_start_daemon (MHD_USE_ERROR_LOG | MHD_USE_NO_THREAD_SAFETY,
  358. port,
  359. NULL, NULL, &ahc_echo, &done_flag,
  360. MHD_OPTION_APP_FD_SETSIZE, (int) FD_SETSIZE,
  361. MHD_OPTION_END);
  362. if (d == NULL)
  363. return 256;
  364. if (0 == port)
  365. {
  366. const union MHD_DaemonInfo *dinfo;
  367. dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT);
  368. if ((NULL == dinfo) || (0 == dinfo->port) )
  369. {
  370. MHD_stop_daemon (d); return 32;
  371. }
  372. port = dinfo->port;
  373. }
  374. c = curl_easy_init ();
  375. curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/hello_world");
  376. curl_easy_setopt (c, CURLOPT_PORT, (long) port);
  377. curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
  378. curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
  379. curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
  380. curl_easy_setopt (c, CURLOPT_READDATA, &pos);
  381. curl_easy_setopt (c, CURLOPT_CUSTOMREQUEST, "DELETE");
  382. curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
  383. curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
  384. curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L);
  385. curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
  386. if (oneone)
  387. curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  388. else
  389. curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
  390. curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
  391. /* NOTE: use of CONNECTTIMEOUT without also
  392. * setting NOSIGNAL results in really weird
  393. * crashes on my system! */
  394. curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L);
  395. multi = curl_multi_init ();
  396. if (multi == NULL)
  397. {
  398. curl_easy_cleanup (c);
  399. MHD_stop_daemon (d);
  400. return 512;
  401. }
  402. mret = curl_multi_add_handle (multi, c);
  403. if (mret != CURLM_OK)
  404. {
  405. curl_multi_cleanup (multi);
  406. curl_easy_cleanup (c);
  407. MHD_stop_daemon (d);
  408. return 1024;
  409. }
  410. start = time (NULL);
  411. while ((time (NULL) - start < 5) && (multi != NULL))
  412. {
  413. maxsock = MHD_INVALID_SOCKET;
  414. maxposixs = -1;
  415. FD_ZERO (&rs);
  416. FD_ZERO (&ws);
  417. FD_ZERO (&es);
  418. curl_multi_perform (multi, &running);
  419. mret = curl_multi_fdset (multi, &rs, &ws, &es, &maxposixs);
  420. if (mret != CURLM_OK)
  421. {
  422. curl_multi_remove_handle (multi, c);
  423. curl_multi_cleanup (multi);
  424. curl_easy_cleanup (c);
  425. MHD_stop_daemon (d);
  426. return 2048;
  427. }
  428. if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &maxsock))
  429. {
  430. curl_multi_remove_handle (multi, c);
  431. curl_multi_cleanup (multi);
  432. curl_easy_cleanup (c);
  433. MHD_stop_daemon (d);
  434. return 4096;
  435. }
  436. tv.tv_sec = 0;
  437. tv.tv_usec = 1000;
  438. if (-1 == select (maxposixs + 1, &rs, &ws, &es, &tv))
  439. {
  440. #ifdef MHD_POSIX_SOCKETS
  441. if (EINTR != errno)
  442. {
  443. fprintf (stderr, "Unexpected select() error: %d. Line: %d\n",
  444. (int) errno, __LINE__);
  445. fflush (stderr);
  446. exit (99);
  447. }
  448. #else
  449. if ((WSAEINVAL != WSAGetLastError ()) ||
  450. (0 != rs.fd_count) || (0 != ws.fd_count) || (0 != es.fd_count) )
  451. {
  452. fprintf (stderr, "Unexpected select() error: %d. Line: %d\n",
  453. (int) WSAGetLastError (), __LINE__);
  454. fflush (stderr);
  455. exit (99);
  456. }
  457. Sleep (1);
  458. #endif
  459. }
  460. curl_multi_perform (multi, &running);
  461. if (0 == running)
  462. {
  463. int pending;
  464. int curl_fine = 0;
  465. while (NULL != (msg = curl_multi_info_read (multi, &pending)))
  466. {
  467. if (msg->msg == CURLMSG_DONE)
  468. {
  469. if (msg->data.result == CURLE_OK)
  470. curl_fine = 1;
  471. else
  472. {
  473. fprintf (stderr,
  474. "%s failed at %s:%d: `%s'\n",
  475. "curl_multi_perform",
  476. __FILE__,
  477. __LINE__, curl_easy_strerror (msg->data.result));
  478. abort ();
  479. }
  480. }
  481. }
  482. if (! curl_fine)
  483. {
  484. fprintf (stderr, "libcurl haven't returned OK code\n");
  485. abort ();
  486. }
  487. curl_multi_remove_handle (multi, c);
  488. curl_multi_cleanup (multi);
  489. curl_easy_cleanup (c);
  490. c = NULL;
  491. multi = NULL;
  492. }
  493. MHD_run (d);
  494. }
  495. if (multi != NULL)
  496. {
  497. curl_multi_remove_handle (multi, c);
  498. curl_easy_cleanup (c);
  499. curl_multi_cleanup (multi);
  500. }
  501. MHD_stop_daemon (d);
  502. if (cbc.pos != strlen ("/hello_world"))
  503. return 8192;
  504. if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
  505. return 16384;
  506. return 0;
  507. }
  508. int
  509. main (int argc, char *const *argv)
  510. {
  511. unsigned int errorCount = 0;
  512. (void) argc; /* Unused. Silent compiler warning. */
  513. if ((NULL == argv) || (0 == argv[0]))
  514. return 99;
  515. oneone = has_in_name (argv[0], "11");
  516. if (0 != curl_global_init (CURL_GLOBAL_WIN32))
  517. return 2;
  518. if (MHD_YES == MHD_is_feature_supported (MHD_FEATURE_THREADS))
  519. {
  520. errorCount += testInternalDelete ();
  521. errorCount += testMultithreadedDelete ();
  522. errorCount += testMultithreadedPoolDelete ();
  523. }
  524. errorCount += testExternalDelete ();
  525. if (errorCount != 0)
  526. fprintf (stderr, "Error (code: %u)\n", errorCount);
  527. curl_global_cleanup ();
  528. return (0 == errorCount) ? 0 : 1; /* 0 == pass */
  529. }