+
+static int get_next_word(const char *buf, const char *delim, char **word)
+{
+ enum line_state_flags {LSF_HAVE_WORD = 1, LSF_BACKSLASH = 2,
+ LSF_SINGLE_QUOTE = 4, LSF_DOUBLE_QUOTE = 8};
+ const char *in;
+ char *out;
+ int ret, state = 0;
+
+ out = para_malloc(strlen(buf) + 1);
+ *out = '\0';
+ *word = out;
+ for (in = buf; *in; in++) {
+ const char *p;
+
+ switch (*in) {
+ case '\\':
+ if (state & LSF_BACKSLASH) /* \\ */
+ goto copy_char;
+ state |= LSF_BACKSLASH;
+ state |= LSF_HAVE_WORD;
+ continue;
+ case 'n':
+ case 't':
+ if (state & LSF_BACKSLASH) { /* \n or \t */
+ *out++ = (*in == 'n')? '\n' : '\t';
+ state &= ~LSF_BACKSLASH;
+ continue;
+ }
+ goto copy_char;
+ case '"':
+ if (state & LSF_BACKSLASH) /* \" */
+ goto copy_char;
+ if (state & LSF_SINGLE_QUOTE) /* '" */
+ goto copy_char;
+ if (state & LSF_DOUBLE_QUOTE) {
+ state &= ~LSF_DOUBLE_QUOTE;
+ continue;
+ }
+ state |= LSF_HAVE_WORD;
+ state |= LSF_DOUBLE_QUOTE;
+ continue;
+ case '\'':
+ if (state & LSF_BACKSLASH) /* \' */
+ goto copy_char;
+ if (state & LSF_DOUBLE_QUOTE) /* "' */
+ goto copy_char;
+ if (state & LSF_SINGLE_QUOTE) {
+ state &= ~LSF_SINGLE_QUOTE;
+ continue;
+ }
+ state |= LSF_HAVE_WORD;
+ state |= LSF_SINGLE_QUOTE;
+ continue;
+ }
+ for (p = delim; *p; p++) {
+ if (*in != *p)
+ continue;
+ if (state & LSF_BACKSLASH)
+ goto copy_char;
+ if (state & LSF_SINGLE_QUOTE)
+ goto copy_char;
+ if (state & LSF_DOUBLE_QUOTE)
+ goto copy_char;
+ if (state & LSF_HAVE_WORD)
+ goto success;
+ break;
+ }
+ if (*p) /* ignore delimiter at the beginning */
+ continue;
+copy_char:
+ state |= LSF_HAVE_WORD;
+ *out++ = *in;
+ state &= ~LSF_BACKSLASH;
+ }
+ ret = 0;
+ if (!(state & LSF_HAVE_WORD))
+ goto out;
+ ret = -ERRNO_TO_PARA_ERROR(EINVAL);
+ if (state & LSF_BACKSLASH) {
+ PARA_ERROR_LOG("trailing backslash\n");
+ goto out;
+ }
+ if ((state & LSF_SINGLE_QUOTE) || (state & LSF_DOUBLE_QUOTE)) {
+ PARA_ERROR_LOG("unmatched quote character\n");
+ goto out;
+ }
+success:
+ *out = '\0';
+ return in - buf;
+out:
+ free(*word);
+ *word = NULL;
+ return ret;
+}
+
+/**
+ * Get the number of the word the cursor is on.
+ *
+ * \param buf The zero-terminated line buffer.
+ * \param delim Characters that separate words.
+ * \param point The cursor position.
+ *
+ * \return Zero-based word number.
+ */
+int compute_word_num(const char *buf, const char *delim, int point)
+{
+ int ret, num_words;
+ const char *p;
+ char *word;
+
+ for (p = buf, num_words = 0; ; p += ret, num_words++) {
+ ret = get_next_word(p, delim, &word);
+ if (ret <= 0)
+ break;
+ free(word);
+ if (p + ret >= buf + point)
+ break;
+ }
+ return num_words;
+}
+
+/**
+ * Free an array of words created by create_argv() or create_shifted_argv().
+ *
+ * \param argv A pointer previously obtained by \ref create_argv().
+ */
+void free_argv(char **argv)
+{
+ int i;
+
+ if (!argv)
+ return;
+ for (i = 0; argv[i]; i++)
+ free(argv[i]);
+ free(argv);
+}
+
+static int create_argv_offset(int offset, const char *buf, const char *delim,
+ char ***result)
+{
+ char *word, **argv = para_malloc((offset + 1) * sizeof(char *));
+ const char *p;
+ int i, ret;
+
+ for (i = 0; i < offset; i++)
+ argv[i] = NULL;
+ for (p = buf; p && *p; p += ret, i++) {
+ ret = get_next_word(p, delim, &word);
+ if (ret < 0)
+ goto err;
+ if (!ret)
+ break;
+ argv = para_realloc(argv, (i + 2) * sizeof(char*));
+ argv[i] = word;
+ }
+ argv[i] = NULL;
+ *result = argv;
+ return i;
+err:
+ while (i > 0)
+ free(argv[--i]);
+ free(argv);
+ *result = NULL;
+ return ret;
+}
+
+/**
+ * Split a buffer into words.
+ *
+ * This parser honors single and double quotes, backslash-escaped characters
+ * and special characters like \p \\n. The result contains pointers to copies
+ * of the words contained in \a buf and has to be freed by using \ref
+ * free_argv().
+ *
+ * \param buf The buffer to be split.
+ * \param delim Each character in this string is treated as a separator.
+ * \param result The array of words is returned here.
+ *
+ * \return Number of words in \a buf, negative on errors.
+ */
+int create_argv(const char *buf, const char *delim, char ***result)
+{
+ return create_argv_offset(0, buf, delim, result);
+}
+
+/**
+ * Split a buffer into words, offset one.
+ *
+ * This is similar to \ref create_argv() but the returned array is one element
+ * larger, words start at index one and element zero is initialized to \p NULL.
+ * Callers must set element zero to a non-NULL value before calling free_argv()
+ * on the returned array to avoid a memory leak.
+ *
+ * \param buf See \ref create_argv().
+ * \param delim See \ref create_argv().
+ * \param result See \ref create_argv().
+ *
+ * \return Number of words plus one on success, negative on errors.
+ */
+int create_shifted_argv(const char *buf, const char *delim, char ***result)
+{
+ return create_argv_offset(1, buf, delim, result);
+}
+
+/**
+ * Find out if the given string is contained in the arg vector.
+ *
+ * \param arg The string to look for.
+ * \param argv The array to search.
+ *
+ * \return The first index whose value equals \a arg, or \p -E_ARG_NOT_FOUND if
+ * arg was not found in \a argv.
+ */
+int find_arg(const char *arg, char **argv)
+{
+ int i;
+
+ if (!argv)
+ return -E_ARG_NOT_FOUND;
+ for (i = 0; argv[i]; i++)
+ if (strcmp(arg, argv[i]) == 0)
+ return i;
+ return -E_ARG_NOT_FOUND;
+}
+
+/**
+ * Compile a regular expression.
+ *
+ * This simple wrapper calls regcomp() and logs a message on errors.
+ *
+ * \param preg See regcomp(3).
+ * \param regex See regcomp(3).
+ * \param cflags See regcomp(3).
+ *
+ * \return Standard.
+ */
+int para_regcomp(regex_t *preg, const char *regex, int cflags)
+{
+ char *buf;
+ size_t size;
+ int ret = regcomp(preg, regex, cflags);
+
+ if (ret == 0)
+ return 1;
+ size = regerror(ret, preg, NULL, 0);
+ buf = para_malloc(size);
+ regerror(ret, preg, buf, size);
+ PARA_ERROR_LOG("%s\n", buf);
+ free(buf);
+ return -E_REGEX;
+}
+
+/**
+ * strdup() for not necessarily zero-terminated strings.
+ *
+ * \param src The source buffer.
+ * \param len The number of bytes to be copied.
+ *
+ * \return A 0-terminated buffer of length \a len + 1.
+ *
+ * This is similar to strndup(), which is a GNU extension. However, one
+ * difference is that strndup() returns \p NULL if insufficient memory was
+ * available while this function aborts in this case.
+ *
+ * \sa strdup(), \ref para_strdup().
+ */
+char *safe_strdup(const char *src, size_t len)
+{
+ char *p;
+
+ assert(len < (size_t)-1);
+ p = para_malloc(len + 1);
+ if (len > 0)
+ memcpy(p, src, len);
+ p[len] = '\0';
+ return p;
+}
+
+/**
+ * Copy the value of a key=value pair.
+ *
+ * This checks whether the given buffer starts with "key=", ignoring case. If
+ * yes, a copy of the value is returned. The source buffer may not be
+ * zero-terminated.
+ *
+ * \param src The source buffer.
+ * \param len The number of bytes of the tag.
+ * \param key Only copy if it is the value of this key.
+ *
+ * \return A zero-terminated buffer, or \p NULL if the key was
+ * not of the given type.
+ */
+char *key_value_copy(const char *src, size_t len, const char *key)
+{
+ int keylen = strlen(key);
+
+ if (len <= keylen)
+ return NULL;
+ if (strncasecmp(src, key, keylen))
+ return NULL;
+ if (src[keylen] != '=')
+ return NULL;
+ return safe_strdup(src + keylen + 1, len - keylen - 1);
+}
+
+static bool utf8_mode(void)
+{
+ static bool initialized, have_utf8;
+
+ if (!initialized) {
+ char *info = nl_langinfo(CODESET);
+ have_utf8 = (info && strcmp(info, "UTF-8") == 0);
+ initialized = true;
+ PARA_INFO_LOG("%susing UTF-8 character encoding\n",
+ have_utf8? "" : "not ");
+ }
+ return have_utf8;
+}
+
+/*
+ * glibc's wcswidth returns -1 if the string contains a tab character, which
+ * makes the function next to useless. The two functions below are taken from
+ * mutt.
+ */
+
+#define IsWPrint(wc) (iswprint(wc) || wc >= 0xa0)
+
+static int mutt_wcwidth(wchar_t wc, size_t pos)
+{
+ int n;
+
+ if (wc == 0x09) /* tab */
+ return (pos | 7) + 1 - pos;
+ n = wcwidth(wc);
+ if (IsWPrint(wc) && n > 0)
+ return n;
+ if (!(wc & ~0x7f))
+ return 2;
+ if (!(wc & ~0xffff))
+ return 6;
+ return 10;
+}
+
+static size_t mutt_wcswidth(const wchar_t *s, size_t n)
+{
+ size_t w = 0;
+
+ while (n--)
+ w += mutt_wcwidth(*s++, w);
+ return w;
+}
+
+/**
+ * Skip a given number of cells at the beginning of a string.
+ *
+ * \param s The input string.
+ * \param cells_to_skip Desired number of cells that should be skipped.
+ * \param bytes_to_skip Result.
+ *
+ * This function computes how many input bytes must be skipped to advance a
+ * string by the given width. If the current character encoding is not UTF-8,
+ * this is simply the given number of cells, i.e. \a cells_to_skip. Otherwise,
+ * \a s is treated as a multibyte string and on successful return, \a s +
+ * bytes_to_skip points to the start of a multibyte string such that the total
+ * width of the multibyte characters that are skipped by advancing \a s that
+ * many bytes equals at least \a cells_to_skip.
+ *
+ * \return Standard.
+ */
+int skip_cells(const char *s, size_t cells_to_skip, size_t *bytes_to_skip)
+{
+ wchar_t wc;
+ mbstate_t ps;
+ size_t n, bytes_parsed, cells_skipped;
+
+ *bytes_to_skip = 0;
+ if (cells_to_skip == 0)
+ return 0;
+ if (!utf8_mode()) {
+ *bytes_to_skip = cells_to_skip;
+ return 0;
+ }
+ bytes_parsed = cells_skipped = 0;
+ memset(&ps, 0, sizeof(ps));
+ n = strlen(s);
+ while (cells_to_skip > cells_skipped) {
+ size_t mbret;
+
+ mbret = mbrtowc(&wc, s + bytes_parsed, n - bytes_parsed, &ps);
+ assert(mbret != 0);
+ if (mbret == (size_t)-1 || mbret == (size_t)-2)
+ return -ERRNO_TO_PARA_ERROR(EILSEQ);
+ bytes_parsed += mbret;
+ cells_skipped += mutt_wcwidth(wc, cells_skipped);
+ }
+ *bytes_to_skip = bytes_parsed;
+ return 1;
+}
+
+/**
+ * Compute the width of an UTF-8 string.
+ *
+ * \param s The string.
+ * \param result The width of \a s is returned here.
+ *
+ * If not in UTF8-mode. this function is just a wrapper for strlen(3).
+ * Otherwise \a s is treated as an UTF-8 string and its display width is
+ * computed. Note that this function may fail if the underlying call to
+ * mbsrtowcs(3) fails, so the caller must check the return value.
+ *
+ * \sa nl_langinfo(3), wcswidth(3).
+ *
+ * \return Standard.
+ */
+__must_check int strwidth(const char *s, size_t *result)
+{
+ const char *src = s;
+ mbstate_t state;
+ static wchar_t *dest;
+ size_t num_wchars;
+
+ /*
+ * Never call any log function here. This may result in an endless loop
+ * as para_gui's para_log() calls this function.
+ */
+
+ if (!utf8_mode()) {
+ *result = strlen(s);
+ return 0;
+ }
+ memset(&state, 0, sizeof(state));
+ *result = 0;
+ num_wchars = mbsrtowcs(NULL, &src, 0, &state);
+ if (num_wchars == (size_t)-1)
+ return -ERRNO_TO_PARA_ERROR(errno);
+ if (num_wchars == 0)
+ return 0;
+ dest = para_malloc(num_wchars * sizeof(*dest));
+ src = s;
+ memset(&state, 0, sizeof(state));
+ num_wchars = mbsrtowcs(dest, &src, num_wchars, &state);
+ assert(num_wchars > 0 && num_wchars != (size_t)-1);
+ *result = mutt_wcswidth(dest, num_wchars);
+ free(dest);
+ return 1;
+}