diff --git a/include/haproxy/proxy.h b/include/haproxy/proxy.h index 69cde1df53..b81783a938 100644 --- a/include/haproxy/proxy.h +++ b/include/haproxy/proxy.h @@ -49,6 +49,7 @@ int stream_set_backend(struct stream *s, struct proxy *be); const char *proxy_cap_str(int cap); const char *proxy_mode_str(int mode); +const char *proxy_find_best_option(const char *word, const char **extra); void proxy_store_name(struct proxy *px); struct proxy *proxy_find_by_id(int id, int cap, int table); struct proxy *proxy_find_by_name(const char *name, int cap, int table); diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c index ac5002536a..07878330f6 100644 --- a/src/cfgparse-listen.c +++ b/src/cfgparse-listen.c @@ -54,6 +54,15 @@ static const char *common_kw_list[] = { NULL /* must be last */ }; +static const char *common_options[] = { + "httpclose", "forceclose", "http-server-close", "http-keep-alive", + "http-tunnel", "redispatch", "httplog", "tcplog", "tcpka", "httpchk", + "ssl-hello-chk", "smtpchk", "pgsql-check", "redis-check", + "mysql-check", "ldap-check", "spop-check", "tcp-check", + "external-check", "forwardfor", "original-to", + NULL /* must be last */ +}; + /* Report a warning if a rule is placed after a 'tcp-request session' rule. * Return 1 if the warning has been emitted, otherwise 0. */ @@ -2238,7 +2247,13 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) } /* end while loop */ } else { - ha_alert("parsing [%s:%d] : unknown option '%s'.\n", file, linenum, args[1]); + const char *best = proxy_find_best_option(args[1], common_options); + + if (best) + ha_alert("parsing [%s:%d] : unknown option '%s'; did you mean '%s' maybe ?\n", file, linenum, args[1], best); + else + ha_alert("parsing [%s:%d] : unknown option '%s'.\n", file, linenum, args[1]); + err_code |= ERR_ALERT | ERR_FATAL; goto out; } diff --git a/src/proxy.c b/src/proxy.c index a9a49439d6..843941e5a0 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -152,6 +152,55 @@ const char *proxy_mode_str(int mode) { return "unknown"; } +/* try to find among known options the one that looks closest to by + * counting transitions between letters, digits and other characters. Will + * return the best matching word if found, otherwise NULL. An optional array + * of extra words to compare may be passed in , but it must then be + * terminated by a NULL entry. If unused it may be NULL. + */ +const char *proxy_find_best_option(const char *word, const char **extra) +{ + uint8_t word_sig[1024]; + uint8_t list_sig[1024]; + const char *best_ptr = NULL; + int dist, best_dist = INT_MAX; + int index; + + make_word_fingerprint(word_sig, word); + + for (index = 0; cfg_opts[index].name; index++) { + make_word_fingerprint(list_sig, cfg_opts[index].name); + dist = word_fingerprint_distance(word_sig, list_sig); + if (dist < best_dist) { + best_dist = dist; + best_ptr = cfg_opts[index].name; + } + } + + for (index = 0; cfg_opts2[index].name; index++) { + make_word_fingerprint(list_sig, cfg_opts2[index].name); + dist = word_fingerprint_distance(word_sig, list_sig); + if (dist < best_dist) { + best_dist = dist; + best_ptr = cfg_opts2[index].name; + } + } + + while (extra && *extra) { + make_word_fingerprint(list_sig, *extra); + dist = word_fingerprint_distance(word_sig, list_sig); + if (dist < best_dist) { + best_dist = dist; + best_ptr = *extra; + } + extra++; + } + + if (best_dist > 2 * strlen(word) || (best_ptr && best_dist > 2 * strlen(best_ptr))) + best_ptr = NULL; + return best_ptr; +} + /* * This function scans the list of backends and servers to retrieve the first * backend and the first server with the given names, and sets them in both