demo.pp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  1. (*
  2. This file is part of libmicrohttpd
  3. Copyright (C) 2013 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 demo.pp (Original: demo.c)
  18. * @brief complex demonstration site: create directory index, offer
  19. * upload via form and HTTP POST, download with mime type detection
  20. * and error reporting (403, etc.) --- and all of this with
  21. * high-performance settings (large buffers, thread pool).
  22. * If you want to benchmark MHD, this code should be used to
  23. * run tests against. Note that the number of threads may need
  24. * to be adjusted depending on the number of available cores.
  25. * @author Christian Grothoff
  26. *)
  27. program demo;
  28. {$mode objfpc}{$H+}
  29. {$MACRO ON}
  30. {$IF DEFINED(CPU_COUNT) AND (CPU_COUNT + 0) < 2}
  31. {$UNDEF CPU_COUNT}
  32. {$ENDIF}
  33. {$IF NOT DEFINED(CPU_COUNT)}
  34. {$DEFINE CPU_COUNT := 2}
  35. {$ENDIF}
  36. uses
  37. sysutils, pthreads, ctypes, BaseUnix, cmem, cutils, libmicrohttpd;
  38. type
  39. {$i magic.inc}
  40. const
  41. (**
  42. * Number of threads to run in the thread pool. Should (roughly) match
  43. * the number of cores on your system.
  44. *)
  45. NUMBER_OF_THREADS = CPU_COUNT;
  46. (**
  47. * How many bytes of a file do we give to libmagic to determine the mime type?
  48. * 16k might be a bit excessive, but ought not hurt performance much anyway,
  49. * and should definitively be on the safe side.
  50. *)
  51. MAGIC_HEADER_SIZE = 16 * 1024;
  52. (**
  53. * Page returned for file-not-found.
  54. *)
  55. FILE_NOT_FOUND_PAGE: Pcchar = '<html><head><title>File not found</title></head><body>File not found</body></html>';
  56. (**
  57. * Page returned for internal errors.
  58. *)
  59. INTERNAL_ERROR_PAGE: Pcchar = '<html><head><title>Internal error</title></head><body>Internal error</body></html>';
  60. (**
  61. * Page returned for refused requests.
  62. *)
  63. REQUEST_REFUSED_PAGE: Pcchar = '<html><head><title>Request refused</title></head><body>Request refused (file exists?)</body></html>';
  64. (**
  65. * Head of index page.
  66. *)
  67. INDEX_PAGE_HEADER = '<html>'#10'<head><title>Welcome</title></head>'#10'<body>'#10+
  68. '<h1>Upload</h1>'#10+
  69. '<form method="POST" enctype="multipart/form-data" action="/">'#10+
  70. '<dl><dt>Content type:</dt><dd>'+
  71. '<input type="radio" name="category" value="books">Book</input>'+
  72. '<input type="radio" name="category" value="images">Image</input>'+
  73. '<input type="radio" name="category" value="music">Music</input>'+
  74. '<input type="radio" name="category" value="software">Software</input>'+
  75. '<input type="radio" name="category" value="videos">Videos</input>'#10+
  76. '<input type="radio" name="category" value="other" checked>Other</input></dd>'+
  77. '<dt>Language:</dt><dd>'+
  78. '<input type="radio" name="language" value="no-lang" checked>none</input>'+
  79. '<input type="radio" name="language" value="en">English</input>'+
  80. '<input type="radio" name="language" value="de">German</input>'+
  81. '<input type="radio" name="language" value="fr">French</input>'+
  82. '<input type="radio" name="language" value="es">Spanish</input></dd>'#10+
  83. '<dt>File:</dt><dd>'+
  84. '<input type="file" name="upload"/></dd></dl>'+
  85. '<input type="submit" value="Send!"/>'#10+
  86. '</form>'#10+
  87. '<h1>Download</h1>'#10+
  88. '<ol>'#10;
  89. (**
  90. * Footer of index page.
  91. *)
  92. INDEX_PAGE_FOOTER = '</ol>'#10'</body>'#10'</html>';
  93. (**
  94. * NULL-terminated array of supported upload categories. Should match HTML
  95. * in the form.
  96. *)
  97. categories: array[0..6] of Pcchar = (
  98. 'books',
  99. 'images',
  100. 'music',
  101. 'software',
  102. 'videos',
  103. 'other',
  104. nil
  105. );
  106. type
  107. (**
  108. * Specification of a supported language.
  109. *)
  110. Language = packed record
  111. (**
  112. * Directory name for the language.
  113. *)
  114. dirname: Pcchar;
  115. (**
  116. * Long name for humans.
  117. *)
  118. longname: Pcchar;
  119. end;
  120. PLanguage = ^Language;
  121. const
  122. (**
  123. * NULL-terminated array of supported upload categories. Should match HTML
  124. * in the form.
  125. *)
  126. languages: array[0..5] of Language = (
  127. (dirname: 'no-lang'; longname: 'No language specified'),
  128. (dirname: 'en'; longname: 'English'),
  129. (dirname: 'de'; longname: 'German'),
  130. (dirname: 'fr'; longname: 'French'),
  131. (dirname: 'es'; longname: 'Spanish'),
  132. (dirname: nil; longname: nil)
  133. );
  134. var
  135. (**
  136. * Response returned if the requested file does not exist (or is not accessible).
  137. *)
  138. file_not_found_response: PMHD_Response;
  139. (**
  140. * Response returned for internal errors.
  141. *)
  142. internal_error_response: PMHD_Response;
  143. (**
  144. * Response returned for '/' (GET) to list the contents of the directory and allow upload.
  145. *)
  146. cached_directory_response: PMHD_Response;
  147. (**
  148. * Response returned for refused uploads.
  149. *)
  150. request_refused_response: PMHD_Response;
  151. (**
  152. * Mutex used when we update the cached directory response object.
  153. *)
  154. mutex: pthread_mutex_t;
  155. (**
  156. * Global handle to MAGIC data.
  157. *)
  158. magic: magic_t;
  159. (**
  160. * Mark the given response as HTML for the brower.
  161. *
  162. * @param response response to mark
  163. *)
  164. procedure mark_as_html(response: PMHD_Response);
  165. begin
  166. MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, 'text/html');
  167. end;
  168. (**
  169. * Replace the existing 'cached_directory_response' with the
  170. * given response.
  171. *
  172. * @param response new directory response
  173. *)
  174. procedure update_cached_response(response: PMHD_Response);
  175. begin
  176. pthread_mutex_lock(@mutex);
  177. if nil <> cached_directory_response then
  178. MHD_destroy_response(cached_directory_response);
  179. cached_directory_response := response;
  180. pthread_mutex_unlock(@mutex);
  181. end;
  182. type
  183. (**
  184. * Context keeping the data for the response we're building.
  185. *)
  186. ResponseDataContext = packed record
  187. (**
  188. * Response data string.
  189. *)
  190. buf: Pcchar;
  191. (**
  192. * Number of bytes allocated for 'buf'.
  193. *)
  194. buf_len: size_t;
  195. (**
  196. * Current position where we append to 'buf'. Must be smaller or equal to 'buf_len'.
  197. *)
  198. off: size_t;
  199. end;
  200. PResponseDataContext = ^ResponseDataContext;
  201. (**
  202. * Create a listing of the files in 'dirname' in HTML.
  203. *
  204. * @param rdc where to store the list of files
  205. * @param dirname name of the directory to list
  206. * @return MHD_YES on success, MHD_NO on error
  207. *)
  208. function list_directory(rdc: PResponseDataContext; dirname: Pcchar): cint; cdecl;
  209. var
  210. fullname: array[0..PATH_MAX] of AnsiChar;
  211. sbuf: stat;
  212. dir: pDir;
  213. de: pDirent;
  214. r: Pointer;
  215. begin
  216. dir := FpOpendir(dirname);
  217. if nil = dir then
  218. Exit(MHD_NO);
  219. while True do
  220. begin
  221. de := FpReaddir(dir^);
  222. if de = nil then
  223. Break;
  224. if '.' = de^.d_name[0] then
  225. Continue;
  226. if SizeOf(fullname) <= size_t(
  227. snprintf(fullname, SizeOf(fullname), '%s/%s', dirname, de^.d_name)) then
  228. Continue; (* ugh, file too long? how can this be!? *)
  229. if 0 <> FpStat(PAnsiChar(fullname), sbuf) then
  230. Continue; (* ugh, failed to 'stat' *)
  231. if not fpS_ISREG(sbuf.st_mode) then
  232. Continue; (* not a regular file, skip *)
  233. if rdc^.off + 1024 > rdc^.buf_len then
  234. begin
  235. if (2 * rdc^.buf_len + 1024) < rdc^.buf_len then
  236. Break; (* more than SIZE_T _index_ size? Too big for us *)
  237. rdc^.buf_len := 2 * rdc^.buf_len + 1024;
  238. r := ReAlloc(rdc^.buf, rdc^.buf_len);
  239. if nil = r then
  240. Break; (* out of memory *)
  241. rdc^.buf := r;
  242. end;
  243. rdc^.off += snprintf(@rdc^.buf[rdc^.off], rdc^.buf_len - rdc^.off,
  244. '<li><a href="/%s">%s</a></li>'#10, fullname, de^.d_name);
  245. end;
  246. FpClosedir(dir^);
  247. Result := MHD_YES;
  248. end;
  249. (**
  250. * Re-scan our local directory and re-build the index.
  251. *)
  252. procedure update_directory;
  253. const
  254. initial_allocation: size_t = 32 * 1024; (* initial size for response buffer *)
  255. var
  256. response: PMHD_Response;
  257. rdc: ResponseDataContext;
  258. language_idx: cuint;
  259. category_idx: cuint;
  260. language: PLanguage;
  261. category: Pcchar;
  262. dir_name: array[0..128] of AnsiChar;
  263. sbuf: stat;
  264. begin
  265. rdc.buf_len := initial_allocation;
  266. rdc.buf := Malloc(rdc.buf_len);
  267. if nil = rdc.buf then
  268. begin
  269. update_cached_response(nil);
  270. Exit;
  271. end;
  272. rdc.off := snprintf(rdc.buf, rdc.buf_len, '%s', INDEX_PAGE_HEADER);
  273. language_idx := 0;
  274. while True do
  275. begin
  276. try
  277. if languages[language_idx].dirname = nil then
  278. Break;
  279. language := @languages[language_idx];
  280. if 0 <> FpStat(language^.dirname, sbuf) then
  281. Continue; (* empty *)
  282. (* we ensured always +1k room, filenames are ~256 bytes,
  283. so there is always still enough space for the header
  284. without need for an additional reallocation check. *)
  285. rdc.off += snprintf(@rdc.buf[rdc.off], rdc.buf_len - rdc.off,
  286. '<h2>%s</h2>'#10, language^.longname);
  287. category_idx := 0;
  288. while True do
  289. begin
  290. try
  291. if categories[category_idx] = nil then
  292. Break;
  293. category := categories[category_idx];
  294. snprintf(dir_name, sizeof(dir_name), '%s/%s', language^.dirname, category);
  295. if 0 <> FpStat(PAnsiChar(dir_name), sbuf) then
  296. Continue; (* empty *)
  297. (* we ensured always +1k room, filenames are ~256 bytes,
  298. so there is always still enough space for the header
  299. without need for an additional reallocation check. *)
  300. rdc.off += snprintf(@rdc.buf[rdc.off], rdc.buf_len - rdc.off,
  301. '<h3>%s</h3>'#10, category);
  302. if MHD_NO = list_directory(@rdc, dir_name) then
  303. begin
  304. Free(rdc.buf);
  305. update_cached_response(nil);
  306. Exit;
  307. end;
  308. finally
  309. Inc(category_idx);
  310. end;
  311. end;
  312. finally
  313. Inc(language_idx);
  314. end;
  315. end;
  316. (* we ensured always +1k room, filenames are ~256 bytes,
  317. so there is always still enough space for the footer
  318. without need for a final reallocation check. *)
  319. rdc.off += snprintf(@rdc.buf[rdc.off], rdc.buf_len - rdc.off, '%s',
  320. INDEX_PAGE_FOOTER);
  321. initial_allocation := rdc.buf_len; (* remember for next time *)
  322. response := MHD_create_response_from_buffer(rdc.off, rdc.buf,
  323. MHD_RESPMEM_MUST_FREE);
  324. mark_as_html(response);
  325. {$IFDEF FORCE_CLOSE}
  326. MHD_add_response_header (response, MHD_HTTP_HEADER_CONNECTION, 'close');
  327. {$ENDIF}
  328. update_cached_response(response);
  329. end;
  330. type
  331. (**
  332. * Context we keep for an upload.
  333. *)
  334. UploadContext = packed record
  335. (**
  336. * Handle where we write the uploaded file to.
  337. *)
  338. fd: cint;
  339. (**
  340. * Name of the file on disk (used to remove on errors).
  341. *)
  342. filename: Pcchar;
  343. (**
  344. * Language for the upload.
  345. *)
  346. language: Pcchar;
  347. (**
  348. * Category for the upload.
  349. *)
  350. category: Pcchar;
  351. (**
  352. * Post processor we're using to process the upload.
  353. *)
  354. pp: PMHD_PostProcessor;
  355. (**
  356. * Handle to connection that we're processing the upload for.
  357. *)
  358. connection: PMHD_Connection;
  359. (**
  360. * Response to generate, NULL to use directory.
  361. *)
  362. response: PMHD_Response;
  363. end;
  364. PUploadContext = ^UploadContext;
  365. (**
  366. * Append the 'size' bytes from 'data' to '*ret', adding
  367. * 0-termination. If '*ret' is NULL, allocate an empty string first.
  368. *
  369. * @param ret string to update, NULL or 0-terminated
  370. * @param data data to append
  371. * @param size number of bytes in 'data'
  372. * @return MHD_NO on allocation failure, MHD_YES on success
  373. *)
  374. function do_append(ret: Ppcchar; data: Pcchar; size: size_t): cint; cdecl;
  375. var
  376. buf: Pcchar;
  377. old_len: size_t;
  378. begin
  379. if nil = ret^ then
  380. old_len := 0
  381. else
  382. old_len := strlen(ret^);
  383. buf := Malloc(old_len + size + 1);
  384. if nil = buf then
  385. Exit(MHD_NO);
  386. Move(ret^^, buf, old_len);
  387. if nil <> ret^ then
  388. Free(ret^);
  389. Move(data^, buf[old_len], size);
  390. buf[old_len + size] := #0;
  391. ret^ := buf;
  392. Result := MHD_YES;
  393. end;
  394. (**
  395. * Iterator over key-value pairs where the value
  396. * maybe made available in increments and/or may
  397. * not be zero-terminated. Used for processing
  398. * POST data.
  399. *
  400. * @param cls user-specified closure
  401. * @param kind type of the value, always MHD_POSTDATA_KIND when called from MHD
  402. * @param key 0-terminated key for the value
  403. * @param filename name of the uploaded file, NULL if not known
  404. * @param content_type mime-type of the data, NULL if not known
  405. * @param transfer_encoding encoding of the data, NULL if not known
  406. * @param data pointer to size bytes of data at the
  407. * specified offset
  408. * @param off offset of data in the overall value
  409. * @param size number of bytes in data available
  410. * @return MHD_YES to continue iterating,
  411. * MHD_NO to abort the iteration
  412. *)
  413. function process_upload_data(cls: Pointer; kind: MHD_ValueKind; key: Pcchar;
  414. filename: Pcchar; content_type: Pcchar; transfer_encoding: Pcchar;
  415. data: Pcchar; off: cuint64; size: size_t): cint; cdecl;
  416. var
  417. uc: PUploadContext;
  418. i: cint;
  419. fn: array[0..PATH_MAX] of AnsiChar;
  420. begin
  421. uc := cls;
  422. if 0 = strcomp(key, 'category') then
  423. Exit(do_append(@uc^.category, data, size));
  424. if 0 = strcomp(key, 'language') then
  425. Exit(do_append(@uc^.language, data, size));
  426. if 0 <> strcomp(key, 'upload') then
  427. begin
  428. WriteLn(stderr, Format('Ignoring unexpected form value `%s''', [key]));
  429. Exit(MHD_YES); (* ignore *)
  430. end;
  431. if nil = filename then
  432. begin
  433. WriteLn(stderr, 'No filename, aborting upload');
  434. Exit(MHD_NO); (* no filename, error *)
  435. end;
  436. if (nil = uc^.category) or (nil = uc^.language) then
  437. begin
  438. WriteLn(stderr, Format('Missing form data for upload `%s''', [filename]));
  439. uc^.response := request_refused_response;
  440. Exit(MHD_NO);
  441. end;
  442. if -1 = uc^.fd then
  443. begin
  444. if (nil <> strstr(filename, '..')) or (nil <> strchr(filename, Ord('/'))) or
  445. (nil <> strchr(filename, Ord('\'))) then
  446. begin
  447. uc^.response := request_refused_response;
  448. Exit(MHD_NO);
  449. end;
  450. (* create directories -- if they don't exist already *)
  451. {$IFDEF MSWINDOWS}
  452. FpMkdir(uc^.language);
  453. {$ELSE}
  454. FpMkdir(uc^.language, S_IRWXU);
  455. {$ENDIF}
  456. snprintf(fn, SizeOf(fn), '%s/%s', uc^.language, uc^.category);
  457. {$IFDEF MSWINDOWS}
  458. FpMkdir(fn);
  459. {$ELSE}
  460. FpMkdir(PAnsiChar(fn), S_IRWXU);
  461. {$ENDIF}
  462. (* open file *)
  463. snprintf(fn, sizeof(fn), '%s/%s/%s', uc^.language, uc^.category, filename);
  464. for i := strlen(fn) - 1 downto 0 do
  465. if isprint(fn[i]) = 1 then
  466. fn[i] := '_';
  467. uc^.fd := FpOpen(PAnsiChar(fn), O_CREAT or O_EXCL
  468. {$IFDEF O_LARGEFILE}
  469. or O_LARGEFILE
  470. {$ENDIF}
  471. or O_WRONLY, S_IRUSR or S_IWUSR);
  472. if -1 = uc^.fd then
  473. begin
  474. WriteLn(stderr, Format('Error opening file `%s'' for upload: %s',
  475. [fn, strerror(errno^)]));
  476. uc^.response := request_refused_response;
  477. Exit(MHD_NO);
  478. end;
  479. uc^.filename := strdup(fn);
  480. end;
  481. if (0 <> size) and (size <> size_t(FpWrite(uc^.fd, data, size))) then
  482. begin
  483. (* write failed; likely: disk full *)
  484. WriteLn(stderr, Format('Error writing to file `%s'': %s', [uc^.filename,
  485. strerror(errno^)]));
  486. uc^.response := internal_error_response;
  487. FpClose(uc^.fd);
  488. uc^.fd := -1;
  489. if nil <> uc^.filename then
  490. begin
  491. FpUnlink(uc^.filename);
  492. Free(uc^.filename);
  493. uc^.filename := nil;
  494. end;
  495. Exit(MHD_NO);
  496. end;
  497. Exit(MHD_YES);
  498. end;
  499. (**
  500. * Function called whenever a request was completed.
  501. * Used to clean up 'struct UploadContext' objects.
  502. *
  503. * @param cls client-defined closure, NULL
  504. * @param connection connection handle
  505. * @param con_cls value as set by the last call to
  506. * the MHD_AccessHandlerCallback, points to NULL if this was
  507. * not an upload
  508. * @param toe reason for request termination
  509. *)
  510. procedure response_completed_callback(cls: Pointer; connection: PMHD_Connection;
  511. con_cls: PPointer; toe: MHD_RequestTerminationCode); cdecl;
  512. var
  513. uc: PUploadContext;
  514. begin
  515. uc := con_cls^;
  516. if nil = uc then
  517. Exit; (* this request wasn't an upload request *)
  518. if nil <> uc^.pp then
  519. begin
  520. MHD_destroy_post_processor(uc^.pp);
  521. uc^.pp := nil;
  522. end;
  523. if -1 <> uc^.fd then
  524. begin
  525. FpClose(uc^.fd);
  526. if nil <> uc^.filename then
  527. begin
  528. WriteLn(stderr, Format(
  529. 'Upload of file `%s'' failed (incomplete or aborted), removing file.',
  530. [uc^.filename]));
  531. FpUnlink(uc^.filename);
  532. end;
  533. end;
  534. if nil <> uc^.filename then
  535. Free(uc^.filename);
  536. Free(uc);
  537. end;
  538. (**
  539. * Return the current directory listing.
  540. *
  541. * @param connection connection to return the directory for
  542. * @return MHD_YES on success, MHD_NO on error
  543. *)
  544. function return_directory_response(connection: PMHD_Connection): cint;
  545. var
  546. ret: cint;
  547. begin
  548. pthread_mutex_lock(@mutex);
  549. if nil = cached_directory_response then
  550. ret := MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR,
  551. internal_error_response)
  552. else
  553. ret := MHD_queue_response(connection, MHD_HTTP_OK,
  554. cached_directory_response);
  555. pthread_mutex_unlock(@mutex);
  556. Result := ret;
  557. end;
  558. (**
  559. * Main callback from MHD, used to generate the page.
  560. *
  561. * @param cls NULL
  562. * @param connection connection handle
  563. * @param url requested URL
  564. * @param method GET, PUT, POST, etc.
  565. * @param version HTTP version
  566. * @param upload_data data from upload (PUT/POST)
  567. * @param upload_data_size number of bytes in "upload_data"
  568. * @param ptr our context
  569. * @return MHD_YES on success, MHD_NO to drop connection
  570. *)
  571. function generate_page(cls: Pointer; connection: PMHD_Connection; url: Pcchar;
  572. method: Pcchar; version: Pcchar; upload_data: Pcchar;
  573. upload_data_size: Psize_t; ptr: PPointer): cint; cdecl;
  574. var
  575. response: PMHD_Response;
  576. ret: cint;
  577. fd: cint;
  578. buf: stat;
  579. (* should be file download *)
  580. file_data: array[0..MAGIC_HEADER_SIZE] of AnsiChar;
  581. got: ssize_t ;
  582. mime: Pcchar;
  583. uc: PUploadContext;
  584. begin
  585. if 0 <> strcomp(url, '/') then
  586. begin
  587. if (0 <> strcomp(method, MHD_HTTP_METHOD_GET)) and
  588. (0 <> strcomp(method, MHD_HTTP_METHOD_HEAD)) then
  589. Exit(MHD_NO); (* unexpected method (we're not polite...) *)
  590. if (0 = FpStat(@url[1], buf)) and (nil = strstr(@url[1], '..')) and
  591. ('/' <> url[1]) then
  592. fd := FpOpen(@url[1], O_RDONLY)
  593. else
  594. fd := -1;
  595. if -1 = fd then
  596. Exit(MHD_queue_response(connection, MHD_HTTP_NOT_FOUND,
  597. file_not_found_response));
  598. (* read beginning of the file to determine mime type *)
  599. got := FpRead(fd, file_data, SizeOf(file_data));
  600. if -1 <> got then
  601. mime := magic_buffer(magic, Pcchar(file_data), got)
  602. else
  603. mime := nil;
  604. lseek(fd, 0, SEEK_SET);
  605. response := MHD_create_response_from_fd(buf.st_size, fd);
  606. if nil = response then
  607. begin
  608. (* internal error (i.e. out of memory) *)
  609. FpClose(fd);
  610. Exit(MHD_NO);
  611. end;
  612. (* add mime type if we had one *)
  613. if nil <> mime then
  614. MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, mime);
  615. ret := MHD_queue_response(connection, MHD_HTTP_OK, response);
  616. MHD_destroy_response(response);
  617. Exit(ret);
  618. end;
  619. if 0 = strcomp(method, MHD_HTTP_METHOD_POST) then
  620. begin
  621. (* upload! *)
  622. uc := ptr^;
  623. if nil = uc then
  624. begin
  625. uc := Malloc(SizeOf(UploadContext));
  626. if nil = uc then
  627. Exit(MHD_NO); (* out of memory, close connection *)
  628. memset(uc, 0, SizeOf(UploadContext));
  629. uc^.fd := -1;
  630. uc^.connection := connection;
  631. uc^.pp := MHD_create_post_processor(connection, 64 * 1024 (* buffer size *),
  632. @process_upload_data, uc);
  633. if nil = uc^.pp then
  634. begin
  635. (* out of memory, close connection *)
  636. Free(uc);
  637. Exit(MHD_NO);
  638. end;
  639. ptr^ := uc;
  640. Exit(MHD_YES);
  641. end;
  642. if 0 <> upload_data_size^ then
  643. begin
  644. if nil = uc^.response then
  645. MHD_post_process(uc^.pp, upload_data, upload_data_size^);
  646. upload_data_size^ := 0;
  647. Exit(MHD_YES);
  648. end;
  649. (* end of upload, finish it! *)
  650. MHD_destroy_post_processor(uc^.pp);
  651. uc^.pp := nil;
  652. if -1 <> uc^.fd then
  653. begin
  654. FpClose(uc^.fd);
  655. uc^.fd := -1;
  656. end;
  657. if nil <> uc^.response then
  658. Exit(MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, uc^.response))
  659. else
  660. begin
  661. update_directory;
  662. Exit(return_directory_response(connection));
  663. end;
  664. end;
  665. if (0 = strcomp(method, MHD_HTTP_METHOD_GET)) or
  666. (0 = strcomp(method, MHD_HTTP_METHOD_HEAD)) then
  667. Exit(return_directory_response(connection));
  668. (* unexpected request, refuse *)
  669. Result := MHD_queue_response(connection, MHD_HTTP_FORBIDDEN,
  670. request_refused_response);
  671. end;
  672. (**
  673. * Function called if we get a SIGPIPE. Does nothing.
  674. *
  675. * @param sig will be SIGPIPE (ignored)
  676. *)
  677. procedure catcher(signal: longint; info: psiginfo; context: psigcontext); cdecl;
  678. begin
  679. (* do nothing *)
  680. end;
  681. (**
  682. * setup handlers to ignore SIGPIPE.
  683. *)
  684. procedure ignore_sigpipe;
  685. var
  686. oldsig: sigactionrec;
  687. sig: sigactionrec;
  688. begin
  689. sig.sa_handler := @catcher;
  690. FpsigEmptySet(sig.sa_mask);
  691. {$IFDEF SA_INTERRUPT}
  692. sig.sa_flags := SA_INTERRUPT; (* SunOS *)
  693. {$ELSE}
  694. sig.sa_flags := SA_RESTART;
  695. {$ENDIF}
  696. if 0 <> FPSigaction(SIGPIPE, @sig, @oldsig) then
  697. WriteLn(stderr, Format('Failed to install SIGPIPE handler: %s',
  698. [strerror(errno^)]));
  699. end;
  700. (**
  701. * Entry point to demo. Note: this HTTP server will make all
  702. * files in the current directory and its subdirectories available
  703. * to anyone. Press ENTER to stop the server once it has started.
  704. *
  705. * @param argc number of arguments in argv
  706. * @param argv first and only argument should be the port number
  707. * @return 0 on success
  708. *)
  709. var
  710. d: PMHD_Daemon;
  711. port: cuint;
  712. begin
  713. if (argc <> 2) or (1 <> sscanf(argv[1], '%u', @port)) or
  714. (UINT16_MAX < port) then
  715. begin
  716. WriteLn(stderr, argv[0], ' PORT');
  717. Halt(1);
  718. end;
  719. ignore_sigpipe;
  720. magic := magic_open(MAGIC_MIME_TYPE);
  721. magic_load(magic, nil);
  722. pthread_mutex_init(@mutex, nil);
  723. file_not_found_response := MHD_create_response_from_buffer(
  724. strlen(FILE_NOT_FOUND_PAGE), FILE_NOT_FOUND_PAGE,
  725. MHD_RESPMEM_PERSISTENT);
  726. mark_as_html(file_not_found_response);
  727. request_refused_response := MHD_create_response_from_buffer(
  728. strlen(REQUEST_REFUSED_PAGE), REQUEST_REFUSED_PAGE,
  729. MHD_RESPMEM_PERSISTENT);
  730. mark_as_html(request_refused_response);
  731. internal_error_response := MHD_create_response_from_buffer(
  732. strlen(INTERNAL_ERROR_PAGE), INTERNAL_ERROR_PAGE,
  733. MHD_RESPMEM_PERSISTENT);
  734. mark_as_html(internal_error_response);
  735. update_directory;
  736. d := MHD_start_daemon(MHD_USE_SELECT_INTERNALLY or MHD_USE_DEBUG
  737. {$IFDEF EPOLL_SUPPORT}
  738. or MHD_USE_EPOLL_LINUX_ONLY
  739. {$ENDIF},
  740. port, nil, nil, @generate_page, nil,
  741. MHD_OPTION_CONNECTION_MEMORY_LIMIT, size_t(256 * 1024),
  742. {$IFDEF PRODUCTION}
  743. MHD_OPTION_PER_IP_CONNECTION_LIMIT, cuint(64),
  744. {$ENDIF}
  745. MHD_OPTION_CONNECTION_TIMEOUT, cuint(120 (* seconds *)),
  746. MHD_OPTION_THREAD_POOL_SIZE, cuint(NUMBER_OF_THREADS),
  747. MHD_OPTION_NOTIFY_COMPLETED, @response_completed_callback, nil,
  748. MHD_OPTION_END);
  749. if nil = d then
  750. Halt(1);
  751. WriteLn(stderr, 'HTTP server running. Press ENTER to stop the server');
  752. ReadLn;
  753. MHD_stop_daemon(d);
  754. MHD_destroy_response(file_not_found_response);
  755. MHD_destroy_response(request_refused_response);
  756. MHD_destroy_response(internal_error_response);
  757. update_cached_response(nil);
  758. pthread_mutex_destroy(@mutex);
  759. magic_close(magic);
  760. end.