On Mon, Mar 13, 2023 at 5:48 PM Yann Ylavic <ylavic....@gmail.com> wrote: > > On Mon, Mar 13, 2023 at 5:42 PM Eric Covener <cove...@gmail.com> wrote: > > > > On Mon, Mar 13, 2023 at 12:31 PM Yann Ylavic <ylavic....@gmail.com> wrote: > > > > > > On Mon, Mar 13, 2023 at 5:25 PM Eric Covener <cove...@gmail.com> wrote: > > > > > > > > On Mon, Mar 13, 2023 at 12:05 PM Yann Ylavic <ylavic....@gmail.com> > > > > wrote: > > > > > > > > > > I could get where you want to with the attached patch (before you > > > > > disabled the test in r1908350). > > > > > It makes so that anything BNEG'ed overrides BCTLS, which can be useful > > > > > to let space through while still encoding controls. > > > > > I had to fix the local_bctls_nospace RewriteRule to not encode '/' > > > > > (not expected in the result) and add a missing '/' in the result too. > > > > > > > > > > > > + RewriteRule ^/modules/rewrite/escaping/local_bctls_nospace/(.*) > > > > /?$1 "[B= /,BNEG,BCTLS]" > > > > + [ > > > > "/modules/rewrite/escaping/local_bctls_nospace/foo/bar/%20baz/%0d" > > > > => "foo/bar/ baz/%0d"], # CTLS but allow space > > > > > > > > I'm not sure I understand this, / isn't a CTL so why do we need to > > > > exclude it with B+BNEG to have it passed through in the test? > > > > > > With BNEG everything not in B= is encoded, so "[B= ?,BNEG]" > > > (regardless of BCTLS) will encode '/'. > > > > Ah, I see. I thought/hoped it would become relative to CTLS. Maybe > > best to keep it simple. > > That's one way to implement it though, if BCTLS is there it could be a > negation of that only, no strong opinion on this.. > With the current implementation the BCTLS above is implied already, so > "[B= /,BNEG]" would be the same.
Maybe instead of BNEG we can implement BNE= (backref noescape charset), so one could "[B,BNE=/]" to encode everything but '/', or "[BCTLS,BNE= ?]" for controls but space, etc.. BNE= always wins over B=, and is meaningless alone. Something like the attached patch now?
Index: modules/mappers/mod_rewrite.c =================================================================== --- modules/mappers/mod_rewrite.c (revision 1908352) +++ modules/mappers/mod_rewrite.c (working copy) @@ -177,7 +177,7 @@ static const char* really_last_key = "rewrite_real #define RULEFLAG_QSLAST (1<<19) #define RULEFLAG_QSNONE (1<<20) /* programattic only */ #define RULEFLAG_ESCAPECTLS (1<<21) -#define RULEFLAG_ESCAPENEG (1<<22) +#define RULEFLAG_NOESCAPEBACKREF (1<<22) /* return code of the rewrite rule * the result may be escaped - or not @@ -329,6 +329,7 @@ typedef struct { int skip; /* number of next rules to skip */ int maxrounds; /* limit on number of loops with N flag */ char *escapes; /* specific backref escapes */ + char *noescapes; /* specific backref chars not to escape */ } rewriterule_entry; typedef struct { @@ -429,7 +430,9 @@ static apr_global_mutex_t *rewrite_mapr_lock_acqui static const char *rewritemap_mutex_type = "rewrite-map"; /* Optional functions imported from mod_ssl when loaded: */ -static char *escape_backref(apr_pool_t *p, const char *path, const char *escapeme, int flags); +static char *escape_backref(apr_pool_t *p, const char *path, + const char *escapeme, const char *noescapeme, + int flags); /* * +-------------------------------------------------------+ @@ -688,7 +691,9 @@ static APR_INLINE unsigned char *c2x(unsigned what * Escapes a backreference in a similar way as php's urlencode does. * Based on ap_os_escape_path in server/util.c */ -static char *escape_backref(apr_pool_t *p, const char *path, const char *escapeme, int flags) +static char *escape_backref(apr_pool_t *p, const char *path, + const char *escapeme, const char *noescapeme, + int flags) { char *copy = apr_palloc(p, 3 * strlen(path) + 1); const unsigned char *s = (const unsigned char *)path; @@ -695,12 +700,12 @@ static APR_INLINE unsigned char *c2x(unsigned what unsigned char *d = (unsigned char *)copy; int noplus = (flags & RULEFLAG_ESCAPENOPLUS) != 0; int ctls = (flags & RULEFLAG_ESCAPECTLS) != 0; - int neg = (flags & RULEFLAG_ESCAPENEG) != 0; unsigned char c; while ((c = *s)) { - if ((ctls ? !TEST_CHAR(c, T_VCHAR_OBSTEXT) : !escapeme) - || (escapeme && ((ap_strchr_c(escapeme, c) != NULL) ^ neg))) { + if (((ctls ? !TEST_CHAR(c, T_VCHAR_OBSTEXT) : !escapeme) + || (escapeme && ap_strchr_c(escapeme, c))) + && (!noescapeme || !ap_strchr_c(noescapeme, c))) { if (apr_isalnum(c) || c == '_') { *d++ = c; } @@ -2487,11 +2492,13 @@ static char *do_expand(char *input, rewrite_ctx *c if (bri->source && n < AP_MAX_REG_MATCH && bri->regmatch[n].rm_eo > bri->regmatch[n].rm_so) { span = bri->regmatch[n].rm_eo - bri->regmatch[n].rm_so; - if (entry && (entry->flags & RULEFLAG_ESCAPEBACKREF)) { + if (entry && (entry->flags & (RULEFLAG_ESCAPEBACKREF | + RULEFLAG_NOESCAPEBACKREF))) { /* escape the backreference */ char *tmp2, *tmp; tmp = apr_pstrmemdup(pool, bri->source + bri->regmatch[n].rm_so, span); - tmp2 = escape_backref(pool, tmp, entry->escapes, entry->flags); + tmp2 = escape_backref(pool, tmp, entry->escapes, entry->noescapes, + entry->flags); rewritelog(ctx->r, 5, ctx->perdir, "escaping backreference '%s' to '%s'", tmp, tmp2); @@ -3583,8 +3590,9 @@ static const char *cmd_rewriterule_setflag(apr_poo cfg->escapes = val; } } - else if (!strcasecmp(key, "NEG")) { - cfg->flags |= RULEFLAG_ESCAPENEG; + else if (!strcasecmp(key, "NE") && val && *val) { + cfg->flags |= RULEFLAG_NOESCAPEBACKREF; + cfg->noescapes = val; } else if (!strcasecmp(key, "NP") || !strcasecmp(key, "ackrefernoplus")) { cfg->flags |= RULEFLAG_ESCAPENOPLUS; @@ -3854,7 +3862,6 @@ static const char *cmd_rewriterule(cmd_parms *cmd, err, a1, a2, a3); } - /* arg3: optional flags field */ newrule->forced_mimetype = NULL; newrule->forced_handler = NULL; newrule->forced_responsecode = HTTP_MOVED_TEMPORARILY; @@ -3863,6 +3870,9 @@ static const char *cmd_rewriterule(cmd_parms *cmd, newrule->cookie = NULL; newrule->skip = 0; newrule->maxrounds = REWRITE_MAX_ROUNDS; + newrule->escapes = newrule->noescapes = NULL; + + /* arg3: optional flags field */ if (a3 != NULL) { if ((err = cmd_parseflagfield(cmd->pool, newrule, a3, cmd_rewriterule_setflag)) != NULL) { Index: framework/t/conf/extra.conf.in =================================================================== --- framework/t/conf/extra.conf.in (revision 1908352) +++ framework/t/conf/extra.conf.in (working copy) @@ -272,8 +272,9 @@ RewriteRule ^/modules/rewrite/escaping/local/(.*) /?$1 RewriteRule ^/modules/rewrite/escaping/local_b/(.*) /?$1 [B] RewriteRule ^/modules/rewrite/escaping/local_bctls/(.*) /?$1 [BCTLS] + RewriteRule ^/modules/rewrite/escaping/local_bctls_nospace/(.*) /?$1 "[BCTLS,BNE= ?]" RewriteRule ^/modules/rewrite/escaping/local_bctls_andslash/(.*) /?$1 [B=/,BCTLS] - RewriteRule ^/modules/rewrite/escaping/local_b_noslash/(.*) /?$1 [B=/,BNEG] + RewriteRule ^/modules/rewrite/escaping/local_b_noslash/(.*) /?$1 [B,BNE=/] RewriteRule ^/modules/rewrite/escaping/local_b_justslash/(.*) /?$1 [B=/] RewriteRule ^/modules/rewrite/escaping/redir/(.*) http://@SERVERNAME@:@PORT@/?$1 [R] RewriteRule ^/modules/rewrite/escaping/redir_ne/(.*) http://@SERVERNAME@:@PORT@/?$1 [R,NE] Index: framework/t/modules/rewrite.t =================================================================== --- framework/t/modules/rewrite.t (revision 1908352) +++ framework/t/modules/rewrite.t (working copy) @@ -38,6 +38,7 @@ my @bflags = ( # t/conf/extra.conf.in [ "/modules/rewrite/escaping/local_b/foo/bar/%20baz%0d" => "foo%2fbar%2f+baz%0d"], # this is why [B] sucks [ "/modules/rewrite/escaping/local_bctls/foo/bar/%20baz/%0d" => "foo/bar/+baz/%0d"], # spaces and ctls only + [ "/modules/rewrite/escaping/local_bctls_nospace/foo/bar/%20baz/%0d" => "foo/bar/ baz/%0d"], # ctls but no space [ "/modules/rewrite/escaping/local_bctls_andslash/foo/bar/%20baz/%0d" => "foo%2fbar%2f+baz%2f%0d"], # not realistic, but opt in to slashes [ "/modules/rewrite/escaping/local_b_noslash/foo/bar/%20baz/%0d" => "foo/bar/+baz/%0d"], # negate something from [B] [ "/modules/rewrite/escaping/local_b_justslash/foo/bar/%20baz/" => "foo%2fbar%2f baz%2f"], # test basic B=/