post_example.pp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. (*
  2. This file is part of libmicrohttpd
  3. Copyright (C) 2011 Christian Grothoff (and other contributing authors)
  4. This library is free software; you can redistribute it and/or
  5. modify it under the terms of the GNU Lesser General Public
  6. License as published by the Free Software Foundation; either
  7. version 2.1 of the License, or (at your option) any later version.
  8. This library is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. Lesser General Public License for more details.
  12. You should have received a copy of the GNU Lesser General Public
  13. License along with this library; if not, write to the Free Software
  14. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  15. *)
  16. (**
  17. * @file post_example.pp (Original: post_example.c)
  18. * @brief example for processing POST requests using libmicrohttpd
  19. * @author Christian Grothoff / Silvio Clécio
  20. *)
  21. program post_example;
  22. {$mode objfpc}{$H+}
  23. uses
  24. SysUtils, BaseUnix, cmem, cutils, libmicrohttpd;
  25. const
  26. (**
  27. * Invalid method page.
  28. *)
  29. METHOD_ERROR = '<html><head><title>Illegal request</title></head><body>Go away.</body></html>';
  30. (**
  31. * Invalid URL page.
  32. *)
  33. NOT_FOUND_ERROR = '<html><head><title>Not found</title></head><body>Go away.</body></html>';
  34. (**
  35. * Front page. (/)
  36. *)
  37. MAIN_PAGE = '<html><head><title>Welcome</title></head><body><form action="/2" method="post">What is your name? <input type="text" name="v1" value="%s" /><input type="submit" value="Next" /></body></html>';
  38. (**
  39. * Second page. (/2)
  40. *)
  41. SECOND_PAGE = '<html><head><title>Tell me more</title></head><body><a href="/">previous</a> <form action="/S" method="post">%s, what is your job? <input type="text" name="v2" value="%s" /><input type="submit" value="Next" /></body></html>';
  42. (**
  43. * Second page (/S)
  44. *)
  45. SUBMIT_PAGE = '<html><head><title>Ready to submit?</title></head><body><form action="/F" method="post"><a href="/2">previous </a> <input type="hidden" name="DONE" value="yes" /><input type="submit" value="Submit" /></body></html>';
  46. (**
  47. * Last page.
  48. *)
  49. LAST_PAGE = '<html><head><title>Thank you</title></head><body>Thank you.</body></html>';
  50. (**
  51. * Name of our cookie.
  52. *)
  53. COOKIE_NAME = 'session';
  54. type
  55. (**
  56. * State we keep for each user/session/browser.
  57. *)
  58. PSession = ^TSession;
  59. TSession = packed record
  60. (**
  61. * We keep all sessions in a linked list.
  62. *)
  63. next: PSession;
  64. (**
  65. * Unique ID for this session.
  66. *)
  67. sid: array[0..33] of Char;
  68. (**
  69. * Reference counter giving the number of connections
  70. * currently using this session.
  71. *)
  72. rc: cint;
  73. (**
  74. * Time when this session was last active.
  75. *)
  76. start: time_t;
  77. (**
  78. * String submitted via form.
  79. *)
  80. value_1: array[0..64] of Char;
  81. (**
  82. * Another value submitted via form.
  83. *)
  84. value_2: array[0..64] of Char;
  85. end;
  86. (**
  87. * Data kept per request.
  88. *)
  89. TRequest = packed record
  90. (**
  91. * Associated session.
  92. *)
  93. session: PSession;
  94. (**
  95. * Post processor handling form data (IF this is
  96. * a POST request).
  97. *)
  98. pp: PMHD_PostProcessor;
  99. (**
  100. * URL to serve in response to this POST (if this request
  101. * was a 'POST')
  102. *)
  103. post_url: pcchar;
  104. end;
  105. PRequest = ^TRequest;
  106. var
  107. (**
  108. * Linked list of all active sessions. Yes, O(n) but a
  109. * hash table would be overkill for a simple example...
  110. *)
  111. _sessions: PSession;
  112. (**
  113. * Return the session handle for this connection, or
  114. * create one if this is a new user.
  115. *)
  116. function get_session(connection: PMHD_Connection): PSession;
  117. var
  118. ret: PSession;
  119. cookie: pcchar;
  120. begin
  121. cookie := MHD_lookup_connection_value(connection, MHD_COOKIE_KIND, COOKIE_NAME);
  122. if cookie <> nil then
  123. begin
  124. (* find existing session *)
  125. ret := _sessions;
  126. while nil <> ret do
  127. begin
  128. if StrComp(cookie, ret^.sid) = 0 then
  129. Break;
  130. ret := ret^.next;
  131. end;
  132. if nil <> ret then
  133. begin
  134. Inc(ret^.rc);
  135. Exit(ret);
  136. end;
  137. end;
  138. (* create fresh session *)
  139. ret := CAlloc(1, SizeOf(TSession));
  140. if nil = ret then
  141. begin
  142. WriteLn(stderr, 'calloc error: ', strerror(errno^));
  143. Exit(nil);
  144. end;
  145. (* not a super-secure way to generate a random session ID,
  146. but should do for a simple example... *)
  147. snprintf(ret^.sid, SizeOf(ret^.sid), '%X%X%X%X', Cardinal(rand),
  148. Cardinal(rand), Cardinal(rand), Cardinal(rand));
  149. Inc(ret^.rc);
  150. ret^.start := FpTime;
  151. ret^.next := _sessions;
  152. _sessions := ret;
  153. Result := ret;
  154. end;
  155. (**
  156. * Type of handler that generates a reply.
  157. *
  158. * @param cls content for the page (handler-specific)
  159. * @param mime mime type to use
  160. * @param session session information
  161. * @param connection connection to process
  162. * @param MHD_YES on success, MHD_NO on failure
  163. *)
  164. type
  165. TPageHandler = function(cls: Pointer; mime: Pcchar; session: PSession;
  166. connection: PMHD_Connection): LongInt; cdecl;
  167. (**
  168. * Entry we generate for each page served.
  169. *)
  170. TPage = packed record
  171. (**
  172. * Acceptable URL for this page.
  173. *)
  174. url: Pcchar;
  175. (**
  176. * Mime type to set for the page.
  177. *)
  178. mime: Pcchar;
  179. (**
  180. * Handler to call to generate response.
  181. *)
  182. handler: TPageHandler;
  183. (**
  184. * Extra argument to handler.
  185. *)
  186. handler_cls: Pcchar;
  187. end;
  188. (**
  189. * Add header to response to set a session cookie.
  190. *
  191. * @param session session to use
  192. * @param response response to modify
  193. *)
  194. procedure add_session_cookie(session: PSession; response: PMHD_Response);
  195. var
  196. cstr: array[0..256] of Char;
  197. begin
  198. snprintf(cstr, SizeOf(cstr), '%s=%s', COOKIE_NAME, session^.sid);
  199. if MHD_NO =
  200. MHD_add_response_header(response, MHD_HTTP_HEADER_SET_COOKIE, cstr) then
  201. WriteLn(stderr, 'Failed to set session cookie header!');
  202. end;
  203. (**
  204. * Handler that returns a simple static HTTP page that
  205. * is passed in via 'cls'.
  206. *
  207. * @param cls a 'const char *' with the HTML webpage to return
  208. * @param mime mime type to use
  209. * @param session session handle
  210. * @param connection connection to use
  211. *)
  212. function serve_simple_form(cls: Pointer; mime: Pcchar; session: PSession;
  213. connection: PMHD_Connection): cint; cdecl;
  214. var
  215. ret: cint;
  216. form: Pcchar;
  217. response: PMHD_Response;
  218. begin
  219. form := cls;
  220. (* return static form *)
  221. response := MHD_create_response_from_buffer(Length(form), Pointer(form),
  222. MHD_RESPMEM_PERSISTENT);
  223. add_session_cookie(session, response);
  224. MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_ENCODING, mime);
  225. ret := MHD_queue_response(connection, MHD_HTTP_OK, response);
  226. MHD_destroy_response(response);
  227. Result := ret;
  228. end;
  229. (**
  230. * Handler that adds the 'v1' value to the given HTML code.
  231. *
  232. * @param cls unused
  233. * @param mime mime type to use
  234. * @param session session handle
  235. * @param connection connection to use
  236. *)
  237. function fill_v1_form(cls: Pointer; mime: Pcchar; session: PSession;
  238. connection: PMHD_Connection): cint; cdecl;
  239. var
  240. ret: cint;
  241. reply: Pcchar;
  242. response: PMHD_Response;
  243. begin
  244. reply := Malloc(strlen(MAIN_PAGE) + strlen(session^.value_1) + 1);
  245. if nil = reply then
  246. Exit(MHD_NO);
  247. snprintf (reply, strlen(MAIN_PAGE) + strlen(session^.value_1) + 1,
  248. MAIN_PAGE, session^.value_1);
  249. (* return static form *)
  250. response := MHD_create_response_from_buffer (strlen(reply), Pointer(reply),
  251. MHD_RESPMEM_MUST_FREE);
  252. if nil = response then
  253. Exit(MHD_NO);
  254. add_session_cookie(session, response);
  255. MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_ENCODING, mime);
  256. ret := MHD_queue_response (connection, MHD_HTTP_OK, response);
  257. MHD_destroy_response(response);
  258. Result := ret;
  259. end;
  260. (**
  261. * Handler that adds the 'v1' and 'v2' values to the given HTML code.
  262. *
  263. * @param cls unused
  264. * @param mime mime type to use
  265. * @param session session handle
  266. * @param connection connection to use
  267. *)
  268. function fill_v1_v2_form(cls: Pointer; mime: Pcchar; session: PSession;
  269. connection: PMHD_Connection): cint; cdecl;
  270. var
  271. ret: cint;
  272. reply: Pcchar;
  273. response: PMHD_Response;
  274. begin
  275. reply := Malloc(strlen(SECOND_PAGE) + strlen(session^.value_1) +
  276. strlen(session^.value_2) + 1);
  277. if nil = reply then
  278. Exit(MHD_NO);
  279. snprintf(reply, strlen(SECOND_PAGE) + strlen(session^.value_1) +
  280. strlen(session^.value_2) + 1, SECOND_PAGE, session^.value_1,
  281. session^.value_2);
  282. (* return static form *)
  283. response := MHD_create_response_from_buffer(strlen(reply), Pointer(reply),
  284. MHD_RESPMEM_MUST_FREE);
  285. if nil = response then
  286. Exit(MHD_NO);
  287. add_session_cookie(session, response);
  288. MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_ENCODING, mime);
  289. ret := MHD_queue_response (connection, MHD_HTTP_OK, response);
  290. MHD_destroy_response(response);
  291. Result := ret;
  292. end;
  293. (**
  294. * Handler used to generate a 404 reply.
  295. *
  296. * @param cls a 'const char *' with the HTML webpage to return
  297. * @param mime mime type to use
  298. * @param session session handle
  299. * @param connection connection to use
  300. *)
  301. function not_found_page(cls: Pointer; mime: Pcchar; session: PSession;
  302. connection: PMHD_Connection): cint; cdecl;
  303. var
  304. ret: cint;
  305. response: PMHD_Response;
  306. begin
  307. (* unsupported HTTP method *)
  308. response := MHD_create_response_from_buffer(Length(NOT_FOUND_ERROR),
  309. Pcchar(NOT_FOUND_ERROR), MHD_RESPMEM_PERSISTENT);
  310. ret := MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response);
  311. MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_ENCODING, mime);
  312. MHD_destroy_response(response);
  313. Result := ret;
  314. end;
  315. const
  316. (**
  317. * List of all pages served by this HTTP server.
  318. *)
  319. pages: array[0..4] of TPage = (
  320. (url: '/'; mime: 'text/html'; handler: @fill_v1_form; handler_cls: nil),
  321. (url: '/2'; mime: 'text/html'; handler: @fill_v1_v2_form; handler_cls: nil),
  322. (url: '/S'; mime: 'text/html'; handler: @serve_simple_form; handler_cls: SUBMIT_PAGE),
  323. (url: '/F'; mime: 'text/html'; handler: @serve_simple_form; handler_cls: LAST_PAGE),
  324. (url: nil; mime: nil; handler: @not_found_page; handler_cls: nil) (* 404 *)
  325. );
  326. (**
  327. * Iterator over key-value pairs where the value
  328. * maybe made available in increments and/or may
  329. * not be zero-terminated. Used for processing
  330. * POST data.
  331. *
  332. * @param cls user-specified closure
  333. * @param kind type of the value
  334. * @param key 0-terminated key for the value
  335. * @param filename name of the uploaded file, NULL if not known
  336. * @param content_type mime-type of the data, NULL if not known
  337. * @param transfer_encoding encoding of the data, NULL if not known
  338. * @param data pointer to size bytes of data at the
  339. * specified offset
  340. * @param off offset of data in the overall value
  341. * @param size number of bytes in data available
  342. * @return MHD_YES to continue iterating,
  343. * MHD_NO to abort the iteration
  344. *)
  345. function post_iterator(cls: Pointer; kind: MHD_ValueKind; key: Pcchar;
  346. filename: Pcchar; content_type: Pcchar; transfer_encoding: Pcchar;
  347. data: Pcchar; off: cuint64; size: size_t): cint; cdecl;
  348. var
  349. request: PRequest;
  350. session: PSession;
  351. begin
  352. request := cls;
  353. session := request^.session;
  354. if StrComp('DONE', key) = 0 then
  355. begin
  356. WriteLn(stdout, Format('Session `%s'' submitted `%s'', `%s''', [
  357. session^.sid, session^.value_1, session^.value_2]));
  358. Exit(MHD_YES);
  359. end;
  360. if StrComp('v1', key) = 0 then
  361. begin
  362. if (size + off) > SizeOf(session^.value_1) then
  363. size := SizeOf(session^.value_1) - off - 1;
  364. Move(data^, session^.value_1[off], size);
  365. if (size + off) < SizeOf(session^.value_1) then
  366. session^.value_1[size + off] := #0;
  367. Exit(MHD_YES);
  368. end;
  369. if StrComp('v2', key) = 0 then
  370. begin
  371. if (size + off) > SizeOf(session^.value_2) then
  372. size := SizeOf(session^.value_2) - off - 1;
  373. Move(data^, session^.value_2[off], size);
  374. if (size + off) < SizeOf(session^.value_2) then
  375. session^.value_2[size + off] := #0;
  376. Exit(MHD_YES);
  377. end;
  378. WriteLn(stderr, Format('Unsupported form value `%s''', [key]));
  379. Result := MHD_YES;
  380. end;
  381. (**
  382. * Main MHD callback for handling requests.
  383. *
  384. *
  385. * @param cls argument given together with the function
  386. * pointer when the handler was registered with MHD
  387. * @param connection handle to connection which is being processed
  388. * @param url the requested url
  389. * @param method the HTTP method used ("GET", "PUT", etc.)
  390. * @param version the HTTP version string (i.e. "HTTP/1.1")
  391. * @param upload_data the data being uploaded (excluding HEADERS,
  392. * for a POST that fits into memory and that is encoded
  393. * with a supported encoding, the POST data will NOT be
  394. * given in upload_data and is instead available as
  395. * part of MHD_get_connection_values; very large POST
  396. * data *will* be made available incrementally in
  397. * upload_data)
  398. * @param upload_data_size set initially to the size of the
  399. * upload_data provided; the method must update this
  400. * value to the number of bytes NOT processed;
  401. * @param ptr pointer that the callback can set to some
  402. * address and that will be preserved by MHD for future
  403. * calls for this request; since the access handler may
  404. * be called many times (i.e., for a PUT/POST operation
  405. * with plenty of upload data) this allows the application
  406. * to easily associate some request-specific state.
  407. * If necessary, this state can be cleaned up in the
  408. * global "MHD_RequestCompleted" callback (which
  409. * can be set with the MHD_OPTION_NOTIFY_COMPLETED).
  410. * Initially, <tt>*con_cls</tt> will be NULL.
  411. * @return MHS_YES if the connection was handled successfully,
  412. * MHS_NO if the socket must be closed due to a serios
  413. * error while handling the request
  414. *)
  415. function create_response(cls: Pointer; connection: PMHD_Connection;
  416. url: Pcchar; method: Pcchar; version: Pcchar; upload_data: Pcchar;
  417. upload_data_size: Psize_t; ptr: PPointer): cint; cdecl;
  418. var
  419. response: PMHD_Response;
  420. request: PRequest;
  421. session: PSession;
  422. ret: cint;
  423. i: Cardinal;
  424. begin
  425. request := ptr^;
  426. if nil = request then
  427. begin
  428. request := CAlloc(1, SizeOf(TRequest));
  429. if nil = request then
  430. begin
  431. WriteLn(stderr, 'calloc error: ', strerror(errno^));
  432. Exit(MHD_NO);
  433. end;
  434. ptr^ := request;
  435. if StrComp(method, MHD_HTTP_METHOD_POST) = 0 then
  436. begin
  437. request^.pp := MHD_create_post_processor(connection, 1024,
  438. @post_iterator, request);
  439. if nil = request^.pp then
  440. begin
  441. WriteLn(stderr, Format('Failed to setup post processor for `%s''',
  442. [url]));
  443. Exit(MHD_NO); (* internal error *)
  444. end;
  445. end;
  446. Exit(MHD_YES);
  447. end;
  448. if nil = request^.session then
  449. begin
  450. request^.session := get_session(connection);
  451. if nil = request^.session then
  452. begin
  453. WriteLn(stderr, Format('Failed to setup session for `%s''', [url]));
  454. Exit(MHD_NO); (* internal error *)
  455. end;
  456. end;
  457. session := request^.session;
  458. session^.start := FpTime;
  459. if StrComp(method, MHD_HTTP_METHOD_POST) = 0 then
  460. begin
  461. (* evaluate POST data *)
  462. MHD_post_process(request^.pp, upload_data, upload_data_size^);
  463. if upload_data_size^ <> 0 then
  464. begin
  465. upload_data_size^ := 0;
  466. Exit(MHD_YES);
  467. end;
  468. (* done with POST data, serve response *)
  469. MHD_destroy_post_processor(request^.pp);
  470. request^.pp := nil;
  471. method := MHD_HTTP_METHOD_GET; (* fake 'GET' *)
  472. if nil <> request^.post_url then
  473. url := request^.post_url;
  474. end;
  475. if (StrComp(method, MHD_HTTP_METHOD_GET) = 0) or
  476. (StrComp(method, MHD_HTTP_METHOD_HEAD) = 0) then
  477. begin
  478. (* find out which page to serve *)
  479. i := 0;
  480. while (pages[i].url <> nil) and (StrComp(pages[i].url, url) <> 0) do
  481. Inc(i);
  482. ret := pages[i].handler(pages[i].handler_cls, pages[i].mime, session,
  483. connection);
  484. if ret <> MHD_YES then
  485. WriteLn(stderr, Format('Failed to create page for `%s''', [url]));
  486. Exit(ret);
  487. end;
  488. (* unsupported HTTP method *)
  489. response := MHD_create_response_from_buffer(Length(METHOD_ERROR),
  490. Pcchar(METHOD_ERROR), MHD_RESPMEM_PERSISTENT);
  491. ret := MHD_queue_response(connection, MHD_HTTP_NOT_ACCEPTABLE, response);
  492. MHD_destroy_response(response);
  493. Result := ret;
  494. end;
  495. (**
  496. * Callback called upon completion of a request.
  497. * Decrements session reference counter.
  498. *
  499. * @param cls not used
  500. * @param connection connection that completed
  501. * @param con_cls session handle
  502. * @param toe status code
  503. *)
  504. procedure request_completed_callback(cls: Pointer; connection: PMHD_Connection;
  505. con_cls: PPointer; toe: MHD_RequestTerminationCode);
  506. var
  507. request: PRequest;
  508. begin
  509. request := con_cls^;
  510. if nil = request then
  511. Exit;
  512. if nil <> request^.session then
  513. Dec(request^.session^.rc);
  514. if nil <> request^.pp then
  515. MHD_destroy_post_processor(request^.pp);
  516. Free(request);
  517. end;
  518. (**
  519. * Clean up handles of sessions that have been idle for
  520. * too long.
  521. *)
  522. procedure expire_sessions;
  523. var
  524. pos: PSession;
  525. prev: PSession;
  526. next: PSession;
  527. now: time_t;
  528. begin
  529. now := FpTime;
  530. prev := nil;
  531. pos := _sessions;
  532. while nil <> pos do
  533. begin
  534. next := pos^.next;
  535. if (now - pos^.start) > (60 * 60) then
  536. begin
  537. (* expire sessions after 1h *)
  538. if nil = prev then
  539. _sessions := pos^.next
  540. else
  541. prev^.next := next;
  542. Free(pos);
  543. end
  544. else
  545. prev := pos;
  546. pos := next;
  547. end;
  548. end;
  549. (**
  550. * Call with the port number as the only argument.
  551. * Never terminates (other than by signals, such as CTRL-C).
  552. *)
  553. var
  554. d: PMHD_Daemon;
  555. tv: timeval;
  556. tvp: ptimeval;
  557. rs: TFDSet;
  558. ws: TFDSet;
  559. es: TFDSet;
  560. max: cint;
  561. mhd_timeout: MHD_UNSIGNED_LONG_LONG;
  562. begin
  563. if argc <> 2 then
  564. begin
  565. WriteLn(argv[0], ' PORT');
  566. Halt(1);
  567. end;
  568. (* initialize PRNG *)
  569. Randomize;
  570. d := MHD_start_daemon(MHD_USE_DEBUG, StrToInt(argv[1]), nil, nil,
  571. @create_response, nil, MHD_OPTION_CONNECTION_TIMEOUT, cuint(15),
  572. MHD_OPTION_NOTIFY_COMPLETED, @request_completed_callback, nil, MHD_OPTION_END);
  573. if nil = d then
  574. Halt(1);
  575. while True do
  576. begin
  577. expire_sessions;
  578. max := 0;
  579. fpFD_ZERO(rs);
  580. fpFD_ZERO(ws);
  581. fpFD_ZERO(es);
  582. if MHD_YES <> MHD_get_fdset(d, @rs, @ws, @es, @max) then
  583. Break; (* fatal internal error *)
  584. if MHD_get_timeout(d, @mhd_timeout) = MHD_YES then
  585. begin
  586. tv.tv_sec := mhd_timeout div 1000;
  587. tv.tv_usec := (mhd_timeout - (tv.tv_sec * 1000)) * 1000;
  588. tvp := @tv;
  589. end
  590. else
  591. tvp := nil;
  592. fpSelect(max + 1, @rs, @ws, @es, tvp);
  593. MHD_run(d);
  594. end;
  595. MHD_stop_daemon(d);
  596. end.