2 * ModSecurity for Apache 2.x, http://www.modsecurity.org/
3 * Copyright (c) 2004-2009 Breach Security, Inc. (http://www.breach.com/)
5 * This product is released under the terms of the General Public Licence,
6 * version 2 (GPLv2). Please refer to the file LICENSE (included with this
7 * distribution) which contains the complete text of the licence.
9 * There are special exceptions to the terms and conditions of the GPL
10 * as it is applied to this software. View the full text of the exception in
11 * file MODSECURITY_LICENSING_EXCEPTION in the directory of this software
14 * If any of the files related to licensing are missing or if you have any
15 * other questions related to licensing please contact Breach Security, Inc.
16 * directly using the email address support@breach.com.
19 #include "msc_release.h"
25 #include <sys/types.h>
31 * NOTE: Be careful as these can ONLY be used on static values for X.
32 * (i.e. VALID_HEX(c++) will NOT work)
34 #define VALID_HEX(X) (((X >= '0')&&(X <= '9')) || ((X >= 'a')&&(X <= 'f')) || ((X >= 'A')&&(X <= 'F')))
35 #define ISODIGIT(X) ((X >= '0')&&(X <= '7'))
37 #if (defined(WIN32) || defined(NETWARE))
38 /** Windows does not define all the octal modes */
51 #endif /* defined(WIN32 || NETWARE) */
56 int parse_boolean(const char *input) {
57 if (input == NULL) return -1;
58 if (strcasecmp(input, "on") == 0) return 1;
59 if (strcasecmp(input, "true") == 0) return 1;
60 if (strcasecmp(input, "1") == 0) return 1;
61 if (strcasecmp(input, "off") == 0) return 0;
62 if (strcasecmp(input, "false") == 0) return 0;
63 if (strcasecmp(input, "0") == 0) return 0;
69 * Parses a string that contains a name-value pair in the form "name=value".
70 * IMP1 It does not check for whitespace between tokens.
72 int parse_name_eq_value(apr_pool_t *mp, const char *input, char **name, char **value) {
75 if ((name == NULL)||(value == NULL)) return -1;
76 if (input == NULL) return 0;
82 while((*p != '=')&&(*p != '\0')) p++;
84 *name = (char *)input;
88 *name = apr_pstrmemdup(mp, input, p - input);
89 if (*name == NULL) return -1;
92 *value = apr_pstrdup(mp, p);
93 if (*value == NULL) return -1;
100 * IMP1 Assumes NUL-terminated
102 char *url_encode(apr_pool_t *mp, char *input, unsigned int input_len, int *changed) {
108 len = input_len * 3 + 1;
109 d = rval = apr_palloc(mp, len);
110 if (rval == NULL) return NULL;
112 /* ENH Only encode the characters that really need to be encoded. */
114 for(i = 0; i < input_len; i++) {
115 unsigned char c = input[i];
121 if ( (c == 42) || ((c >= 48)&&(c <= 57)) || ((c >= 65)&&(c <= 90))
122 || ((c >= 97)&&(c <= 122))
127 c2x(c, (unsigned char *)d);
139 * Appends an URL-encoded version of the source string to the
140 * destination string, but makes sure that no more than "maxlen"
143 char *strnurlencat(char *destination, char *source, unsigned int maxlen) {
145 char *d = destination;
147 /* ENH Only encode the characters that really need to be encoded. */
149 /* Advance to the end of destination string. */
150 while(*d != '\0') d++;
152 /* Loop while there's bytes in the source string or
153 * until we reach the output limit.
155 while((*s != '\0')&&(maxlen > 0)) {
156 unsigned char c = *s;
162 if ( (c == 42) || ((c >= 48)&&(c <= 57)) || ((c >= 65)&&(c <= 90))
163 || ((c >= 97)&&(c <= 122))
170 c2x(c, (unsigned char *)d);
174 /* If there's not enough room for the encoded
192 char *file_basename(apr_pool_t *mp, const char *filename) {
195 if (filename == NULL) return NULL;
196 d = apr_pstrdup(mp, filename);
197 if (d == NULL) return NULL;
200 if (p != NULL) d = p + 1;
201 p = strrchr(d, '\\');
202 if (p != NULL) d = p + 1;
211 char *file_dirname(apr_pool_t *p, const char *filename) {
214 if (filename == NULL) return NULL;
215 b = apr_pstrdup(p, filename);
216 if (b == NULL) return NULL;
220 d = strrchr(c, '\\');
221 if (d != NULL) *d = '\0';
224 d = strrchr(b, '\\');
225 if (d != NULL) *d = '\0';
231 char *file_dirname(apr_pool_t *p, const char *filename) {
234 if (filename == NULL) return NULL;
235 b = apr_pstrdup(p, filename);
236 if (b == NULL) return NULL;
239 if (c != NULL) *c = '\0';
249 int hex2bytes_inplace(unsigned char *data, int len) {
250 unsigned char *d = data;
253 if ((data == NULL)||(len == 0)) return 0;
255 for(i = 0; i <= len - 2; i += 2) {
256 *d++ = x2c(&data[i]);
265 * Converts a series of bytes into its hexadecimal
268 char *bytes2hex(apr_pool_t *pool, unsigned char *data, int len) {
269 static unsigned char b2hex[] = "0123456789abcdef";
273 hex = apr_palloc(pool, (len * 2) + 1);
274 if (hex == NULL) return NULL;
277 for(i = 0; i < len; i++) {
278 hex[j++] = b2hex[data[i] >> 4];
279 hex[j++] = b2hex[data[i] & 0x0f];
289 int is_token_char(unsigned char c) {
290 /* ENH Is the performance important at all? We could use a table instead. */
292 /* CTLs not allowed */
293 if ((c <= 32)||(c >= 127)) return 0;
320 int remove_lf_crlf_inplace(char *text) {
324 if (text == NULL) return -1;
332 if (*(p - 1) == '\n') {
335 if (*(p - 2) == '\r') {
346 * Converts a byte given as its hexadecimal representation
347 * into a proper byte. Handles uppercase and lowercase letters
348 * but does not check for overflows.
350 unsigned char x2c(unsigned char *what) {
351 register unsigned char digit;
353 digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A') + 10 : (what[0] - '0'));
355 digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10 : (what[1] - '0'));
361 * Converts a single hexadecimal digit into a decimal value.
363 unsigned char xsingle2c(unsigned char *what) {
364 register unsigned char digit;
366 digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A') + 10 : (what[0] - '0'));
374 char *guess_tmp_dir(apr_pool_t *p) {
375 char *filename = NULL;
377 /* ENH Use apr_temp_dir_get instead. */
380 filename = apr_pcalloc(p, 256);
381 if (filename == NULL) return "";
382 if (GetTempPath(255, filename) != 0) return filename;
385 filename = getenv("TMPDIR");
386 if (filename != NULL) return filename;
388 filename = getenv("TEMP");
389 if (filename != NULL) return filename;
391 filename = getenv("TMP");
392 if (filename != NULL) return filename;
406 char *current_logtime(apr_pool_t *mp) {
411 apr_time_exp_lt(&t, apr_time_now());
413 apr_strftime(tstr, &len, 80, "%d/%b/%Y:%H:%M:%S ", &t);
414 apr_snprintf(tstr + strlen(tstr), 80 - strlen(tstr), "%c%.2d%.2d",
415 t.tm_gmtoff < 0 ? '-' : '+',
416 t.tm_gmtoff / (60 * 60), t.tm_gmtoff % (60 * 60));
417 return apr_pstrdup(mp, tstr);
423 char *current_filetime(apr_pool_t *mp) {
428 apr_time_exp_lt(&t, apr_time_now());
430 apr_strftime(tstr, &len, 80, "%Y%m%d-%H%M%S", &t);
431 return apr_pstrdup(mp, tstr);
437 int msc_mkstemp_ex(char *template, int mode) {
438 /* ENH Use apr_file_mktemp instead. */
440 #if !(defined(WIN32)||defined(NETWARE))
441 return mkstemp(template);
443 if (mktemp(template) == NULL) return -1;
444 return open(template, O_WRONLY | O_APPEND | O_CREAT | O_BINARY, mode);
451 int msc_mkstemp(char *template) {
452 return msc_mkstemp_ex(template, CREATEMODE_UNISTD);
456 * Converts the input string to lowercase (in-place).
458 char *strtolower_inplace(unsigned char *str) {
459 unsigned char *c = str;
461 if (str == NULL) return NULL;
472 * Converts a single byte into its hexadecimal representation.
473 * Will overwrite two bytes at the destination.
475 unsigned char *c2x(unsigned what, unsigned char *where) {
476 static const char c2x_table[] = "0123456789abcdef";
479 *where++ = c2x_table[what >> 4];
480 *where++ = c2x_table[what & 0x0f];
485 char *log_escape(apr_pool_t *mp, const char *text) {
486 return _log_escape(mp, (const unsigned char *)text, text ? strlen(text) : 0, 1, 0);
489 char *log_escape_nq(apr_pool_t *mp, const char *text) {
490 return _log_escape(mp, (const unsigned char *)text, text ? strlen(text) : 0, 0, 0);
493 char *log_escape_ex(apr_pool_t *mp, const char *text, unsigned long int text_length) {
494 return _log_escape(mp, (const unsigned char *)text, text_length, 1, 0);
497 char *log_escape_nq_ex(apr_pool_t *mp, const char *text, unsigned long int text_length) {
498 return _log_escape(mp, (const unsigned char *)text, text_length, 0, 0);
501 char *log_escape_header_name(apr_pool_t *mp, const char *text) {
502 return _log_escape(mp, (const unsigned char *)text, text ? strlen(text) : 0, 0, 1);
505 char *log_escape_raw(apr_pool_t *mp, const unsigned char *text, unsigned long int text_length) {
506 unsigned char *ret = apr_palloc(mp, text_length * 4 + 1);
507 unsigned long int i, j;
509 for (i = 0, j = 0; i < text_length; i++, j += 4) {
512 c2x(text[i], ret+j+2);
514 ret[text_length * 4] = '\0';
520 * Transform text to ASCII printable or hex escaped
522 char *log_escape_hex(apr_pool_t *mp, const unsigned char *text, unsigned long int text_length) {
523 unsigned char *ret = apr_palloc(mp, text_length * 4 + 1);
524 unsigned long int i, j;
526 for (i = 0, j = 0; i < text_length; i++) {
527 if ( (text[i] == '"')
534 c2x(text[i], ret+j+2);
548 * Transform input into a form safe for logging.
550 char *_log_escape(apr_pool_t *mp, const unsigned char *input, unsigned long int input_len,
551 int escape_quotes, int escape_colon)
553 unsigned char *d = NULL;
557 if (input == NULL) return NULL;
559 ret = apr_palloc(mp, input_len * 4 + 1);
560 if (ret == NULL) return NULL;
561 d = (unsigned char *)ret;
564 while(i < input_len) {
607 if ((input[i] <= 0x1f)||(input[i] >= 0x7f)) {
627 * JavaScript decoding.
628 * IMP1 Assumes NUL-terminated
631 int js_decode_nonstrict_inplace(unsigned char *input, long int input_len) {
632 unsigned char *d = (unsigned char *)input;
635 if (input == NULL) return -1;
638 while (i < input_len) {
639 if (input[i] == '\\') {
640 /* Character is an escape. */
642 if ( (i + 5 < input_len) && (input[i + 1] == 'u')
643 && (VALID_HEX(input[i + 2])) && (VALID_HEX(input[i + 3]))
644 && (VALID_HEX(input[i + 4])) && (VALID_HEX(input[i + 5])) )
648 /* Use only the lower byte. */
649 *d = x2c(&input[i + 4]);
651 /* Full width ASCII (ff01 - ff5e) needs 0x20 added */
652 if ( (*d > 0x00) && (*d < 0x5f)
653 && ((input[i + 2] == 'f') || (input[i + 2] == 'F'))
654 && ((input[i + 3] == 'f') || (input[i + 3] == 'F')))
663 else if ( (i + 3 < input_len) && (input[i + 1] == 'x')
664 && VALID_HEX(input[i + 2]) && VALID_HEX(input[i + 3])) {
666 *d++ = x2c(&input[i + 2]);
670 else if ((i + 1 < input_len) && ISODIGIT(input[i + 1])) {
671 /* \OOO (only one byte, \000 - \377) */
675 while((i + 1 + j < input_len)&&(j < 3)) {
676 buf[j] = input[i + 1 + j];
678 if (!ISODIGIT(input[i + 1 + j])) break;
683 /* Do not use 3 characters if we will be > 1 byte */
684 if ((j == 3) && (buf[0] > '3')) {
688 *d++ = (unsigned char)strtol(buf, NULL, 8);
693 else if (i + 1 < input_len) {
695 unsigned char c = input[i + 1];
696 switch(input[i + 1]) {
718 /* The remaining (\?,\\,\',\") are just a removal
719 * of the escape char which is default.
728 /* Not enough bytes */
729 while(i < input_len) {
748 * IMP1 Assumes NUL-terminated
750 int urldecode_uni_nonstrict_inplace_ex(unsigned char *input, long int input_len, int *changed) {
751 unsigned char *d = input;
756 if (input == NULL) return -1;
759 while (i < input_len) {
760 if (input[i] == '%') {
761 /* Character is a percent sign. */
763 if ((i + 1 < input_len)&&( (input[i + 1] == 'u')||(input[i + 1] == 'U') )) {
764 /* IIS-specific %u encoding. */
765 if (i + 5 < input_len) {
766 /* We have at least 4 data bytes. */
767 if ( (VALID_HEX(input[i + 2]))&&(VALID_HEX(input[i + 3]))
768 &&(VALID_HEX(input[i + 4]))&&(VALID_HEX(input[i + 5])) )
770 /* We first make use of the lower byte here, ignoring the higher byte. */
771 *d = x2c(&input[i + 4]);
773 /* Full width ASCII (ff01 - ff5e) needs 0x20 added */
774 if ( (*d > 0x00) && (*d < 0x5f)
775 && ((input[i + 2] == 'f') || (input[i + 2] == 'F'))
776 && ((input[i + 3] == 'f') || (input[i + 3] == 'F')))
786 /* Invalid data, skip %u. */
792 /* Not enough bytes (4 data bytes), skip %u. */
799 /* Standard URL encoding. */
801 /* Are there enough bytes available? */
802 if (i + 2 < input_len) {
805 /* Decode a %xx combo only if it is valid.
807 char c1 = input[i + 1];
808 char c2 = input[i + 2];
810 if (VALID_HEX(c1) && VALID_HEX(c2)) {
811 *d++ = x2c(&input[i + 1]);
816 /* Not a valid encoding, skip this % */
821 /* Not enough bytes available, skip this % */
828 /* Character is not a percent sign. */
829 if (input[i] == '+') {
848 * IMP1 Assumes NUL-terminated
850 int urldecode_nonstrict_inplace_ex(unsigned char *input, long int input_len, int *invalid_count, int *changed) {
851 unsigned char *d = (unsigned char *)input;
856 if (input == NULL) return -1;
859 while (i < input_len) {
860 if (input[i] == '%') {
861 /* Character is a percent sign. */
863 /* Are there enough bytes available? */
864 if (i + 2 < input_len) {
865 char c1 = input[i + 1];
866 char c2 = input[i + 2];
868 if (VALID_HEX(c1) && VALID_HEX(c2)) {
869 /* Valid encoding - decode it. */
870 *d++ = x2c(&input[i + 1]);
875 /* Not a valid encoding, skip this % */
881 /* Not enough bytes available, copy the raw bytes. */
887 /* Character is not a percent sign. */
888 if (input[i] == '+') {
906 * IMP1 Assumes NUL-terminated
908 int html_entities_decode_inplace(apr_pool_t *mp, unsigned char *input, int input_len) {
909 unsigned char *d = input;
912 if ((input == NULL)||(input_len <= 0)) return 0;
915 while((i < input_len)&&(count < input_len)) {
918 /* Require an ampersand and at least one character to
919 * start looking into the entity.
921 if ((input[i] == '&')&&(i + 1 < input_len)) {
924 if (input[j] == '#') {
925 /* Numerical entity. */
928 if (!(j + 1 < input_len)) goto HTML_ENT_OUT; /* Not enough bytes. */
931 if ((input[j] == 'x')||(input[j] == 'X')) {
932 /* Hexadecimal entity. */
935 if (!(j + 1 < input_len)) goto HTML_ENT_OUT; /* Not enough bytes. */
936 j++; /* j is the position of the first digit now. */
939 while((j < input_len)&&(isxdigit(input[j]))) j++;
940 if (j > k) { /* Do we have at least one digit? */
941 /* Decode the entity. */
942 char *x = apr_pstrmemdup(mp, (const char *)&input[k], j - k);
943 *d++ = (unsigned char)strtol(x, NULL, 16);
946 /* Skip over the semicolon if it's there. */
947 if ((j < input_len)&&(input[j] == ';')) i = j + 1;
955 /* Decimal entity. */
957 while((j < input_len)&&(isdigit(input[j]))) j++;
958 if (j > k) { /* Do we have at least one digit? */
959 /* Decode the entity. */
960 char *x = apr_pstrmemdup(mp, (const char *)&input[k], j - k);
961 *d++ = (unsigned char)strtol(x, NULL, 10);
964 /* Skip over the semicolon if it's there. */
965 if ((j < input_len)&&(input[j] == ';')) i = j + 1;
977 while((j < input_len)&&(isalnum(input[j]))) j++;
978 if (j > k) { /* Do we have at least one digit? */
979 char *x = apr_pstrmemdup(mp, (const char *)&input[k], j - k);
981 /* Decode the entity. */
982 /* ENH What about others? */
983 if (strcasecmp(x, "quot") == 0) *d++ = '"';
985 if (strcasecmp(x, "amp") == 0) *d++ = '&';
987 if (strcasecmp(x, "lt") == 0) *d++ = '<';
989 if (strcasecmp(x, "gt") == 0) *d++ = '>';
991 if (strcasecmp(x, "nbsp") == 0) *d++ = NBSP;
993 /* We do no want to convert this entity, copy the raw data over. */
1000 /* Skip over the semicolon if it's there. */
1001 if ((j < input_len)&&(input[j] == ';')) i = j + 1;
1011 for(z = 0; ((z < copy) && (count < input_len)); z++) {
1024 * IMP1 Assumes NUL-terminated
1026 int ansi_c_sequences_decode_inplace(unsigned char *input, int input_len) {
1027 unsigned char *d = input;
1031 while(i < input_len) {
1032 if ((input[i] == '\\')&&(i + 1 < input_len)) {
1035 switch(input[i + 1]) {
1071 if (c != -1) i += 2;
1073 /* Hexadecimal or octal? */
1075 if ((input[i + 1] == 'x')||(input[i + 1] == 'X')) {
1077 if ((i + 3 < input_len)&&(isxdigit(input[i + 2]))&&(isxdigit(input[i + 3]))) {
1079 c = x2c(&input[i + 2]);
1082 /* Invalid encoding, do nothing. */
1086 if (ISODIGIT(input[i + 1])) { /* Octal. */
1090 while((i + 1 + j < input_len)&&(j < 3)) {
1091 buf[j] = input[i + 1 + j];
1093 if (!ISODIGIT(input[i + 1 + j])) break;
1098 c = strtol(buf, NULL, 8);
1105 /* Didn't recognise encoding, copy raw bytes. */
1106 *d++ = input[i + 1];
1110 /* Converted the encoding. */
1115 /* Input character not a backslash, copy it. */
1128 * IMP1 Assumes NUL-terminated
1130 int normalise_path_inplace(unsigned char *input, int input_len, int win, int *changed) {
1131 unsigned char *d = input;
1137 while ((i < input_len)&&(count < input_len)) {
1140 /* Convert backslash to forward slash on Windows only. */
1141 if ((win)&&(c == '\\')) {
1147 /* Is there a directory back-reference? Yes, we
1148 * require at least 5 prior bytes here. That's on
1151 if ((count >= 5)&&(*(d - 1) == '.')&&(*(d - 2) == '.')&&(*(d - 3) == '/')) {
1152 unsigned char *cd = d - 4;
1153 int ccount = count - 4;
1157 /* Go back until we reach the beginning or a forward slash. */
1158 while ((ccount > 0)&&(*cd != '/')) {
1168 /* Is there a directory self-reference? */
1169 if ((count >= 2)&&(*(d - 1) == '.')&&(*(d - 2) == '/')) {
1170 /* Ignore the last two bytes. */
1175 /* Or are there just multiple occurences of forward slash? */
1176 if ((count >= 1)&&(*(d - 1) == '/')) {
1177 /* Ignore the last one byte. */
1184 /* Copy the byte over. */
1195 char *modsec_build(apr_pool_t *mp) {
1196 return apr_psprintf(mp, "%02i%02i%02i%1i%02i",
1197 atoi(MODSEC_VERSION_MAJOR),
1198 atoi(MODSEC_VERSION_MINOR),
1199 atoi(MODSEC_VERSION_MAINT),
1200 get_modsec_build_type(NULL),
1201 atoi(MODSEC_VERSION_RELEASE));
1204 int is_empty_string(const char *string) {
1207 if (string == NULL) return 1;
1209 for(i = 0; string[i] != '\0'; i++) {
1210 if (!isspace(string[i])) {
1218 char *resolve_relative_path(apr_pool_t *pool, const char *parent_filename, const char *filename) {
1219 if (filename == NULL) return NULL;
1220 // TODO Support paths on operating systems other than Unix.
1221 if (filename[0] == '/') return (char *)filename;
1223 return apr_pstrcat(pool, apr_pstrndup(pool, parent_filename,
1224 strlen(parent_filename) - strlen(apr_filepath_name_get(parent_filename))),
1229 * Decode a string that contains CSS-escaped characters.
1232 * http://www.w3.org/TR/REC-CSS2/syndata.html#q4
1233 * http://www.unicode.org/roadmaps/
1235 int css_decode_inplace(unsigned char *input, long int input_len) {
1236 unsigned char *d = (unsigned char *)input;
1237 long int i, j, count;
1239 if (input == NULL) return -1;
1242 while (i < input_len) {
1244 /* Is the character a backslash? */
1245 if (input[i] == '\\') {
1247 /* Is there at least one more byte? */
1248 if (i + 1 < input_len) {
1249 i++; /* We are not going to need the backslash. */
1251 /* Check for 1-6 hex characters following the backslash */
1254 && (i + j < input_len)
1255 && (VALID_HEX(input[i + j])))
1260 if (j > 0) { /* We have at least one valid hexadecimal character. */
1263 /* For now just use the last two bytes. */
1265 /* Number of hex characters */
1267 *d++ = xsingle2c(&input[i]);
1272 /* Use the last two from the end. */
1273 *d++ = x2c(&input[i + j - 2]);
1277 /* Use the last two from the end, but request
1278 * a full width check.
1280 *d = x2c(&input[i + j - 2]);
1285 /* Use the last two from the end, but request
1286 * a full width check if the number is greater
1287 * or equal to 0xFFFF.
1289 *d = x2c(&input[i + j - 2]);
1291 /* Do full check if first byte is 0 */
1292 if (input[i] == '0') {
1301 /* Use the last two from the end, but request
1302 * a full width check if the number is greater
1303 * or equal to 0xFFFF.
1305 *d = x2c(&input[i + j - 2]);
1307 /* Do full check if first/second bytes are 0 */
1308 if ( (input[i] == '0')
1309 && (input[i + 1] == '0')
1319 /* Full width ASCII (0xff01 - 0xff5e) needs 0x20 added */
1321 if ( (*d > 0x00) && (*d < 0x5f)
1322 && ((input[i + j - 3] == 'f') ||
1323 (input[i + j - 3] == 'F'))
1324 && ((input[i + j - 4] == 'f') ||
1325 (input[i + j - 4] == 'F')))
1333 /* We must ignore a single whitespace after a hex escape */
1334 if ((i + j < input_len) && isspace(input[i + j])) {
1343 /* No hexadecimal digits after backslash */
1344 else if (input[i] == '\n') {
1345 /* A newline character following backslash is ignored. */
1349 /* The character after backslash is not a hexadecimal digit, nor a newline. */
1351 /* Use one character after backslash as is. */
1357 /* No characters after backslash. */
1359 /* Do not include backslash in output (continuation to nothing) */
1364 /* Character is not a backslash. */
1366 /* Copy one normal character to output. */
1372 /* Terminate output string. */
1379 * Translate UNIX octal umask/mode to APR apr_fileperms_t
1381 apr_fileperms_t mode2fileperms(int mode) {
1382 apr_fileperms_t perms = 0;
1384 if (mode & S_IXOTH) perms |= APR_WEXECUTE;
1385 if (mode & S_IWOTH) perms |= APR_WWRITE;
1386 if (mode & S_IROTH) perms |= APR_WREAD;
1387 if (mode & S_IXGRP) perms |= APR_GEXECUTE;
1388 if (mode & S_IWGRP) perms |= APR_GWRITE;
1389 if (mode & S_IRGRP) perms |= APR_GREAD;
1390 if (mode & S_IXUSR) perms |= APR_UEXECUTE;
1391 if (mode & S_IWUSR) perms |= APR_UWRITE;
1392 if (mode & S_IRUSR) perms |= APR_UREAD;
1393 if (mode & S_ISVTX) perms |= APR_WSTICKY;
1394 if (mode & S_ISGID) perms |= APR_GSETID;
1395 if (mode & S_ISUID) perms |= APR_USETID;