2
0

demo_https.pp 28 KB

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