You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

614 lines
17 KiB

15 years ago
26 years ago
24 years ago
18 years ago
18 years ago
25 years ago
23 years ago
26 years ago
26 years ago
24 years ago
26 years ago
26 years ago
19 years ago
26 years ago
19 years ago
19 years ago
19 years ago
26 years ago
26 years ago
25 years ago
26 years ago
26 years ago
21 years ago
  1. /*
  2. +----------------------------------------------------------------------+
  3. | PHP Version 5 |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 1997-2011 The PHP Group |
  6. +----------------------------------------------------------------------+
  7. | This source file is subject to version 3.01 of the PHP license, |
  8. | that is bundled with this package in the file LICENSE, and is |
  9. | available through the world-wide-web at the following url: |
  10. | http://www.php.net/license/3_01.txt |
  11. | If you did not receive a copy of the PHP license and are unable to |
  12. | obtain it through the world-wide-web, please send a note to |
  13. | license@php.net so we can mail you a copy immediately. |
  14. +----------------------------------------------------------------------+
  15. | Authors: Rasmus Lerdorf <rasmus@lerdorf.on.ca> |
  16. | Stig Sther Bakken <ssb@php.net> |
  17. | David Sklar <sklar@student.net> |
  18. +----------------------------------------------------------------------+
  19. */
  20. /* $Id$ */
  21. #include "php_apache_http.h"
  22. #if defined(PHP_WIN32) || defined(NETWARE)
  23. #include "zend.h"
  24. #include "ap_compat.h"
  25. #endif
  26. #ifdef ZTS
  27. int php_apache_info_id;
  28. #else
  29. php_apache_info_struct php_apache_info;
  30. #endif
  31. #define SECTION(name) PUTS("<h2>" name "</h2>\n")
  32. #ifndef PHP_WIN32
  33. extern module *top_module;
  34. extern module **ap_loaded_modules;
  35. #else
  36. extern __declspec(dllimport) module *top_module;
  37. extern __declspec(dllimport) module **ap_loaded_modules;
  38. #endif
  39. PHP_FUNCTION(virtual);
  40. PHP_FUNCTION(apache_request_headers);
  41. PHP_FUNCTION(apache_response_headers);
  42. PHP_FUNCTION(apachelog);
  43. PHP_FUNCTION(apache_note);
  44. PHP_FUNCTION(apache_lookup_uri);
  45. PHP_FUNCTION(apache_child_terminate);
  46. PHP_FUNCTION(apache_setenv);
  47. PHP_FUNCTION(apache_get_version);
  48. PHP_FUNCTION(apache_get_modules);
  49. PHP_FUNCTION(apache_reset_timeout);
  50. PHP_MINFO_FUNCTION(apache);
  51. ZEND_BEGIN_ARG_INFO(arginfo_apache_child_terminate, 0)
  52. ZEND_END_ARG_INFO()
  53. ZEND_BEGIN_ARG_INFO_EX(arginfo_apache_note, 0, 0, 1)
  54. ZEND_ARG_INFO(0, note_name)
  55. ZEND_ARG_INFO(0, note_value)
  56. ZEND_END_ARG_INFO()
  57. ZEND_BEGIN_ARG_INFO_EX(arginfo_apache_virtual, 0, 0, 1)
  58. ZEND_ARG_INFO(0, filename)
  59. ZEND_END_ARG_INFO()
  60. ZEND_BEGIN_ARG_INFO(arginfo_apache_request_headers, 0)
  61. ZEND_END_ARG_INFO()
  62. ZEND_BEGIN_ARG_INFO(arginfo_apache_response_headers, 0)
  63. ZEND_END_ARG_INFO()
  64. ZEND_BEGIN_ARG_INFO_EX(arginfo_apache_setenv, 0, 0, 2)
  65. ZEND_ARG_INFO(0, variable)
  66. ZEND_ARG_INFO(0, value)
  67. ZEND_ARG_INFO(0, walk_to_top)
  68. ZEND_END_ARG_INFO()
  69. ZEND_BEGIN_ARG_INFO_EX(arginfo_apache_lookup_uri, 0, 0, 1)
  70. ZEND_ARG_INFO(0, uri)
  71. ZEND_END_ARG_INFO()
  72. ZEND_BEGIN_ARG_INFO(arginfo_apache_get_version, 0)
  73. ZEND_END_ARG_INFO()
  74. ZEND_BEGIN_ARG_INFO(arginfo_apache_get_modules, 0)
  75. ZEND_END_ARG_INFO()
  76. ZEND_BEGIN_ARG_INFO(arginfo_apache_reset_timeout, 0)
  77. ZEND_END_ARG_INFO()
  78. const zend_function_entry apache_functions[] = {
  79. PHP_FE(virtual, arginfo_apache_virtual)
  80. PHP_FE(apache_request_headers, arginfo_apache_request_headers)
  81. PHP_FE(apache_note, arginfo_apache_note)
  82. PHP_FE(apache_lookup_uri, arginfo_apache_lookup_uri)
  83. PHP_FE(apache_child_terminate, arginfo_apache_child_terminate)
  84. PHP_FE(apache_setenv, arginfo_apache_setenv)
  85. PHP_FE(apache_response_headers, arginfo_apache_response_headers)
  86. PHP_FE(apache_get_version, arginfo_apache_get_version)
  87. PHP_FE(apache_get_modules, arginfo_apache_get_modules)
  88. PHP_FE(apache_reset_timeout, arginfo_apache_reset_timeout)
  89. PHP_FALIAS(getallheaders, apache_request_headers, arginfo_apache_request_headers)
  90. {NULL, NULL, NULL}
  91. };
  92. PHP_INI_BEGIN()
  93. STD_PHP_INI_ENTRY("xbithack", "0", PHP_INI_ALL, OnUpdateLong, xbithack, php_apache_info_struct, php_apache_info)
  94. STD_PHP_INI_ENTRY("engine", "1", PHP_INI_ALL, OnUpdateLong, engine, php_apache_info_struct, php_apache_info)
  95. STD_PHP_INI_ENTRY("last_modified", "0", PHP_INI_ALL, OnUpdateLong, last_modified, php_apache_info_struct, php_apache_info)
  96. STD_PHP_INI_ENTRY("child_terminate", "0", PHP_INI_ALL, OnUpdateLong, terminate_child, php_apache_info_struct, php_apache_info)
  97. PHP_INI_END()
  98. static void php_apache_globals_ctor(php_apache_info_struct *apache_globals TSRMLS_DC)
  99. {
  100. apache_globals->in_request = 0;
  101. }
  102. static PHP_MINIT_FUNCTION(apache)
  103. {
  104. #ifdef ZTS
  105. ts_allocate_id(&php_apache_info_id, sizeof(php_apache_info_struct), (ts_allocate_ctor) php_apache_globals_ctor, NULL);
  106. #else
  107. php_apache_globals_ctor(&php_apache_info TSRMLS_CC);
  108. #endif
  109. REGISTER_INI_ENTRIES();
  110. return SUCCESS;
  111. }
  112. static PHP_MSHUTDOWN_FUNCTION(apache)
  113. {
  114. UNREGISTER_INI_ENTRIES();
  115. return SUCCESS;
  116. }
  117. zend_module_entry apache_module_entry = {
  118. STANDARD_MODULE_HEADER,
  119. "apache",
  120. apache_functions,
  121. PHP_MINIT(apache),
  122. PHP_MSHUTDOWN(apache),
  123. NULL,
  124. NULL,
  125. PHP_MINFO(apache),
  126. NO_VERSION_YET,
  127. STANDARD_MODULE_PROPERTIES
  128. };
  129. /* {{{ PHP_MINFO_FUNCTION
  130. */
  131. PHP_MINFO_FUNCTION(apache)
  132. {
  133. char *apv = (char *) ap_get_server_version();
  134. module *modp = NULL;
  135. char output_buf[128];
  136. #if !defined(WIN32) && !defined(WINNT)
  137. char name[64];
  138. char modulenames[1024];
  139. char *p;
  140. #endif
  141. server_rec *serv;
  142. extern char server_root[MAX_STRING_LEN];
  143. extern uid_t user_id;
  144. extern char *user_name;
  145. extern gid_t group_id;
  146. extern int max_requests_per_child;
  147. serv = ((request_rec *) SG(server_context))->server;
  148. php_info_print_table_start();
  149. #ifdef PHP_WIN32
  150. php_info_print_table_row(1, "Apache for Windows 95/NT");
  151. php_info_print_table_end();
  152. php_info_print_table_start();
  153. #elif defined(NETWARE)
  154. php_info_print_table_row(1, "Apache for NetWare");
  155. php_info_print_table_end();
  156. php_info_print_table_start();
  157. #else
  158. php_info_print_table_row(2, "APACHE_INCLUDE", PHP_APACHE_INCLUDE);
  159. php_info_print_table_row(2, "APACHE_TARGET", PHP_APACHE_TARGET);
  160. #endif
  161. if (apv && *apv) {
  162. php_info_print_table_row(2, "Apache Version", apv);
  163. }
  164. #ifdef APACHE_RELEASE
  165. snprintf(output_buf, sizeof(output_buf), "%d", APACHE_RELEASE);
  166. php_info_print_table_row(2, "Apache Release", output_buf);
  167. #endif
  168. snprintf(output_buf, sizeof(output_buf), "%d", MODULE_MAGIC_NUMBER);
  169. php_info_print_table_row(2, "Apache API Version", output_buf);
  170. snprintf(output_buf, sizeof(output_buf), "%s:%u", serv->server_hostname, serv->port);
  171. php_info_print_table_row(2, "Hostname:Port", output_buf);
  172. #if !defined(WIN32) && !defined(WINNT)
  173. snprintf(output_buf, sizeof(output_buf), "%s(%d)/%d", user_name, (int)user_id, (int)group_id);
  174. php_info_print_table_row(2, "User/Group", output_buf);
  175. snprintf(output_buf, sizeof(output_buf), "Per Child: %d - Keep Alive: %s - Max Per Connection: %d", max_requests_per_child, serv->keep_alive ? "on":"off", serv->keep_alive_max);
  176. php_info_print_table_row(2, "Max Requests", output_buf);
  177. #endif
  178. snprintf(output_buf, sizeof(output_buf), "Connection: %d - Keep-Alive: %d", serv->timeout, serv->keep_alive_timeout);
  179. php_info_print_table_row(2, "Timeouts", output_buf);
  180. #if !defined(WIN32) && !defined(WINNT)
  181. /*
  182. This block seems to be working on NetWare; But it seems to be showing
  183. all modules instead of just the loaded ones
  184. */
  185. php_info_print_table_row(2, "Server Root", server_root);
  186. strcpy(modulenames, "");
  187. for(modp = top_module; modp; modp = modp->next) {
  188. strlcpy(name, modp->name, sizeof(name));
  189. if ((p = strrchr(name, '.'))) {
  190. *p='\0'; /* Cut off ugly .c extensions on module names */
  191. }
  192. strlcat(modulenames, name, sizeof(modulenames));
  193. if (modp->next) {
  194. strlcat(modulenames, ", ", sizeof(modulenames));
  195. }
  196. }
  197. php_info_print_table_row(2, "Loaded Modules", modulenames);
  198. #endif
  199. php_info_print_table_end();
  200. DISPLAY_INI_ENTRIES();
  201. {
  202. register int i;
  203. array_header *arr;
  204. table_entry *elts;
  205. request_rec *r;
  206. r = ((request_rec *) SG(server_context));
  207. arr = table_elts(r->subprocess_env);
  208. elts = (table_entry *)arr->elts;
  209. SECTION("Apache Environment");
  210. php_info_print_table_start();
  211. php_info_print_table_header(2, "Variable", "Value");
  212. for (i=0; i < arr->nelts; i++) {
  213. php_info_print_table_row(2, elts[i].key, elts[i].val);
  214. }
  215. php_info_print_table_end();
  216. }
  217. {
  218. array_header *env_arr;
  219. table_entry *env;
  220. int i;
  221. request_rec *r;
  222. r = ((request_rec *) SG(server_context));
  223. SECTION("HTTP Headers Information");
  224. php_info_print_table_start();
  225. php_info_print_table_colspan_header(2, "HTTP Request Headers");
  226. php_info_print_table_row(2, "HTTP Request", r->the_request);
  227. env_arr = table_elts(r->headers_in);
  228. env = (table_entry *)env_arr->elts;
  229. for (i = 0; i < env_arr->nelts; ++i) {
  230. if (env[i].key && (!PG(safe_mode) || (PG(safe_mode) && strncasecmp(env[i].key, "authorization", 13)))) {
  231. php_info_print_table_row(2, env[i].key, env[i].val);
  232. }
  233. }
  234. php_info_print_table_colspan_header(2, "HTTP Response Headers");
  235. env_arr = table_elts(r->headers_out);
  236. env = (table_entry *)env_arr->elts;
  237. for(i = 0; i < env_arr->nelts; ++i) {
  238. if (env[i].key) {
  239. php_info_print_table_row(2, env[i].key, env[i].val);
  240. }
  241. }
  242. php_info_print_table_end();
  243. }
  244. }
  245. /* }}} */
  246. /* {{{ proto bool apache_child_terminate(void)
  247. Terminate apache process after this request */
  248. PHP_FUNCTION(apache_child_terminate)
  249. {
  250. #ifndef MULTITHREAD
  251. if (AP(terminate_child)) {
  252. ap_child_terminate( ((request_rec *)SG(server_context)) );
  253. RETURN_TRUE;
  254. } else { /* tell them to get lost! */
  255. php_error_docref(NULL TSRMLS_CC, E_WARNING, "This function is disabled");
  256. RETURN_FALSE;
  257. }
  258. #else
  259. php_error_docref(NULL TSRMLS_CC, E_WARNING, "This function is not supported in this build");
  260. RETURN_FALSE;
  261. #endif
  262. }
  263. /* }}} */
  264. /* {{{ proto string apache_note(string note_name [, string note_value])
  265. Get and set Apache request notes */
  266. PHP_FUNCTION(apache_note)
  267. {
  268. char *note_name, *note_val = NULL;
  269. int note_name_len, note_val_len;
  270. char *old_val;
  271. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s", &note_name, &note_name_len, &note_val, &note_val_len) == FAILURE) {
  272. return;
  273. }
  274. old_val = (char *) table_get(((request_rec *)SG(server_context))->notes, note_name);
  275. if (note_val) {
  276. table_set(((request_rec *)SG(server_context))->notes, note_name, note_val);
  277. }
  278. if (old_val) {
  279. RETURN_STRING(old_val, 1);
  280. }
  281. RETURN_FALSE;
  282. }
  283. /* }}} */
  284. /* {{{ proto bool virtual(string filename)
  285. Perform an Apache sub-request */
  286. /* This function is equivalent to <!--#include virtual...-->
  287. * in mod_include. It does an Apache sub-request. It is useful
  288. * for including CGI scripts or .shtml files, or anything else
  289. * that you'd parse through Apache (for .phtml files, you'd probably
  290. * want to use <?Include>. This only works when PHP is compiled
  291. * as an Apache module, since it uses the Apache API for doing
  292. * sub requests.
  293. */
  294. PHP_FUNCTION(virtual)
  295. {
  296. char *filename;
  297. int filename_len;
  298. request_rec *rr = NULL;
  299. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &filename, &filename_len) == FAILURE) {
  300. return;
  301. }
  302. if (!(rr = sub_req_lookup_uri (filename, ((request_rec *) SG(server_context))))) {
  303. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include '%s' - URI lookup failed", filename);
  304. if (rr)
  305. destroy_sub_req (rr);
  306. RETURN_FALSE;
  307. }
  308. if (rr->status != 200) {
  309. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include '%s' - error finding URI", filename);
  310. if (rr)
  311. destroy_sub_req (rr);
  312. RETURN_FALSE;
  313. }
  314. php_end_ob_buffers(1 TSRMLS_CC);
  315. php_header(TSRMLS_C);
  316. if (run_sub_req(rr)) {
  317. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include '%s' - request execution failed", filename);
  318. if (rr)
  319. destroy_sub_req (rr);
  320. RETURN_FALSE;
  321. }
  322. if (rr)
  323. destroy_sub_req (rr);
  324. RETURN_TRUE;
  325. }
  326. /* }}} */
  327. /* {{{ proto array getallheaders(void)
  328. Alias for apache_request_headers() */
  329. /* }}} */
  330. /* {{{ proto array apache_request_headers(void)
  331. Fetch all HTTP request headers */
  332. PHP_FUNCTION(apache_request_headers)
  333. {
  334. array_header *env_arr;
  335. table_entry *tenv;
  336. int i;
  337. array_init(return_value);
  338. env_arr = table_elts(((request_rec *) SG(server_context))->headers_in);
  339. tenv = (table_entry *)env_arr->elts;
  340. for (i = 0; i < env_arr->nelts; ++i) {
  341. if (!tenv[i].key ||
  342. (PG(safe_mode) &&
  343. !strncasecmp(tenv[i].key, "authorization", 13))) {
  344. continue;
  345. }
  346. if (add_assoc_string(return_value, tenv[i].key, (tenv[i].val==NULL) ? "" : tenv[i].val, 1)==FAILURE) {
  347. RETURN_FALSE;
  348. }
  349. }
  350. }
  351. /* }}} */
  352. /* {{{ proto array apache_response_headers(void)
  353. Fetch all HTTP response headers */
  354. PHP_FUNCTION(apache_response_headers)
  355. {
  356. array_header *env_arr;
  357. table_entry *tenv;
  358. int i;
  359. array_init(return_value);
  360. env_arr = table_elts(((request_rec *) SG(server_context))->headers_out);
  361. tenv = (table_entry *)env_arr->elts;
  362. for (i = 0; i < env_arr->nelts; ++i) {
  363. if (!tenv[i].key) continue;
  364. if (add_assoc_string(return_value, tenv[i].key, (tenv[i].val==NULL) ? "" : tenv[i].val, 1)==FAILURE) {
  365. RETURN_FALSE;
  366. }
  367. }
  368. }
  369. /* }}} */
  370. /* {{{ proto bool apache_setenv(string variable, string value [, bool walk_to_top])
  371. Set an Apache subprocess_env variable */
  372. PHP_FUNCTION(apache_setenv)
  373. {
  374. int var_len, val_len;
  375. zend_bool top=0;
  376. char *var = NULL, *val = NULL;
  377. request_rec *r = (request_rec *) SG(server_context);
  378. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|b", &var, &var_len, &val, &val_len, &top) == FAILURE) {
  379. return;
  380. }
  381. while(top) {
  382. if(r->prev) r = r->prev;
  383. else break;
  384. }
  385. ap_table_setn(r->subprocess_env, ap_pstrndup(r->pool, var, var_len), ap_pstrndup(r->pool, val, val_len));
  386. RETURN_TRUE;
  387. }
  388. /* }}} */
  389. /* {{{ proto object apache_lookup_uri(string URI)
  390. Perform a partial request of the given URI to obtain information about it */
  391. PHP_FUNCTION(apache_lookup_uri)
  392. {
  393. char *filename;
  394. int filename_len;
  395. request_rec *rr=NULL;
  396. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &filename, &filename_len) == FAILURE) {
  397. return;
  398. }
  399. if (!(rr = sub_req_lookup_uri(filename, ((request_rec *) SG(server_context))))) {
  400. php_error_docref(NULL TSRMLS_CC, E_WARNING, "URI lookup failed '%s'", filename);
  401. RETURN_FALSE;
  402. }
  403. object_init(return_value);
  404. add_property_long(return_value,"status", rr->status);
  405. if (rr->the_request) {
  406. add_property_string(return_value,"the_request", rr->the_request, 1);
  407. }
  408. if (rr->status_line) {
  409. add_property_string(return_value,"status_line", (char *)rr->status_line, 1);
  410. }
  411. if (rr->method) {
  412. add_property_string(return_value,"method", (char *)rr->method, 1);
  413. }
  414. if (rr->content_type) {
  415. add_property_string(return_value,"content_type", (char *)rr->content_type, 1);
  416. }
  417. if (rr->handler) {
  418. add_property_string(return_value,"handler", (char *)rr->handler, 1);
  419. }
  420. if (rr->uri) {
  421. add_property_string(return_value,"uri", rr->uri, 1);
  422. }
  423. if (rr->filename) {
  424. add_property_string(return_value,"filename", rr->filename, 1);
  425. }
  426. if (rr->path_info) {
  427. add_property_string(return_value,"path_info", rr->path_info, 1);
  428. }
  429. if (rr->args) {
  430. add_property_string(return_value,"args", rr->args, 1);
  431. }
  432. if (rr->boundary) {
  433. add_property_string(return_value,"boundary", rr->boundary, 1);
  434. }
  435. add_property_long(return_value,"no_cache", rr->no_cache);
  436. add_property_long(return_value,"no_local_copy", rr->no_local_copy);
  437. add_property_long(return_value,"allowed", rr->allowed);
  438. add_property_long(return_value,"sent_bodyct", rr->sent_bodyct);
  439. add_property_long(return_value,"bytes_sent", rr->bytes_sent);
  440. add_property_long(return_value,"byterange", rr->byterange);
  441. add_property_long(return_value,"clength", rr->clength);
  442. #if MODULE_MAGIC_NUMBER >= 19980324
  443. if (rr->unparsed_uri) {
  444. add_property_string(return_value,"unparsed_uri", rr->unparsed_uri, 1);
  445. }
  446. if(rr->mtime) {
  447. add_property_long(return_value,"mtime", rr->mtime);
  448. }
  449. #endif
  450. if(rr->request_time) {
  451. add_property_long(return_value,"request_time", rr->request_time);
  452. }
  453. destroy_sub_req(rr);
  454. }
  455. /* }}} */
  456. #if 0
  457. This function is most likely a bad idea. Just playing with it for now.
  458. PHP_FUNCTION(apache_exec_uri)
  459. {
  460. char *filename;
  461. int filename_len;
  462. request_rec *rr=NULL;
  463. TSRMLS_FETCH();
  464. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &filename, &filename_len) == FAILURE) {
  465. return;
  466. }
  467. if(!(rr = ap_sub_req_lookup_uri(filename, ((request_rec *) SG(server_context))))) {
  468. php_error_docref(NULL TSRMLS_CC, E_WARNING, "URI lookup failed", filename);
  469. RETURN_FALSE;
  470. }
  471. RETVAL_LONG(ap_run_sub_req(rr));
  472. ap_destroy_sub_req(rr);
  473. }
  474. #endif
  475. /* {{{ proto string apache_get_version(void)
  476. Fetch Apache version */
  477. PHP_FUNCTION(apache_get_version)
  478. {
  479. char *apv = (char *) ap_get_server_version();
  480. if (apv && *apv) {
  481. RETURN_STRING(apv, 1);
  482. }
  483. RETURN_FALSE;
  484. }
  485. /* }}} */
  486. /* {{{ proto array apache_get_modules(void)
  487. Get a list of loaded Apache modules */
  488. PHP_FUNCTION(apache_get_modules)
  489. {
  490. int n;
  491. char *p;
  492. array_init(return_value);
  493. for (n = 0; ap_loaded_modules[n]; ++n) {
  494. char *s = (char *) ap_loaded_modules[n]->name;
  495. if ((p = strchr(s, '.'))) {
  496. add_next_index_stringl(return_value, s, (p - s), 1);
  497. } else {
  498. add_next_index_string(return_value, s, 1);
  499. }
  500. }
  501. }
  502. /* }}} */
  503. /* {{{ proto bool apache_reset_timeout(void)
  504. Reset the Apache write timer */
  505. PHP_FUNCTION(apache_reset_timeout)
  506. {
  507. if (PG(safe_mode)) {
  508. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot reset the Apache timeout in safe mode");
  509. RETURN_FALSE;
  510. }
  511. ap_reset_timeout((request_rec *)SG(server_context));
  512. RETURN_TRUE;
  513. }
  514. /* }}} */
  515. /*
  516. * Local variables:
  517. * tab-width: 4
  518. * c-basic-offset: 4
  519. * End:
  520. * vim600: sw=4 ts=4 fdm=marker
  521. * vim<600: sw=4 ts=4
  522. */