diff options
Diffstat (limited to 'subversion/libsvn_subr/config_file.c')
-rw-r--r-- | subversion/libsvn_subr/config_file.c | 321 |
1 files changed, 261 insertions, 60 deletions
diff --git a/subversion/libsvn_subr/config_file.c b/subversion/libsvn_subr/config_file.c index e4a5936d78342..bd2ec828a5425 100644 --- a/subversion/libsvn_subr/config_file.c +++ b/subversion/libsvn_subr/config_file.c @@ -25,6 +25,8 @@ #include <apr_lib.h> #include <apr_env.h> +#include <apr_tables.h> + #include "config_impl.h" #include "svn_io.h" #include "svn_types.h" @@ -37,8 +39,9 @@ #include "svn_user.h" #include "svn_ctype.h" -#include "svn_private_config.h" +#include "private/svn_config_private.h" #include "private/svn_subr_private.h" +#include "svn_private_config.h" #ifdef __HAIKU__ # include <FindDirectory.h> @@ -52,8 +55,9 @@ /* File parsing context */ typedef struct parse_context_t { - /* This config struct */ - svn_config_t *cfg; + /* The configuration constructor. */ + svn_config__constructor_t *constructor; + void *constructor_baton; /* The stream struct */ svn_stream_t *stream; @@ -64,10 +68,14 @@ typedef struct parse_context_t /* Emulate an ungetc */ int ungotten_char; + /* We're currently parsing a section */ + svn_boolean_t in_section; + /* Temporary strings */ svn_stringbuf_t *section; svn_stringbuf_t *option; svn_stringbuf_t *value; + svn_stringbuf_t *line_read; /* Parser buffer for getc() to avoid call overhead into several libraries for every character */ @@ -80,6 +88,109 @@ typedef struct parse_context_t } parse_context_t; +/* Config representation constructor */ +struct svn_config__constructor_t +{ + /* Constructor callbacks; see docs for svn_config__constructor_create. */ + svn_config__open_section_fn open_section; + svn_config__close_section_fn close_section; + svn_config__add_value_fn add_value; +}; + +svn_config__constructor_t * +svn_config__constructor_create( + svn_config__open_section_fn open_section_callback, + svn_config__close_section_fn close_section_callback, + svn_config__add_value_fn add_value_callback, + apr_pool_t *result_pool) +{ + svn_config__constructor_t *ctor = apr_palloc(result_pool, sizeof(*ctor)); + ctor->open_section = open_section_callback; + ctor->close_section = close_section_callback; + ctor->add_value = add_value_callback; + return ctor; +} + +/* Called after we've parsed a section name and before we start + parsing any options within that section. */ +static APR_INLINE svn_error_t * +open_section(parse_context_t *ctx, svn_boolean_t *stop) +{ + if (ctx->constructor->open_section) + { + svn_error_t *err = ctx->constructor->open_section( + ctx->constructor_baton, ctx->section); + if (err) + { + if (err->apr_err == SVN_ERR_CEASE_INVOCATION) + { + *stop = TRUE; + svn_error_clear(err); + return SVN_NO_ERROR; + } + else + return svn_error_trace(err); + } + } + + *stop = FALSE; + ctx->in_section = TRUE; + return SVN_NO_ERROR; +} + +/* Called after we've parsed all options within a section and before + we start parsing the next section. */ +static APR_INLINE svn_error_t * +close_section(parse_context_t *ctx, svn_boolean_t *stop) +{ + ctx->in_section = FALSE; + if (ctx->constructor->close_section) + { + svn_error_t *err = ctx->constructor->close_section( + ctx->constructor_baton, ctx->section); + if (err) + { + if (err->apr_err == SVN_ERR_CEASE_INVOCATION) + { + *stop = TRUE; + svn_error_clear(err); + return SVN_NO_ERROR; + } + else + return svn_error_trace(err); + } + } + + *stop = FALSE; + return SVN_NO_ERROR; +} + +/* Called every tyme we've parsed a complete (option, value) pair. */ +static APR_INLINE svn_error_t * +add_value(parse_context_t *ctx, svn_boolean_t *stop) +{ + if (ctx->constructor->add_value) + { + svn_error_t *err = ctx->constructor->add_value( + ctx->constructor_baton, ctx->section, + ctx->option, ctx->value); + if (err) + { + if (err->apr_err == SVN_ERR_CEASE_INVOCATION) + { + *stop = TRUE; + svn_error_clear(err); + return SVN_NO_ERROR; + } + else + return svn_error_trace(err); + } + } + + *stop = FALSE; + return SVN_NO_ERROR; +} + /* Emulate getc() because streams don't support it. * @@ -161,6 +272,57 @@ parser_ungetc(parse_context_t *ctx, int c) return SVN_NO_ERROR; } +/* Read from CTX to the end of the line (or file) and write the data + into LINE. Previous contents of *LINE will be overwritten. + Returns the char that ended the line in *C; the char is either + EOF or newline. */ +static svn_error_t * +parser_get_line(parse_context_t *ctx, svn_stringbuf_t *line, int *c) +{ + int ch; + svn_stringbuf_setempty(line); + + /* Loop until EOF of newline. There is one extra iteration per + * parser buffer refill. The initial parser_getc() also ensures + * that the unget buffer is emptied. */ + SVN_ERR(parser_getc(ctx, &ch)); + while (ch != '\n' && ch != EOF) + { + const char *start, *newline; + + /* Save the char we just read. */ + svn_stringbuf_appendbyte(line, ch); + + /* Scan the parser buffer for NL. */ + start = ctx->parser_buffer + ctx->buffer_pos; + newline = memchr(start, '\n', ctx->buffer_size - ctx->buffer_pos); + if (newline) + { + /* NL found before the end of the of the buffer. */ + svn_stringbuf_appendbytes(line, start, newline - start); + ch = '\n'; + ctx->buffer_pos = newline - ctx->parser_buffer + 1; + break; + } + else + { + /* Hit the end of the buffer. This may be either due to + * buffer size or EOF. In any case, all (remaining) current + * buffer data is part of the line to read. */ + const char *end = ctx->parser_buffer + ctx->buffer_size; + ctx->buffer_pos = ctx->buffer_size; + + svn_stringbuf_appendbytes(line, start, end - start); + } + + /* refill buffer, check for EOF */ + SVN_ERR(parser_getc_plain(ctx, &ch)); + } + + *c = ch; + return SVN_NO_ERROR; +} + /* Eat chars from STREAM until encounter non-whitespace, newline, or EOF. Set *PCOUNT to the number of characters eaten, not counting the last one, and return the last char read (the one that caused the @@ -245,23 +407,16 @@ skip_bom(parse_context_t *ctx) return SVN_NO_ERROR; } -/* Parse a single option value */ +/* Parse continuation lines of single option value if they exist. Append + * their contents, including *PCH, to CTX->VALUE. Return the first char + * of the next line or EOF in *PCH. */ static svn_error_t * -parse_value(int *pch, parse_context_t *ctx) +parse_value_continuation_lines(int *pch, parse_context_t *ctx) { svn_boolean_t end_of_val = FALSE; - int ch; + svn_boolean_t stop; + int ch = *pch; - /* Read the first line of the value */ - svn_stringbuf_setempty(ctx->value); - SVN_ERR(parser_getc(ctx, &ch)); - while (ch != EOF && ch != '\n') - /* last ch seen was ':' or '=' in parse_option. */ - { - const char char_from_int = (char)ch; - svn_stringbuf_appendbyte(ctx->value, char_from_int); - SVN_ERR(parser_getc(ctx, &ch)); - } /* Leading and trailing whitespace is ignored. */ svn_stringbuf_strip_whitespace(ctx->value); @@ -273,8 +428,9 @@ parse_value(int *pch, parse_context_t *ctx) { /* At end of file. The value is complete, there can't be any continuation lines. */ - svn_config_set(ctx->cfg, ctx->section->data, - ctx->option->data, ctx->value->data); + SVN_ERR(add_value(ctx, &stop)); + if (stop) + return SVN_NO_ERROR; break; } else @@ -310,16 +466,15 @@ parse_value(int *pch, parse_context_t *ctx) else { /* This is a continuation line. Read it. */ - svn_stringbuf_appendbyte(ctx->value, ' '); + SVN_ERR(parser_ungetc(ctx, ch)); + SVN_ERR(parser_get_line(ctx, ctx->line_read, &ch)); - while (ch != EOF && ch != '\n') - { - const char char_from_int = (char)ch; - svn_stringbuf_appendbyte(ctx->value, char_from_int); - SVN_ERR(parser_getc(ctx, &ch)); - } /* Trailing whitespace is ignored. */ - svn_stringbuf_strip_whitespace(ctx->value); + svn_stringbuf_strip_whitespace(ctx->line_read); + + svn_stringbuf_appendbyte(ctx->value, ' '); + svn_stringbuf_appendbytes(ctx->value, ctx->line_read->data, + ctx->line_read->len); } } } @@ -336,17 +491,33 @@ parse_option(int *pch, parse_context_t *ctx, apr_pool_t *scratch_pool) { svn_error_t *err = SVN_NO_ERROR; int ch; + char *equals, *separator; + + /* Read the first line. */ + parser_ungetc(ctx, *pch); /* Yes, the first char is relevant. */ + SVN_ERR(parser_get_line(ctx, ctx->line_read, &ch)); - svn_stringbuf_setempty(ctx->option); - ch = *pch; /* Yes, the first char is relevant. */ - while (ch != EOF && ch != ':' && ch != '=' && ch != '\n') + /* Extract the option name from it. */ + equals = strchr(ctx->line_read->data, '='); + if (equals) + { + /* Efficiently look for a colon separator prior to the equals sign. + * Since there is no strnchr, we limit the search by temporarily + * limiting the string. */ + char *colon; + *equals = 0; + colon = strchr(ctx->line_read->data, ':'); + *equals = '='; + + separator = colon ? colon : equals; + } + else { - const char char_from_int = (char)ch; - svn_stringbuf_appendbyte(ctx->option, char_from_int); - SVN_ERR(parser_getc(ctx, &ch)); + /* No '=' separator so far. Look for colon. */ + separator = strchr(ctx->line_read->data, ':'); } - if (ch != ':' && ch != '=') + if (!separator) { ch = EOF; err = svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL, @@ -356,8 +527,22 @@ parse_option(int *pch, parse_context_t *ctx, apr_pool_t *scratch_pool) else { /* Whitespace around the name separator is ignored. */ - svn_stringbuf_strip_whitespace(ctx->option); - err = parse_value(&ch, ctx); + const char *end = separator; + while (svn_ctype_isspace(*--end)) + ; + while (svn_ctype_isspace(*++separator)) + ; + + /* Extract the option. It can't contain whitespace chars. */ + svn_stringbuf_setempty(ctx->option); + svn_stringbuf_appendbytes(ctx->option, ctx->line_read->data, + end + 1 - ctx->line_read->data); + + /* Extract the first line of the value. */ + end = ctx->line_read->data + ctx->line_read->len; + svn_stringbuf_setempty(ctx->value); + svn_stringbuf_appendbytes(ctx->value, separator, end - separator); + err = parse_value_continuation_lines(&ch, ctx); } *pch = ch; @@ -379,17 +564,12 @@ parse_section_name(int *pch, parse_context_t *ctx, { svn_error_t *err = SVN_NO_ERROR; int ch; + char *terminal; - svn_stringbuf_setempty(ctx->section); - SVN_ERR(parser_getc(ctx, &ch)); - while (ch != EOF && ch != ']' && ch != '\n') - { - const char char_from_int = (char)ch; - svn_stringbuf_appendbyte(ctx->section, char_from_int); - SVN_ERR(parser_getc(ctx, &ch)); - } + SVN_ERR(parser_get_line(ctx, ctx->section, &ch)); + terminal = strchr(ctx->section->data, ']'); - if (ch != ']') + if (!terminal) { ch = EOF; err = svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL, @@ -399,9 +579,8 @@ parse_section_name(int *pch, parse_context_t *ctx, else { /* Everything from the ']' to the end of the line is ignored. */ - SVN_ERR(skip_to_eoln(ctx, &ch)); - if (ch != EOF) - ++ctx->line; + *terminal = 0; + ctx->section->len = terminal - ctx->section->data; } *pch = ch; @@ -531,6 +710,7 @@ svn_config__shallow_replace_section(svn_config_t *target, } + svn_error_t * svn_config__parse_file(svn_config_t *cfg, const char *file, svn_boolean_t must_exist, apr_pool_t *result_pool) @@ -554,7 +734,12 @@ svn_config__parse_file(svn_config_t *cfg, const char *file, SVN_ERR(err); stream = svn_stream_from_aprfile2(apr_file, FALSE, scratch_pool); - err = svn_config__parse_stream(cfg, stream, result_pool, scratch_pool); + err = svn_config__parse_stream(stream, + svn_config__constructor_create( + NULL, NULL, + svn_config__default_add_value_fn, + scratch_pool), + cfg, scratch_pool); if (err != SVN_NO_ERROR) { @@ -571,21 +756,27 @@ svn_config__parse_file(svn_config_t *cfg, const char *file, } svn_error_t * -svn_config__parse_stream(svn_config_t *cfg, svn_stream_t *stream, - apr_pool_t *result_pool, apr_pool_t *scratch_pool) +svn_config__parse_stream(svn_stream_t *stream, + svn_config__constructor_t *constructor, + void *constructor_baton, + apr_pool_t *scratch_pool) { parse_context_t *ctx; + svn_boolean_t stop; int ch, count; ctx = apr_palloc(scratch_pool, sizeof(*ctx)); - ctx->cfg = cfg; + ctx->constructor = constructor; + ctx->constructor_baton = constructor_baton; ctx->stream = stream; ctx->line = 1; ctx->ungotten_char = EOF; + ctx->in_section = FALSE; ctx->section = svn_stringbuf_create_empty(scratch_pool); ctx->option = svn_stringbuf_create_empty(scratch_pool); ctx->value = svn_stringbuf_create_empty(scratch_pool); + ctx->line_read = svn_stringbuf_create_empty(scratch_pool); ctx->buffer_pos = 0; ctx->buffer_size = 0; ctx->hit_stream_eof = FALSE; @@ -599,13 +790,23 @@ svn_config__parse_stream(svn_config_t *cfg, svn_stream_t *stream, switch (ch) { case '[': /* Start of section header */ - if (count == 0) - SVN_ERR(parse_section_name(&ch, ctx, scratch_pool)); - else + if (count != 0) return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL, _("line %d: Section header" " must start in the first column"), ctx->line); + + /* Close the previous section before starting a new one. */ + if (ctx->in_section) + { + SVN_ERR(close_section(ctx, &stop)); + if (stop) + return SVN_NO_ERROR; + } + SVN_ERR(parse_section_name(&ch, ctx, scratch_pool)); + SVN_ERR(open_section(ctx, &stop)); + if (stop) + return SVN_NO_ERROR; break; case '#': /* Comment */ @@ -644,6 +845,9 @@ svn_config__parse_stream(svn_config_t *cfg, svn_stream_t *stream, } while (ch != EOF); + /* Emit the last close-section call to wrap up. */ + if (ctx->in_section) + SVN_ERR(close_section(ctx, &stop)); return SVN_NO_ERROR; } @@ -945,12 +1149,12 @@ svn_config_ensure(const char *config_dir, apr_pool_t *pool) "### http-timeout Timeout for HTTP requests in seconds" NL "### http-compression Whether to compress HTTP requests" NL + "### (yes/no/auto)." NL "### http-max-connections Maximum number of parallel server" NL "### connections to use for any given" NL "### HTTP operation." NL "### http-chunked-requests Whether to use chunked transfer" NL "### encoding for HTTP requests body." NL - "### neon-debug-mask Debug mask for Neon HTTP library" NL "### ssl-authority-files List of files, each of a trusted CA" NL "### ssl-trust-default-ca Trust the system 'default' CAs" NL @@ -1043,7 +1247,6 @@ svn_config_ensure(const char *config_dir, apr_pool_t *pool) "### Most users will not need to explicitly set the http-library" NL "### option, but valid values for the option include:" NL "### 'serf': Serf-based module (Subversion 1.5 - present)" NL - "### 'neon': Neon-based module (Subversion 1.0 - 1.7)" NL "### Availability of these modules may depend on your specific" NL "### Subversion distribution." NL "###" NL @@ -1068,7 +1271,6 @@ svn_config_ensure(const char *config_dir, apr_pool_t *pool) "# http-proxy-username = blah" NL "# http-proxy-password = doubleblah" NL "# http-timeout = 60" NL - "# neon-debug-mask = 130" NL #ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE "# store-plaintext-passwords = no" NL #endif @@ -1107,9 +1309,8 @@ svn_config_ensure(const char *config_dir, apr_pool_t *pool) "# http-proxy-port = 7000" NL "# http-proxy-username = defaultusername" NL "# http-proxy-password = defaultpassword" NL - "# http-compression = no" NL + "# http-compression = auto" NL "# No http-timeout, so just use the builtin default." NL - "# No neon-debug-mask, so neon debugging is disabled." NL "# ssl-authority-files = /path/to/CAcert.pem;/path/to/CAcert2.pem" NL "#" NL "# Password / passphrase caching parameters:" NL @@ -1421,7 +1622,7 @@ svn_config_get_user_config_path(const char **path, if (! homedir) return SVN_NO_ERROR; *path = svn_dirent_join_many(pool, - svn_dirent_canonicalize(homedir, pool), + homedir, SVN_CONFIG__USR_DIRECTORY, fname, SVN_VA_NULL); } #endif /* WIN32 */ |