DLLIMPORT int cfg_rmsec(cfg_t *cfg, const char *name)
{
	cfg_opt_t *opt;
	long int index;

	opt = cfg_getopt_secidx(cfg, name, &index);
	return cfg_opt_rmnsec(opt, index);
}


Fix #163: unterminated username used with getpwnam()

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>

DLLIMPORT const char *cfg_opt_getstr(cfg_opt_t *opt)
{
	return cfg_opt_getnstr(opt, 0);
}


DLLIMPORT cfg_bool_t cfg_opt_getnbool(cfg_opt_t *opt, unsigned int index)
{
	if (!opt || opt->type != CFGT_BOOL) {
		errno = EINVAL;
		return cfg_false;
	}

	if (opt->values && index < opt->nvalues)
		return opt->values[index]->boolean;
	if (opt->simple_value.boolean)
		return *opt->simple_value.boolean;

	return cfg_false;
}


static int cfg_print_pff_indent(cfg_t *cfg, FILE *fp,
				cfg_print_filter_func_t fb_pff, int indent)
{
	int i, result = CFG_SUCCESS;

	for (i = 0; cfg->opts[i].name; i++) {
		cfg_print_filter_func_t pff = cfg->pff ? cfg->pff : fb_pff;
		if (pff && pff(cfg, &cfg->opts[i]))
			continue;
		result += cfg_opt_print_pff_indent(&cfg->opts[i], fp, pff, indent);
	}

	return result;
}


DLLIMPORT int cfg_opt_setnbool(cfg_opt_t *opt, cfg_bool_t value, unsigned int index)
{
	cfg_value_t *val;

	if (!opt || opt->type != CFGT_BOOL) {
		errno = EINVAL;
		return CFG_FAIL;
	}

	val = cfg_opt_getval(opt, index);
	if (!val)
		return CFG_FAIL;

	val->boolean = value;
	opt->flags |= CFGF_MODIFIED;

	return CFG_SUCCESS;
}


DLLIMPORT cfg_t *cfg_getsec(cfg_t *cfg, const char *name)
{
	cfg_opt_t *opt;
	long int index;

	opt = cfg_getopt_secidx(cfg, name, &index);
	return cfg_opt_getnsec(opt, index);
}


DLLIMPORT const char *cfg_title(cfg_t *cfg)
{
	if (cfg)
		return cfg->title;
	return NULL;
}


DLLIMPORT int cfg_setnstr(cfg_t *cfg, const char *name, const char *value, unsigned int index)
{
	cfg_opt_t *opt;

	opt = cfg_getopt(cfg, name);
	if (opt && opt->validcb2 && (*opt->validcb2)(cfg, opt, (void *)value) != 0)
		return CFG_FAIL;

	return cfg_opt_setnstr(opt, value, index);
}


static cfg_value_t *cfg_opt_getval(cfg_opt_t *opt, unsigned int index)
{
	cfg_value_t *val = NULL;

	if (index != 0 && !is_set(CFGF_LIST, opt->flags) && !is_set(CFGF_MULTI, opt->flags)) {
		errno = EINVAL;
		return NULL;
	}

	if (opt->simple_value.ptr)
		val = (cfg_value_t *)opt->simple_value.ptr;
	else {
		if (is_set(CFGF_RESET, opt->flags)) {
			cfg_free_value(opt);
			opt->flags &= ~CFGF_RESET;
		}

		if (index >= opt->nvalues)
			val = cfg_addval(opt);
		else
			val = opt->values[index];
	}

	return val;
}


DLLIMPORT cfg_validate_callback_t cfg_set_validate_func(cfg_t *cfg, const char *name, cfg_validate_callback_t vf)
{
	cfg_opt_t *opt;
	cfg_validate_callback_t oldvf;

	opt = cfg_getopt_array(cfg->opts, cfg->flags, name);
	if (!opt)
		return NULL;

	oldvf = opt->validcb;
	opt->validcb = vf;

	return oldvf;
}


DLLIMPORT int cfg_free_value(cfg_opt_t *opt)
{
	if (!opt) {
		errno = EINVAL;
		return CFG_FAIL;
	}

	if (opt->comment && !is_set(CFGF_RESET, opt->flags)) {
		free(opt->comment);
		opt->comment = NULL;
	}

	if (opt->values) {
		unsigned int i;

		for (i = 0; i < opt->nvalues; i++) {
			if (opt->type == CFGT_STR) {
				free((void *)opt->values[i]->string);
			} else if (opt->type == CFGT_SEC) {
				opt->values[i]->section->path = NULL; /* Global search path */
				cfg_free(opt->values[i]->section);
			} else if (opt->type == CFGT_PTR && opt->freecb && opt->values[i]->ptr) {
				(opt->freecb) (opt->values[i]->ptr);
			}
			free(opt->values[i]);
		}
		free(opt->values);
	}

	opt->values  = NULL;
	opt->nvalues = 0;

	return CFG_SUCCESS;
}


static int cfg_parse_internal(cfg_t *cfg, int level, int force_state, cfg_opt_t *force_opt)
{
	int state = 0;
	char *comment = NULL;
	char *opttitle = NULL;
	cfg_opt_t *opt = NULL;
	cfg_value_t *val = NULL;
	cfg_opt_t funcopt = CFG_STR(NULL, NULL, 0);

	int ignore = 0;		/* ignore until this token, traverse parser w/o error */
	int num_values = 0;	/* number of values found for a list option */
	int rc;

	if (force_state != -1)
		state = force_state;
	if (force_opt)
		opt = force_opt;

	while (1) {
		int tok = cfg_yylex(cfg);

		if (tok == 0) {
			/* lexer.l should have called cfg_error() */
			goto error;
		}

		if (tok == EOF) {
			if (state != 0) {
				cfg_error(cfg, _("premature end of file"));
				goto error;
			}

			if (opt && is_set(CFGF_DEPRECATED, opt->flags))
				cfg_handle_deprecated(cfg, opt);

			if (comment)
				free(comment);

			return STATE_EOF;
		}

		switch (state) {
		case 0:	/* expecting an option name */
			if (opt && is_set(CFGF_DEPRECATED, opt->flags))
				cfg_handle_deprecated(cfg, opt);

			switch (tok) {
			case '}':
				if (level == 0) {
					cfg_error(cfg, _("unexpected closing brace"));
					goto error;
				}
				if (comment)
					free(comment);

				return STATE_EOF;

			case CFGT_STR:
				break;

			case CFGT_COMMENT:
				if (!is_set(CFGF_COMMENTS, cfg->flags))
					continue;

				if (comment)
					free(comment);
				comment = strdup(cfg_yylval);
				continue;

			default:
				cfg_error(cfg, _("unexpected token '%s'"), cfg_yylval);
				goto error;
			}

			opt = cfg_getopt(cfg, cfg_yylval);
			if (!opt) {
				if (is_set(CFGF_IGNORE_UNKNOWN, cfg->flags)) {
					state = 10;
					break;
				}

				/* Not found, is it a dynamic key-value section? */
				if (is_set(CFGF_KEYSTRVAL, cfg->flags)) {
					opt = cfg_addopt(cfg, cfg_yylval);
					if (!opt)
						goto error;

					state = 1;
					break;
				}

				goto error;
			}

			if (opt->type == CFGT_SEC) {
				if (is_set(CFGF_TITLE, opt->flags))
					state = 6;
				else
					state = 5;
			} else if (opt->type == CFGT_FUNC) {
				state = 7;
			} else {
				state = 1;
			}
			break;

		case 1:	/* expecting an equal sign or plus-equal sign */
			if (!opt)
				goto error;

			if (tok == '+') {
				if (!is_set(CFGF_LIST, opt->flags)) {
					cfg_error(cfg, _("attempt to append to non-list option '%s'"), opt->name);
					goto error;
				}
				/* Even if the reset flag was set by
				 * cfg_init_defaults, appending to the defaults
				 * should be ok.
				 */
				opt->flags &= ~CFGF_RESET;
			} else if (tok == '=') {
				/* set the (temporary) reset flag to clear the old
				 * values, since we obviously didn't want to append */
				opt->flags |= CFGF_RESET;
			} else {
				cfg_error(cfg, _("missing equal sign after option '%s'"), opt->name);
				goto error;
			}

			opt->flags |= CFGF_MODIFIED;

			if (is_set(CFGF_LIST, opt->flags)) {
				state = 3;
				num_values = 0;
			} else {
				state = 2;
			}
			break;

		case 2:	/* expecting an option value */
			if (tok == '}' && opt && is_set(CFGF_LIST, opt->flags)) {
				state = 0;
				if (num_values == 0 && is_set(CFGF_RESET, opt->flags))
					/* Reset flags was set, and the empty list was
					 * specified. Free all old values. */
					cfg_free_value(opt);
				break;
			}

			if (tok != CFGT_STR) {
				cfg_error(cfg, _("unexpected token '%s'"), cfg_yylval);
				goto error;
			}

			if (cfg_setopt(cfg, opt, cfg_yylval) == NULL)
				goto error;

			if (opt && opt->validcb && (*opt->validcb) (cfg, opt) != 0)
				goto error;

			/* Inherit last read comment */
			cfg_opt_setcomment(opt, comment);
			if (comment)
				free(comment);
			comment = NULL;

			if (opt && is_set(CFGF_LIST, opt->flags)) {
				++num_values;
				state = 4;
			} else {
				state = 0;
			}
			break;

		case 3:	/* expecting an opening brace for a list option */
			if (tok != '{') {
				if (tok != CFGT_STR) {
					cfg_error(cfg, _("unexpected token '%s'"), cfg_yylval);
					goto error;
				}

				if (cfg_setopt(cfg, opt, cfg_yylval) == NULL)
					goto error;
				if (opt && opt->validcb && (*opt->validcb) (cfg, opt) != 0)
					goto error;
				++num_values;
				state = 0;
			} else {
				state = 2;
			}
			break;

		case 4:	/* expecting a separator for a list option, or closing (list) brace */
			if (tok == ',') {
				state = 2;
			} else if (tok == '}') {
				state = 0;
				if (opt && opt->validcb && (*opt->validcb) (cfg, opt) != 0)
					goto error;
			} else {
				cfg_error(cfg, _("unexpected token '%s'"), cfg_yylval);
				goto error;
			}
			break;

		case 5:	/* expecting an opening brace for a section */
			if (tok != '{') {
				cfg_error(cfg, _("missing opening brace for section '%s'"), opt ? opt->name : "");
				goto error;
			}

			val = cfg_setopt(cfg, opt, opttitle);
			if (!val)
				goto error;

			if (opttitle)
				free(opttitle);
			opttitle = NULL;

			val->section->path = cfg->path; /* Remember global search path */
			val->section->line = cfg->line;
			val->section->errfunc = cfg->errfunc;
			rc = cfg_parse_internal(val->section, level + 1, -1, NULL);
			if (rc != STATE_EOF)
				goto error;

			cfg->line = val->section->line;
			if (opt && opt->validcb && (*opt->validcb) (cfg, opt) != 0)
				goto error;
			state = 0;
			break;

		case 6:	/* expecting a title for a section */
			if (tok != CFGT_STR) {
				cfg_error(cfg, _("missing title for section '%s'"), opt ? opt->name : "");
				goto error;
			} else {
				opttitle = strdup(cfg_yylval);
				if (!opttitle)
					goto error;
			}
			state = 5;
			break;

		case 7:	/* expecting an opening parenthesis for a function */
			if (tok != '(') {
				cfg_error(cfg, _("missing parenthesis for function '%s'"), opt ? opt->name : "");
				goto error;
			}
			state = 8;
			break;

		case 8:	/* expecting a function parameter or a closing paren */
			if (tok == ')') {
				if (call_function(cfg, opt, &funcopt))
					goto error;
				state = 0;
			} else if (tok == CFGT_STR) {
				val = cfg_addval(&funcopt);
				if (!val)
					goto error;

				val->string = strdup(cfg_yylval);
				if (!val->string)
					goto error;

				state = 9;
			} else {
				cfg_error(cfg, _("syntax error in call of function '%s'"), opt ? opt->name : "");
				goto error;
			}
			break;

		case 9:	/* expecting a comma in a function or a closing paren */
			if (tok == ')') {
				if (call_function(cfg, opt, &funcopt))
					goto error;
				state = 0;
			} else if (tok == ',') {
				state = 8;
			} else {
				cfg_error(cfg, _("syntax error in call of function '%s'"), opt ? opt->name : "");
				goto error;
			}
			break;

		case 10: /* unknown option, mini-discard parser states: 10-15 */
			if (comment) {
				free(comment);
				comment = NULL;
			}

			if (tok == '+') {
				ignore = '=';
				state = 13; /* Append to list, should be followed by '=' */
			} else if (tok == '=') {
				ignore = 0;
				state = 14; /* Assignment, regular handling */
			} else if (tok == '(') {
				ignore = ')';
				state = 13; /* Function, ignore until end of param list */
			} else if (tok == '{') {
				state = 12; /* Section, ignore all until closing brace */
			} else if (tok == CFGT_STR) {
				state = 11; /* No '=' ... must be a titled section */
			} else if (tok == '}' && force_state == 10) {
				if (comment)
					free(comment);

				return STATE_CONTINUE;
			}
			break;

		case 11: /* unknown option, expecting start of title section */
			if (tok != '{') {
				cfg_error(cfg, _("unexpected token '%s'"), cfg_yylval);
				goto error;
			}
			state = 12;
			break;

		case 12: /* unknown option, recursively ignore entire sub-section */
			rc = cfg_parse_internal(cfg, level + 1, 10, NULL);
			if (rc != STATE_CONTINUE)
				goto error;
			ignore = '}';
			state = 13;
			break;

		case 13: /* unknown option, consume tokens silently until end of func/list */
			if (tok != ignore)
				break;

			if (ignore == '=') {
				ignore = 0;
				state = 14;
				break;
			}

			/* Are we done with recursive ignore of sub-section? */
			if (force_state == 10) {
				if (comment)
					free(comment);

				return STATE_CONTINUE;
			}

			ignore = 0;
			state = 0;
			break;

		case 14: /* unknown option, assuming value or start of list */
			if (tok == '{') {
				ignore = '}';
				state = 13;
				break;
			}

			if (tok != CFGT_STR) {
				cfg_error(cfg, _("unexpected token '%s'"), cfg_yylval);
				goto error;
			}

			ignore = 0;
			if (force_state == 10)
				state = 15;
			else
				state = 0;
			break;

		case 15: /* unknown option, dummy read of next parameter in sub-section */
			state = 10;
			break;

		default:
			cfg_error(cfg, _("Internal error in cfg_parse_internal(), unknown state %d"), state);
			goto error;
		}
	}

	if (comment)
		free(comment);

	return STATE_EOF;

error:
	if (opttitle)
		free(opttitle);
	if (comment)
		free(comment);

	return STATE_ERROR;
}


DLLIMPORT int cfg_opt_setnstr(cfg_opt_t *opt, const char *value, unsigned int index)
{
	char *newstr, *oldstr = NULL;
	cfg_value_t *val;

	if (!opt || opt->type != CFGT_STR) {
		errno = EINVAL;
		return CFG_FAIL;
	}

	val = cfg_opt_getval(opt, index);
	if (!val)
		return CFG_FAIL;

	if (val->string)
		oldstr = val->string;

	if (value) {
		newstr = strdup(value);
		if (!newstr)
			return CFG_FAIL;
		val->string = newstr;
	} else {
		val->string = NULL;
	}

	if (oldstr)
		free(oldstr);
	opt->flags |= CFGF_MODIFIED;

	return CFG_SUCCESS;
}


DLLIMPORT unsigned int cfg_opt_size(cfg_opt_t *opt)
{
	if (opt)
		return opt->nvalues;
	return 0;
}


DLLIMPORT int cfg_add_searchpath(cfg_t *cfg, const char *dir)
{
	cfg_searchpath_t *p;
	char *d;

	if (!cfg || !dir) {
		errno = EINVAL;
		return CFG_FAIL;
	}

	d = cfg_tilde_expand(dir);
	if (!d)
		return CFG_FAIL;

	p = malloc(sizeof(cfg_searchpath_t));
	if (!p) {
		free(d);
		return CFG_FAIL;
	}

	p->next   = cfg->path;
	p->dir    = d;
	cfg->path = p;

	return CFG_SUCCESS;
}


DLLIMPORT int cfg_opt_nprint_var(cfg_opt_t *opt, unsigned int index, FILE *fp)
{
	const char *str;

	if (!opt || !fp) {
		errno = EINVAL;
		return CFG_FAIL;
	}

	switch (opt->type) {
	case CFGT_INT:
		fprintf(fp, "%ld", cfg_opt_getnint(opt, index));
		break;

	case CFGT_FLOAT:
		fprintf(fp, "%f", cfg_opt_getnfloat(opt, index));
		break;

	case CFGT_STR:
		str = cfg_opt_getnstr(opt, index);
		fprintf(fp, "\"");
		while (str && *str) {
			if (*str == '"')
				fprintf(fp, "\\\"");
			else if (*str == '\\')
				fprintf(fp, "\\\\");
			else
				fprintf(fp, "%c", *str);
			str++;
		}
		fprintf(fp, "\"");
		break;

	case CFGT_BOOL:
		fprintf(fp, "%s", cfg_opt_getnbool(opt, index) ? "true" : "false");
		break;

	case CFGT_NONE:
	case CFGT_SEC:
	case CFGT_FUNC:
	case CFGT_PTR:
	case CFGT_COMMENT:
		break;
	}

	return CFG_SUCCESS;
}


static void cfg_free_opt_array(cfg_opt_t *opts)
{
	int i;

	for (i = 0; opts[i].name; ++i) {
		free((void *)opts[i].name);
		if (opts[i].comment)
			free(opts[i].comment);
		if (opts[i].def.parsed)
			free(opts[i].def.parsed);
		if (opts[i].def.string)
			free((void *)opts[i].def.string);
		if (opts[i].subopts)
			cfg_free_opt_array(opts[i].subopts);
	}
	free(opts);
}


DLLIMPORT cfg_bool_t cfg_getbool(cfg_t *cfg, const char *name)
{
	return cfg_getnbool(cfg, name, 0);
}


static cfg_opt_t *cfg_getopt_secidx(cfg_t *cfg, const char *name,
				    long int *index)
{
	cfg_opt_t *opt = NULL;
	cfg_t *sec = cfg;

	if (!cfg || !cfg->name || !name || !*name) {
		errno = EINVAL;
		return NULL;
	}

	while (name && *name) {
		char *title = NULL;
		long int i = -1;
		char *secname;
		size_t len;

		len = strcspn(name, "|=");
		if (!index && name[len] == 0 /*len == strlen(name) */ )
			/* no more subsections */
			break;

		if (!len)
			break;

		secname = strndup(name, len);
		if (!secname)
			return NULL;

		do {
			char *endptr;

			opt = cfg_getopt_leaf(sec, secname);
			if (!opt || opt->type != CFGT_SEC) {
				opt = NULL;
				break;
			}
			if (name[len] != '=') {
				/* non-multi, and backwards compat */
				i = 0;
				break;
			}
			if (!is_set(CFGF_MULTI, opt->flags))
				break;
			name += len + 1;
			title = parse_title(name, &len);
			if (!title)
				break;
			if (is_set(CFGF_TITLE, opt->flags)) {
				i = cfg_opt_gettsecidx(opt, title);
				break;
			}

			i = strtol(title, &endptr, 0);
			if (*endptr != '\0')
				i = -1;
		} while(0);

		if (index)
			*index = i;

		sec = i >= 0 ? cfg_opt_getnsec(opt, i) : NULL;
		if (!sec && !is_set(CFGF_IGNORE_UNKNOWN, cfg->flags)) {
			if (opt && !is_set(CFGF_MULTI, opt->flags))
				cfg_error(cfg, _("no such option '%s'"), secname);
			else if (title)
				cfg_error(cfg, _("no sub-section '%s' in '%s'"), title, secname);
			else
				cfg_error(cfg, _("no sub-section title/index for '%s'"), secname);
		}

		free(secname);
		if (title)
			free(title);
		if (!sec)
			return NULL;

		name += len;
		name += strspn(name, "|");
	}

	if (!index) {
		opt = cfg_getopt_leaf(sec, name);

		if (!opt && !is_set(CFGF_IGNORE_UNKNOWN, cfg->flags) && !is_set(CFGF_KEYSTRVAL, sec->flags))
			cfg_error(cfg, _("no such option '%s'"), name);
	}

	return opt;
}


DLLIMPORT double cfg_opt_getnfloat(cfg_opt_t *opt, unsigned int index)
{
	if (!opt || opt->type != CFGT_FLOAT) {
		errno = EINVAL;
		return 0;
	}

	if (opt->values && index < opt->nvalues)
		return opt->values[index]->fpnumber;
	if (opt->simple_value.fpnumber)
		return *opt->simple_value.fpnumber;

	return 0;
}


DLLIMPORT cfg_opt_t *cfg_getnopt(cfg_t *cfg, unsigned int index)
{
	unsigned int i;

	if (!cfg)
		return NULL;

	for (i = 0; cfg->opts && cfg->opts[i].name; i++) {
		if (i == index)
			return &cfg->opts[i];
	}

	return NULL;
}


static int cfg_opt_print_pff_indent(cfg_opt_t *opt, FILE *fp,
				    cfg_print_filter_func_t pff, int indent)
{
	if (!opt || !fp) {
		errno = EINVAL;
		return CFG_FAIL;
	}

	if (is_set(CFGF_COMMENTS, opt->flags) && opt->comment) {
		cfg_indent(fp, indent);
		fprintf(fp, "/* %s */\n", opt->comment);
	}

	if (opt->type == CFGT_SEC) {
		cfg_t *sec;
		unsigned int i;

		for (i = 0; i < cfg_opt_size(opt); i++) {
			sec = cfg_opt_getnsec(opt, i);
			cfg_indent(fp, indent);
			if (is_set(CFGF_TITLE, opt->flags))
				fprintf(fp, "%s \"%s\" {\n", opt->name, cfg_title(sec));
			else
				fprintf(fp, "%s {\n", opt->name);
			cfg_print_pff_indent(sec, fp, pff, indent + 1);
			cfg_indent(fp, indent);
			fprintf(fp, "}\n");
		}
	} else if (opt->type != CFGT_FUNC && opt->type != CFGT_NONE) {
		if (is_set(CFGF_LIST, opt->flags)) {
			cfg_indent(fp, indent);
			fprintf(fp, "%s = {", opt->name);

			if (opt->nvalues) {
				unsigned int i;

				if (opt->pf)
					opt->pf(opt, 0, fp);
				else
					cfg_opt_nprint_var(opt, 0, fp);
				for (i = 1; i < opt->nvalues; i++) {
					fprintf(fp, ", ");
					if (opt->pf)
						opt->pf(opt, i, fp);
					else
						cfg_opt_nprint_var(opt, i, fp);
				}
			}

			fprintf(fp, "}");
		} else {
			cfg_indent(fp, indent);
			/* comment out the option if is not set */
			if (cfg_opt_size(opt) == 0 ||
			    (opt->type == CFGT_STR && !cfg_opt_getnstr(opt, 0)))
				fprintf(fp, "# ");
			fprintf(fp, "%s=", opt->name);
			if (opt->pf)
				opt->pf(opt, 0, fp);
			else
				cfg_opt_nprint_var(opt, 0, fp);
		}

		fprintf(fp, "\n");
	} else if (opt->pf) {
		cfg_indent(fp, indent);
		opt->pf(opt, 0, fp);
		fprintf(fp, "\n");
	}

	return CFG_SUCCESS;
}


static void cfg_indent(FILE *fp, int indent)
{
	while (indent--)
		fprintf(fp, "  ");
}


cookies: make bad_domain() not consider a trailing dot fine

The check for a dot in the domain must not consider a single trailing
dot to be fine, as then TLD + trailing dot is fine and curl will accept
setting cookies for it.

CVE-2022-27779

Reported-by: Axel Chong
Bug: https://curl.se/docs/CVE-2022-27779.html
Closes #8820

void Curl_flush_cookies(struct Curl_easy *data, bool cleanup)
{
  CURLcode res;

  if(data->set.str[STRING_COOKIEJAR]) {
    if(data->state.cookielist) {
      /* If there is a list of cookie files to read, do it first so that
         we have all the told files read before we write the new jar.
         Curl_cookie_loadfiles() LOCKS and UNLOCKS the share itself! */
      Curl_cookie_loadfiles(data);
    }

    Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);

    /* if we have a destination file for all the cookies to get dumped to */
    res = cookie_output(data, data->cookies, data->set.str[STRING_COOKIEJAR]);
    if(res)
      infof(data, "WARNING: failed to save cookies in %s: %s",
            data->set.str[STRING_COOKIEJAR], curl_easy_strerror(res));
  }
  else {
    if(cleanup && data->state.cookielist) {
      /* since nothing is written, we can just free the list of cookie file
         names */
      curl_slist_free_all(data->state.cookielist); /* clean up list */
      data->state.cookielist = NULL;
    }
    Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
  }

  if(cleanup && (!data->share || (data->cookies != data->share->cookies))) {
    Curl_cookie_cleanup(data->cookies);
    data->cookies = NULL;
  }
  Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
}


static bool bad_domain(const char *domain)
{
  if(strcasecompare(domain, "localhost"))
    return FALSE;
  else {
    /* there must be a dot present, but that dot must not be a trailing dot */
    char *dot = strchr(domain, '.');
    if(dot)
      return dot[1] ? FALSE : TRUE;
  }
  return TRUE;
}


struct CookieInfo *Curl_cookie_init(struct Curl_easy *data,
                                    const char *file,
                                    struct CookieInfo *inc,
                                    bool newsession)
{
  struct CookieInfo *c;
  FILE *fp = NULL;
  bool fromfile = TRUE;
  char *line = NULL;

  if(!inc) {
    /* we didn't get a struct, create one */
    c = calloc(1, sizeof(struct CookieInfo));
    if(!c)
      return NULL; /* failed to get memory */
    c->filename = strdup(file?file:"none"); /* copy the name just in case */
    if(!c->filename)
      goto fail; /* failed to get memory */
    /*
     * Initialize the next_expiration time to signal that we don't have enough
     * information yet.
     */
    c->next_expiration = CURL_OFF_T_MAX;
  }
  else {
    /* we got an already existing one, use that */
    c = inc;
  }
  c->running = FALSE; /* this is not running, this is init */

  if(file && !strcmp(file, "-")) {
    fp = stdin;
    fromfile = FALSE;
  }
  else if(!file || !*file) {
    /* points to an empty string or NULL */
    fp = NULL;
  }
  else {
    fp = fopen(file, FOPEN_READTEXT);
    if(!fp)
      infof(data, "WARNING: failed to open cookie file \"%s\"", file);
  }

  c->newsession = newsession; /* new session? */

  if(fp) {
    char *lineptr;
    bool headerline;

    line = malloc(MAX_COOKIE_LINE);
    if(!line)
      goto fail;
    while(Curl_get_line(line, MAX_COOKIE_LINE, fp)) {
      if(checkprefix("Set-Cookie:", line)) {
        /* This is a cookie line, get it! */
        lineptr = &line[11];
        headerline = TRUE;
      }
      else {
        lineptr = line;
        headerline = FALSE;
      }
      while(*lineptr && ISBLANK(*lineptr))
        lineptr++;

      Curl_cookie_add(data, c, headerline, TRUE, lineptr, NULL, NULL, TRUE);
    }
    free(line); /* free the line buffer */

    /*
     * Remove expired cookies from the hash. We must make sure to run this
     * after reading the file, and not on every cookie.
     */
    remove_expired(c);

    if(fromfile && fp)
      fclose(fp);
  }

  c->running = TRUE;          /* now, we're running */
  if(data)
    data->state.cookie_engine = TRUE;

  return c;

fail:
  free(line);
  /*
   * Only clean up if we allocated it here, as the original could still be in
   * use by a share handle.
   */
  if(!inc)
    Curl_cookie_cleanup(c);
  if(fromfile && fp)
    fclose(fp);
  return NULL; /* out of memory */
}


static void freecookie(struct Cookie *co)
{
  free(co->expirestr);
  free(co->domain);
  free(co->path);
  free(co->spath);
  free(co->name);
  free(co->value);
  free(co->maxage);
  free(co->version);
  free(co);
}


Curl_cookie_add(struct Curl_easy *data,
                /*
                 * The 'data' pointer here may be NULL at times, and thus
                 * must only be used very carefully for things that can deal
                 * with data being NULL. Such as infof() and similar
                 */
                struct CookieInfo *c,
                bool httpheader, /* TRUE if HTTP header-style line */
                bool noexpire, /* if TRUE, skip remove_expired() */
                char *lineptr,   /* first character of the line */
                const char *domain, /* default domain */
                const char *path,   /* full path used when this cookie is set,
                                       used to get default path for the cookie
                                       unless set */
                bool secure)  /* TRUE if connection is over secure origin */
{
  struct Cookie *clist;
  struct Cookie *co;
  struct Cookie *lastc = NULL;
  time_t now = time(NULL);
  bool replace_old = FALSE;
  bool badcookie = FALSE; /* cookies are good by default. mmmmm yummy */
  size_t myhash;

#ifdef CURL_DISABLE_VERBOSE_STRINGS
  (void)data;
#endif

  /* First, alloc and init a new struct for it */
  co = calloc(1, sizeof(struct Cookie));
  if(!co)
    return NULL; /* bail out if we're this low on memory */

  if(httpheader) {
    /* This line was read off a HTTP-header */
    char name[MAX_NAME];
    char what[MAX_NAME];
    const char *ptr;
    const char *semiptr;

    size_t linelength = strlen(lineptr);
    if(linelength > MAX_COOKIE_LINE) {
      /* discard overly long lines at once */
      free(co);
      return NULL;
    }

    semiptr = strchr(lineptr, ';'); /* first, find a semicolon */

    while(*lineptr && ISBLANK(*lineptr))
      lineptr++;

    ptr = lineptr;
    do {
      /* we have a <what>=<this> pair or a stand-alone word here */
      name[0] = what[0] = 0; /* init the buffers */
      if(1 <= sscanf(ptr, "%" MAX_NAME_TXT "[^;\r\n=] =%"
                     MAX_NAME_TXT "[^;\r\n]",
                     name, what)) {
        /*
         * Use strstore() below to properly deal with received cookie
         * headers that have the same string property set more than once,
         * and then we use the last one.
         */
        const char *whatptr;
        bool done = FALSE;
        bool sep;
        size_t len = strlen(what);
        size_t nlen = strlen(name);
        const char *endofn = &ptr[ nlen ];

        /*
         * Check for too long individual name or contents, or too long
         * combination of name + contents. Chrome and Firefox support 4095 or
         * 4096 bytes combo
         */
        if(nlen >= (MAX_NAME-1) || len >= (MAX_NAME-1) ||
           ((nlen + len) > MAX_NAME)) {
          freecookie(co);
          infof(data, "oversized cookie dropped, name/val %zu + %zu bytes",
                nlen, len);
          return NULL;
        }

        /* name ends with a '=' ? */
        sep = (*endofn == '=')?TRUE:FALSE;

        if(nlen) {
          endofn--; /* move to the last character */
          if(ISBLANK(*endofn)) {
            /* skip trailing spaces in name */
            while(*endofn && ISBLANK(*endofn) && nlen) {
              endofn--;
              nlen--;
            }
            name[nlen] = 0; /* new end of name */
          }
        }

        /* Strip off trailing whitespace from the 'what' */
        while(len && ISBLANK(what[len-1])) {
          what[len-1] = 0;
          len--;
        }

        /* Skip leading whitespace from the 'what' */
        whatptr = what;
        while(*whatptr && ISBLANK(*whatptr))
          whatptr++;

        /*
         * Check if we have a reserved prefix set before anything else, as we
         * otherwise have to test for the prefix in both the cookie name and
         * "the rest". Prefixes must start with '__' and end with a '-', so
         * only test for names where that can possibly be true.
         */
        if(nlen > 3 && name[0] == '_' && name[1] == '_') {
          if(!strncmp("__Secure-", name, 9))
            co->prefix |= COOKIE_PREFIX__SECURE;
          else if(!strncmp("__Host-", name, 7))
            co->prefix |= COOKIE_PREFIX__HOST;
        }

        if(!co->name) {
          /* The very first name/value pair is the actual cookie name */
          if(!sep) {
            /* Bad name/value pair. */
            badcookie = TRUE;
            break;
          }
          co->name = strdup(name);
          co->value = strdup(whatptr);
          done = TRUE;
          if(!co->name || !co->value) {
            badcookie = TRUE;
            break;
          }
        }
        else if(!len) {
          /*
           * this was a "<name>=" with no content, and we must allow
           * 'secure' and 'httponly' specified this weirdly
           */
          done = TRUE;
          /*
           * secure cookies are only allowed to be set when the connection is
           * using a secure protocol, or when the cookie is being set by
           * reading from file
           */
          if(strcasecompare("secure", name)) {
            if(secure || !c->running) {
              co->secure = TRUE;
            }
            else {
              badcookie = TRUE;
              break;
            }
          }
          else if(strcasecompare("httponly", name))
            co->httponly = TRUE;
          else if(sep)
            /* there was a '=' so we're not done parsing this field */
            done = FALSE;
        }
        if(done)
          ;
        else if(strcasecompare("path", name)) {
          strstore(&co->path, whatptr);
          if(!co->path) {
            badcookie = TRUE; /* out of memory bad */
            break;
          }
          free(co->spath); /* if this is set again */
          co->spath = sanitize_cookie_path(co->path);
          if(!co->spath) {
            badcookie = TRUE; /* out of memory bad */
            break;
          }
        }
        else if(strcasecompare("domain", name)) {
          bool is_ip;

          /*
           * Now, we make sure that our host is within the given domain, or
           * the given domain is not valid and thus cannot be set.
           */

          if('.' == whatptr[0])
            whatptr++; /* ignore preceding dot */

#ifndef USE_LIBPSL
          /*
           * Without PSL we don't know when the incoming cookie is set on a
           * TLD or otherwise "protected" suffix. To reduce risk, we require a
           * dot OR the exact host name being "localhost".
           */
          if(bad_domain(whatptr))
            domain = ":";
#endif

          is_ip = Curl_host_is_ipnum(domain ? domain : whatptr);

          if(!domain
             || (is_ip && !strcmp(whatptr, domain))
             || (!is_ip && tailmatch(whatptr, domain))) {
            strstore(&co->domain, whatptr);
            if(!co->domain) {
              badcookie = TRUE;
              break;
            }
            if(!is_ip)
              co->tailmatch = TRUE; /* we always do that if the domain name was
                                       given */
          }
          else {
            /*
             * We did not get a tailmatch and then the attempted set domain is
             * not a domain to which the current host belongs. Mark as bad.
             */
            badcookie = TRUE;
            infof(data, "skipped cookie with bad tailmatch domain: %s",
                  whatptr);
          }
        }
        else if(strcasecompare("version", name)) {
          strstore(&co->version, whatptr);
          if(!co->version) {
            badcookie = TRUE;
            break;
          }
        }
        else if(strcasecompare("max-age", name)) {
          /*
           * Defined in RFC2109:
           *
           * Optional.  The Max-Age attribute defines the lifetime of the
           * cookie, in seconds.  The delta-seconds value is a decimal non-
           * negative integer.  After delta-seconds seconds elapse, the
           * client should discard the cookie.  A value of zero means the
           * cookie should be discarded immediately.
           */
          strstore(&co->maxage, whatptr);
          if(!co->maxage) {
            badcookie = TRUE;
            break;
          }
        }
        else if(strcasecompare("expires", name)) {
          strstore(&co->expirestr, whatptr);
          if(!co->expirestr) {
            badcookie = TRUE;
            break;
          }
        }

        /*
         * Else, this is the second (or more) name we don't know about!
         */
      }
      else {
        /* this is an "illegal" <what>=<this> pair */
      }

      if(!semiptr || !*semiptr) {
        /* we already know there are no more cookies */
        semiptr = NULL;
        continue;
      }

      ptr = semiptr + 1;
      while(*ptr && ISBLANK(*ptr))
        ptr++;
      semiptr = strchr(ptr, ';'); /* now, find the next semicolon */

      if(!semiptr && *ptr)
        /*
         * There are no more semicolons, but there's a final name=value pair
         * coming up
         */
        semiptr = strchr(ptr, '\0');
    } while(semiptr);

    if(co->maxage) {
      CURLofft offt;
      offt = curlx_strtoofft((*co->maxage == '\"')?
                             &co->maxage[1]:&co->maxage[0], NULL, 10,
                             &co->expires);
      if(offt == CURL_OFFT_FLOW)
        /* overflow, used max value */
        co->expires = CURL_OFF_T_MAX;
      else if(!offt) {
        if(!co->expires)
          /* already expired */
          co->expires = 1;
        else if(CURL_OFF_T_MAX - now < co->expires)
          /* would overflow */
          co->expires = CURL_OFF_T_MAX;
        else
          co->expires += now;
      }
    }
    else if(co->expirestr) {
      /*
       * Note that if the date couldn't get parsed for whatever reason, the
       * cookie will be treated as a session cookie
       */
      co->expires = Curl_getdate_capped(co->expirestr);

      /*
       * Session cookies have expires set to 0 so if we get that back from the
       * date parser let's add a second to make it a non-session cookie
       */
      if(co->expires == 0)
        co->expires = 1;
      else if(co->expires < 0)
        co->expires = 0;
    }

    if(!badcookie && !co->domain) {
      if(domain) {
        /* no domain was given in the header line, set the default */
        co->domain = strdup(domain);
        if(!co->domain)
          badcookie = TRUE;
      }
    }

    if(!badcookie && !co->path && path) {
      /*
       * No path was given in the header line, set the default.  Note that the
       * passed-in path to this function MAY have a '?' and following part that
       * MUST NOT be stored as part of the path.
       */
      char *queryp = strchr(path, '?');

      /*
       * queryp is where the interesting part of the path ends, so now we
       * want to the find the last
       */
      char *endslash;
      if(!queryp)
        endslash = strrchr(path, '/');
      else
        endslash = memrchr(path, '/', (queryp - path));
      if(endslash) {
        size_t pathlen = (endslash-path + 1); /* include end slash */
        co->path = malloc(pathlen + 1); /* one extra for the zero byte */
        if(co->path) {
          memcpy(co->path, path, pathlen);
          co->path[pathlen] = 0; /* null-terminate */
          co->spath = sanitize_cookie_path(co->path);
          if(!co->spath)
            badcookie = TRUE; /* out of memory bad */
        }
        else
          badcookie = TRUE;
      }
    }

    /*
     * If we didn't get a cookie name, or a bad one, the this is an illegal
     * line so bail out.
     */
    if(badcookie || !co->name) {
      freecookie(co);
      return NULL;
    }

  }
  else {
    /*
     * This line is NOT a HTTP header style line, we do offer support for
     * reading the odd netscape cookies-file format here
     */
    char *ptr;
    char *firstptr;
    char *tok_buf = NULL;
    int fields;

    /*
     * IE introduced HTTP-only cookies to prevent XSS attacks. Cookies marked
     * with httpOnly after the domain name are not accessible from javascripts,
     * but since curl does not operate at javascript level, we include them
     * anyway. In Firefox's cookie files, these lines are preceded with
     * #HttpOnly_ and then everything is as usual, so we skip 10 characters of
     * the line..
     */
    if(strncmp(lineptr, "#HttpOnly_", 10) == 0) {
      lineptr += 10;
      co->httponly = TRUE;
    }

    if(lineptr[0]=='#') {
      /* don't even try the comments */
      free(co);
      return NULL;
    }
    /* strip off the possible end-of-line characters */
    ptr = strchr(lineptr, '\r');
    if(ptr)
      *ptr = 0; /* clear it */
    ptr = strchr(lineptr, '\n');
    if(ptr)
      *ptr = 0; /* clear it */

    firstptr = strtok_r(lineptr, "\t", &tok_buf); /* tokenize it on the TAB */

    /*
     * Now loop through the fields and init the struct we already have
     * allocated
     */
    for(ptr = firstptr, fields = 0; ptr && !badcookie;
        ptr = strtok_r(NULL, "\t", &tok_buf), fields++) {
      switch(fields) {
      case 0:
        if(ptr[0]=='.') /* skip preceding dots */
          ptr++;
        co->domain = strdup(ptr);
        if(!co->domain)
          badcookie = TRUE;
        break;
      case 1:
        /*
         * flag: A TRUE/FALSE value indicating if all machines within a given
         * domain can access the variable. Set TRUE when the cookie says
         * .domain.com and to false when the domain is complete www.domain.com
         */
        co->tailmatch = strcasecompare(ptr, "TRUE")?TRUE:FALSE;
        break;
      case 2:
        /* The file format allows the path field to remain not filled in */
        if(strcmp("TRUE", ptr) && strcmp("FALSE", ptr)) {
          /* only if the path doesn't look like a boolean option! */
          co->path = strdup(ptr);
          if(!co->path)
            badcookie = TRUE;
          else {
            co->spath = sanitize_cookie_path(co->path);
            if(!co->spath) {
              badcookie = TRUE; /* out of memory bad */
            }
          }
          break;
        }
        /* this doesn't look like a path, make one up! */
        co->path = strdup("/");
        if(!co->path)
          badcookie = TRUE;
        co->spath = strdup("/");
        if(!co->spath)
          badcookie = TRUE;
        fields++; /* add a field and fall down to secure */
        /* FALLTHROUGH */
      case 3:
        co->secure = FALSE;
        if(strcasecompare(ptr, "TRUE")) {
          if(secure || c->running)
            co->secure = TRUE;
          else
            badcookie = TRUE;
        }
        break;
      case 4:
        if(curlx_strtoofft(ptr, NULL, 10, &co->expires))
          badcookie = TRUE;
        break;
      case 5:
        co->name = strdup(ptr);
        if(!co->name)
          badcookie = TRUE;
        else {
          /* For Netscape file format cookies we check prefix on the name */
          if(strncasecompare("__Secure-", co->name, 9))
            co->prefix |= COOKIE_PREFIX__SECURE;
          else if(strncasecompare("__Host-", co->name, 7))
            co->prefix |= COOKIE_PREFIX__HOST;
        }
        break;
      case 6:
        co->value = strdup(ptr);
        if(!co->value)
          badcookie = TRUE;
        break;
      }
    }
    if(6 == fields) {
      /* we got a cookie with blank contents, fix it */
      co->value = strdup("");
      if(!co->value)
        badcookie = TRUE;
      else
        fields++;
    }

    if(!badcookie && (7 != fields))
      /* we did not find the sufficient number of fields */
      badcookie = TRUE;

    if(badcookie) {
      freecookie(co);
      return NULL;
    }

  }

  if(co->prefix & COOKIE_PREFIX__SECURE) {
    /* The __Secure- prefix only requires that the cookie be set secure */
    if(!co->secure) {
      freecookie(co);
      return NULL;
    }
  }
  if(co->prefix & COOKIE_PREFIX__HOST) {
    /*
     * The __Host- prefix requires the cookie to be secure, have a "/" path
     * and not have a domain set.
     */
    if(co->secure && co->path && strcmp(co->path, "/") == 0 && !co->tailmatch)
      ;
    else {
      freecookie(co);
      return NULL;
    }
  }

  if(!c->running &&    /* read from a file */
     c->newsession &&  /* clean session cookies */
     !co->expires) {   /* this is a session cookie since it doesn't expire! */
    freecookie(co);
    return NULL;
  }

  co->livecookie = c->running;
  co->creationtime = ++c->lastct;

  /*
   * Now we have parsed the incoming line, we must now check if this supersedes
   * an already existing cookie, which it may if the previous have the same
   * domain and path as this.
   */

  /* at first, remove expired cookies */
  if(!noexpire)
    remove_expired(c);

#ifdef USE_LIBPSL
  /*
   * Check if the domain is a Public Suffix and if yes, ignore the cookie. We
   * must also check that the data handle isn't NULL since the psl code will
   * dereference it.
   */
  if(data && (domain && co->domain && !Curl_host_is_ipnum(co->domain))) {
    const psl_ctx_t *psl = Curl_psl_use(data);
    int acceptable;

    if(psl) {
      acceptable = psl_is_cookie_domain_acceptable(psl, domain, co->domain);
      Curl_psl_release(data);
    }
    else
      acceptable = !bad_domain(domain);

    if(!acceptable) {
      infof(data, "cookie '%s' dropped, domain '%s' must not "
                  "set cookies for '%s'", co->name, domain, co->domain);
      freecookie(co);
      return NULL;
    }
  }
#endif

  myhash = cookiehash(co->domain);
  clist = c->cookies[myhash];
  replace_old = FALSE;
  while(clist) {
    if(strcasecompare(clist->name, co->name)) {
      /* the names are identical */

      if(clist->domain && co->domain) {
        if(strcasecompare(clist->domain, co->domain) &&
          (clist->tailmatch == co->tailmatch))
          /* The domains are identical */
          replace_old = TRUE;
      }
      else if(!clist->domain && !co->domain)
        replace_old = TRUE;

      if(replace_old) {
        /* the domains were identical */

        if(clist->spath && co->spath) {
          if(clist->secure && !co->secure && !secure) {
            size_t cllen;
            const char *sep;

            /*
             * A non-secure cookie may not overlay an existing secure cookie.
             * For an existing cookie "a" with path "/login", refuse a new
             * cookie "a" with for example path "/login/en", while the path
             * "/loginhelper" is ok.
             */

            sep = strchr(clist->spath + 1, '/');

            if(sep)
              cllen = sep - clist->spath;
            else
              cllen = strlen(clist->spath);

            if(strncasecompare(clist->spath, co->spath, cllen)) {
              freecookie(co);
              return NULL;
            }
          }
          else if(strcasecompare(clist->spath, co->spath))
            replace_old = TRUE;
          else
            replace_old = FALSE;
        }
        else if(!clist->spath && !co->spath)
          replace_old = TRUE;
        else
          replace_old = FALSE;

      }

      if(replace_old && !co->livecookie && clist->livecookie) {
        /*
         * Both cookies matched fine, except that the already present cookie is
         * "live", which means it was set from a header, while the new one was
         * read from a file and thus isn't "live". "live" cookies are preferred
         * so the new cookie is freed.
         */
        freecookie(co);
        return NULL;
      }

      if(replace_old) {
        co->next = clist->next; /* get the next-pointer first */

        /* when replacing, creationtime is kept from old */
        co->creationtime = clist->creationtime;

        /* then free all the old pointers */
        free(clist->name);
        free(clist->value);
        free(clist->domain);
        free(clist->path);
        free(clist->spath);
        free(clist->expirestr);
        free(clist->version);
        free(clist->maxage);

        *clist = *co;  /* then store all the new data */

        free(co);   /* free the newly allocated memory */
        co = clist; /* point to the previous struct instead */

        /*
         * We have replaced a cookie, now skip the rest of the list but make
         * sure the 'lastc' pointer is properly set
         */
        do {
          lastc = clist;
          clist = clist->next;
        } while(clist);
        break;
      }
    }
    lastc = clist;
    clist = clist->next;
  }

  if(c->running)
    /* Only show this when NOT reading the cookies from a file */
    infof(data, "%s cookie %s=\"%s\" for domain %s, path %s, "
          "expire %" CURL_FORMAT_CURL_OFF_T,
          replace_old?"Replaced":"Added", co->name, co->value,
          co->domain, co->path, co->expires);

  if(!replace_old) {
    /* then make the last item point on this new one */
    if(lastc)
      lastc->next = co;
    else
      c->cookies[myhash] = co;
    c->numcookies++; /* one more cookie in the jar */
  }

  /*
   * Now that we've added a new cookie to the jar, update the expiration
   * tracker in case it is the next one to expire.
   */
  if(co->expires && (co->expires < c->next_expiration))
    c->next_expiration = co->expires;

  return co;
}


static size_t cookie_hash_domain(const char *domain, const size_t len)
{
  const char *end = domain + len;
  size_t h = 5381;

  while(domain < end) {
    h += h << 5;
    h ^= Curl_raw_toupper(*domain++);
  }

  return (h % COOKIE_HASH_SIZE);
}


void Curl_cookie_freelist(struct Cookie *co)
{
  struct Cookie *next;
  while(co) {
    next = co->next;
    freecookie(co);
    co = next;
  }
}


static int cookie_sort(const void *p1, const void *p2)
{
  struct Cookie *c1 = *(struct Cookie **)p1;
  struct Cookie *c2 = *(struct Cookie **)p2;
  size_t l1, l2;

  /* 1 - compare cookie path lengths */
  l1 = c1->path ? strlen(c1->path) : 0;
  l2 = c2->path ? strlen(c2->path) : 0;

  if(l1 != l2)
    return (l2 > l1) ? 1 : -1 ; /* avoid size_t <=> int conversions */

  /* 2 - compare cookie domain lengths */
  l1 = c1->domain ? strlen(c1->domain) : 0;
  l2 = c2->domain ? strlen(c2->domain) : 0;

  if(l1 != l2)
    return (l2 > l1) ? 1 : -1 ;  /* avoid size_t <=> int conversions */

  /* 3 - compare cookie name lengths */
  l1 = c1->name ? strlen(c1->name) : 0;
  l2 = c2->name ? strlen(c2->name) : 0;

  if(l1 != l2)
    return (l2 > l1) ? 1 : -1;

  /* 4 - compare cookie creation time */
  return (c2->creationtime > c1->creationtime) ? 1 : -1;
}


void Curl_cookie_cleanup(struct CookieInfo *c)
{
  if(c) {
    unsigned int i;
    free(c->filename);
    for(i = 0; i < COOKIE_HASH_SIZE; i++)
      Curl_cookie_freelist(c->cookies[i]);
    free(c); /* free the base struct as well */
  }
}


static struct curl_slist *cookie_list(struct Curl_easy *data)
{
  struct curl_slist *list = NULL;
  struct curl_slist *beg;
  struct Cookie *c;
  char *line;
  unsigned int i;

  if(!data->cookies || (data->cookies->numcookies == 0))
    return NULL;

  for(i = 0; i < COOKIE_HASH_SIZE; i++) {
    for(c = data->cookies->cookies[i]; c; c = c->next) {
      if(!c->domain)
        continue;
      line = get_netscape_format(c);
      if(!line) {
        curl_slist_free_all(list);
        return NULL;
      }
      beg = Curl_slist_append_nodup(list, line);
      if(!beg) {
        free(line);
        curl_slist_free_all(list);
        return NULL;
      }
      list = beg;
    }
  }

  return list;
}


void Curl_cookie_clearsess(struct CookieInfo *cookies)
{
  struct Cookie *first, *curr, *next, *prev = NULL;
  unsigned int i;

  if(!cookies)
    return;

  for(i = 0; i < COOKIE_HASH_SIZE; i++) {
    if(!cookies->cookies[i])
      continue;

    first = curr = prev = cookies->cookies[i];

    for(; curr; curr = next) {
      next = curr->next;
      if(!curr->expires) {
        if(first == curr)
          first = next;

        if(prev == curr)
          prev = next;
        else
          prev->next = next;

        freecookie(curr);
        cookies->numcookies--;
      }
      else
        prev = curr;
    }

    cookies->cookies[i] = first;
  }
}


static void remove_expired(struct CookieInfo *cookies)
{
  struct Cookie *co, *nx;
  curl_off_t now = (curl_off_t)time(NULL);
  unsigned int i;

  /*
   * If the earliest expiration timestamp in the jar is in the future we can
   * skip scanning the whole jar and instead exit early as there won't be any
   * cookies to evict.  If we need to evict however, reset the next_expiration
   * counter in order to track the next one. In case the recorded first
   * expiration is the max offset, then perform the safe fallback of checking
   * all cookies.
   */
  if(now < cookies->next_expiration &&
      cookies->next_expiration != CURL_OFF_T_MAX)
    return;
  else
    cookies->next_expiration = CURL_OFF_T_MAX;

  for(i = 0; i < COOKIE_HASH_SIZE; i++) {
    struct Cookie *pv = NULL;
    co = cookies->cookies[i];
    while(co) {
      nx = co->next;
      if(co->expires && co->expires < now) {
        if(!pv) {
          cookies->cookies[i] = co->next;
        }
        else {
          pv->next = co->next;
        }
        cookies->numcookies--;
        freecookie(co);
      }
      else {
        /*
         * If this cookie has an expiration timestamp earlier than what we've
         * seen so far then record it for the next round of expirations.
         */
        if(co->expires && co->expires < cookies->next_expiration)
          cookies->next_expiration = co->expires;
        pv = co;
      }
      co = nx;
    }
  }
}


static bool pathmatch(const char *cookie_path, const char *request_uri)
{
  size_t cookie_path_len;
  size_t uri_path_len;
  char *uri_path = NULL;
  char *pos;
  bool ret = FALSE;

  /* cookie_path must not have last '/' separator. ex: /sample */
  cookie_path_len = strlen(cookie_path);
  if(1 == cookie_path_len) {
    /* cookie_path must be '/' */
    return TRUE;
  }

  uri_path = strdup(request_uri);
  if(!uri_path)
    return FALSE;
  pos = strchr(uri_path, '?');
  if(pos)
    *pos = 0x0;

  /* #-fragments are already cut off! */
  if(0 == strlen(uri_path) || uri_path[0] != '/') {
    strstore(&uri_path, "/");
    if(!uri_path)
      return FALSE;
  }

  /*
   * here, RFC6265 5.1.4 says
   *  4. Output the characters of the uri-path from the first character up
   *     to, but not including, the right-most %x2F ("/").
   *  but URL path /hoge?fuga=xxx means /hoge/index.cgi?fuga=xxx in some site
   *  without redirect.
   *  Ignore this algorithm because /hoge is uri path for this case
   *  (uri path is not /).
   */

  uri_path_len = strlen(uri_path);

  if(uri_path_len < cookie_path_len) {
    ret = FALSE;
    goto pathmatched;
  }

  /* not using checkprefix() because matching should be case-sensitive */
  if(strncmp(cookie_path, uri_path, cookie_path_len)) {
    ret = FALSE;
    goto pathmatched;
  }

  /* The cookie-path and the uri-path are identical. */
  if(cookie_path_len == uri_path_len) {
    ret = TRUE;
    goto pathmatched;
  }

  /* here, cookie_path_len < uri_path_len */
  if(uri_path[cookie_path_len] == '/') {
    ret = TRUE;
    goto pathmatched;
  }

  ret = FALSE;

pathmatched:
  free(uri_path);
  return ret;
}


static size_t cookiehash(const char * const domain)
{
  const char *top;
  size_t len;

  if(!domain || Curl_host_is_ipnum(domain))
    return 0;

  top = get_top_domain(domain, &len);
  return cookie_hash_domain(top, len);
}


static char *get_netscape_format(const struct Cookie *co)
{
  return aprintf(
    "%s"     /* httponly preamble */
    "%s%s\t" /* domain */
    "%s\t"   /* tailmatch */
    "%s\t"   /* path */
    "%s\t"   /* secure */
    "%" CURL_FORMAT_CURL_OFF_T "\t"   /* expires */
    "%s\t"   /* name */
    "%s",    /* value */
    co->httponly?"#HttpOnly_":"",
    /*
     * Make sure all domains are prefixed with a dot if they allow
     * tailmatching. This is Mozilla-style.
     */
    (co->tailmatch && co->domain && co->domain[0] != '.')? ".":"",
    co->domain?co->domain:"unknown",
    co->tailmatch?"TRUE":"FALSE",
    co->path?co->path:"/",
    co->secure?"TRUE":"FALSE",
    co->expires,
    co->name,
    co->value?co->value:"");
}


static void strstore(char **str, const char *newstr)
{
  free(*str);
  *str = strdup(newstr);
}


struct curl_slist *Curl_cookie_list(struct Curl_easy *data)
{
  struct curl_slist *list;
  Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
  list = cookie_list(data);
  Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
  return list;
}


static int cookie_sort_ct(const void *p1, const void *p2)
{
  struct Cookie *c1 = *(struct Cookie **)p1;
  struct Cookie *c2 = *(struct Cookie **)p2;

  return (c2->creationtime > c1->creationtime) ? 1 : -1;
}


static struct Cookie *dup_cookie(struct Cookie *src)
{
  struct Cookie *d = calloc(sizeof(struct Cookie), 1);
  if(d) {
    CLONE(expirestr);
    CLONE(domain);
    CLONE(path);
    CLONE(spath);
    CLONE(name);
    CLONE(value);
    CLONE(maxage);
    CLONE(version);
    d->expires = src->expires;
    d->tailmatch = src->tailmatch;
    d->secure = src->secure;
    d->livecookie = src->livecookie;
    d->httponly = src->httponly;
    d->creationtime = src->creationtime;
  }
  return d;

  fail:
  freecookie(d);
  return NULL;
}


static const char *get_top_domain(const char * const domain, size_t *outlen)
{
  size_t len = 0;
  const char *first = NULL, *last;

  if(domain) {
    len = strlen(domain);
    last = memrchr(domain, '.', len);
    if(last) {
      first = memrchr(domain, '.', (last - domain));
      if(first)
        len -= (++first - domain);
    }
  }

  if(outlen)
    *outlen = len;

  return first? first: domain;
}


void Curl_cookie_loadfiles(struct Curl_easy *data)
{
  struct curl_slist *list = data->state.cookielist;
  if(list) {
    Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
    while(list) {
      struct CookieInfo *newcookies = Curl_cookie_init(data,
                                        list->data,
                                        data->cookies,
                                        data->set.cookiesession);
      if(!newcookies)
        /*
         * Failure may be due to OOM or a bad cookie; both are ignored
         * but only the first should be
         */
        infof(data, "ignoring failed cookie_init for %s", list->data);
      else
        data->cookies = newcookies;
      list = list->next;
    }
    curl_slist_free_all(data->state.cookielist); /* clean up list */
    data->state.cookielist = NULL; /* don't do this again! */
    Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
  }
}


static bool tailmatch(const char *cooke_domain, const char *hostname)
{
  size_t cookie_domain_len = strlen(cooke_domain);
  size_t hostname_len = strlen(hostname);

  if(hostname_len < cookie_domain_len)
    return FALSE;

  if(!strcasecompare(cooke_domain, hostname + hostname_len-cookie_domain_len))
    return FALSE;

  /*
   * A lead char of cookie_domain is not '.'.
   * RFC6265 4.1.2.3. The Domain Attribute says:
   * For example, if the value of the Domain attribute is
   * "example.com", the user agent will include the cookie in the Cookie
   * header when making HTTP requests to example.com, www.example.com, and
   * www.corp.example.com.
   */
  if(hostname_len == cookie_domain_len)
    return TRUE;
  if('.' == *(hostname + hostname_len - cookie_domain_len - 1))
    return TRUE;
  return FALSE;
}


static char *sanitize_cookie_path(const char *cookie_path)
{
  size_t len;
  char *new_path = strdup(cookie_path);
  if(!new_path)
    return NULL;

  /* some stupid site sends path attribute with '"'. */
  len = strlen(new_path);
  if(new_path[0] == '\"') {
    memmove((void *)new_path, (const void *)(new_path + 1), len);
    len--;
  }
  if(len && (new_path[len - 1] == '\"')) {
    new_path[len - 1] = 0x0;
    len--;
  }

  /* RFC6265 5.2.4 The Path Attribute */
  if(new_path[0] != '/') {
    /* Let cookie-path be the default-path. */
    strstore(&new_path, "/");
    return new_path;
  }

  /* convert /hoge/ to /hoge */
  if(len && new_path[len - 1] == '/') {
    new_path[len - 1] = 0x0;
  }

  return new_path;
}


void Curl_cookie_clearall(struct CookieInfo *cookies)
{
  if(cookies) {
    unsigned int i;
    for(i = 0; i < COOKIE_HASH_SIZE; i++) {
      Curl_cookie_freelist(cookies->cookies[i]);
      cookies->cookies[i] = NULL;
    }
    cookies->numcookies = 0;
  }
}


Fix handling of APNG with 0 delay_den (#313)

Status DecodeImageAPNG(Span<const uint8_t> bytes, ThreadPool* pool,
                       CodecInOut* io) {
  Reader r;
  unsigned int id, i, j, w, h, w0, h0, x0, y0;
  unsigned int delay_num, delay_den, dop, bop, rowbytes, imagesize;
  unsigned char sig[8];
  png_structp png_ptr;
  png_infop info_ptr;
  CHUNK chunk;
  CHUNK chunkIHDR;
  std::vector<CHUNK> chunksInfo;
  bool isAnimated = false;
  bool skipFirst = false;
  bool hasInfo = false;
  bool all_dispose_bg = true;
  APNGFrame frameRaw = {};

  r = {bytes.data(), bytes.data() + bytes.size()};
  // Not an aPNG => not an error
  unsigned char png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10};
  if (r.Read(sig, 8) || memcmp(sig, png_signature, 8) != 0) {
    return false;
  }
  id = read_chunk(&r, &chunkIHDR);

  io->frames.clear();
  io->dec_pixels = 0;
  io->metadata.m.SetUintSamples(8);
  io->metadata.m.SetAlphaBits(8);
  io->metadata.m.color_encoding =
      ColorEncoding::SRGB();  // todo: get data from png metadata
  (void)io->dec_hints.Foreach(
      [](const std::string& key, const std::string& /*value*/) {
        JXL_WARNING("APNG decoder ignoring %s hint", key.c_str());
        return true;
      });

  bool errorstate = true;
  if (id == kId_IHDR && chunkIHDR.size == 25) {
    w0 = w = png_get_uint_32(chunkIHDR.p + 8);
    h0 = h = png_get_uint_32(chunkIHDR.p + 12);

    if (w > cMaxPNGSize || h > cMaxPNGSize) {
      return false;
    }

    x0 = 0;
    y0 = 0;
    delay_num = 1;
    delay_den = 10;
    dop = 0;
    bop = 0;
    rowbytes = w * 4;
    imagesize = h * rowbytes;

    frameRaw.p = new unsigned char[imagesize];
    frameRaw.rows = new png_bytep[h * sizeof(png_bytep)];
    for (j = 0; j < h; j++) frameRaw.rows[j] = frameRaw.p + j * rowbytes;

    if (!processing_start(png_ptr, info_ptr, (void*)&frameRaw, hasInfo,
                          chunkIHDR, chunksInfo)) {
      bool last_base_was_none = true;
      while (!r.Eof()) {
        id = read_chunk(&r, &chunk);
        if (!id) break;
        JXL_ASSERT(chunk.p != nullptr);

        if (id == kId_acTL && !hasInfo && !isAnimated) {
          isAnimated = true;
          skipFirst = true;
          io->metadata.m.have_animation = true;
          io->metadata.m.animation.tps_numerator = 1000;
        } else if (id == kId_IEND ||
                   (id == kId_fcTL && (!hasInfo || isAnimated))) {
          if (hasInfo) {
            if (!processing_finish(png_ptr, info_ptr)) {
              ImageBundle bundle(&io->metadata.m);
              bundle.duration = delay_num * 1000 / delay_den;
              bundle.origin.x0 = x0;
              bundle.origin.y0 = y0;
              // TODO(veluca): this could in principle be implemented.
              if (last_base_was_none && !all_dispose_bg &&
                  (x0 != 0 || y0 != 0 || w0 != w || h0 != h || bop != 0)) {
                return JXL_FAILURE(
                    "APNG with dispose-to-0 is not supported for non-full or "
                    "blended frames");
              }
              switch (dop) {
                case 0:
                  bundle.use_for_next_frame = true;
                  last_base_was_none = false;
                  all_dispose_bg = false;
                  break;
                case 2:
                  bundle.use_for_next_frame = false;
                  all_dispose_bg = false;
                  break;
                default:
                  bundle.use_for_next_frame = false;
                  last_base_was_none = true;
              }
              bundle.blend = bop != 0;
              io->dec_pixels += w0 * h0;

              Image3F sub_frame(w0, h0);
              ImageF sub_frame_alpha(w0, h0);
              for (size_t y = 0; y < h0; ++y) {
                float* const JXL_RESTRICT row_r = sub_frame.PlaneRow(0, y);
                float* const JXL_RESTRICT row_g = sub_frame.PlaneRow(1, y);
                float* const JXL_RESTRICT row_b = sub_frame.PlaneRow(2, y);
                float* const JXL_RESTRICT row_alpha = sub_frame_alpha.Row(y);
                uint8_t* const f = frameRaw.rows[y];
                for (size_t x = 0; x < w0; ++x) {
                  if (f[4 * x + 3] == 0) {
                    row_alpha[x] = 0;
                    row_r[x] = 0;
                    row_g[x] = 0;
                    row_b[x] = 0;
                    continue;
                  }
                  row_r[x] = f[4 * x + 0] * (1.f / 255);
                  row_g[x] = f[4 * x + 1] * (1.f / 255);
                  row_b[x] = f[4 * x + 2] * (1.f / 255);
                  row_alpha[x] = f[4 * x + 3] * (1.f / 255);
                }
              }
              bundle.SetFromImage(std::move(sub_frame), ColorEncoding::SRGB());
              bundle.SetAlpha(std::move(sub_frame_alpha),
                              /*alpha_is_premultiplied=*/false);
              io->frames.push_back(std::move(bundle));
            } else {
              delete[] chunk.p;
              break;
            }
          }

          if (id == kId_IEND) {
            errorstate = false;
            break;
          }
          // At this point the old frame is done. Let's start a new one.
          w0 = png_get_uint_32(chunk.p + 12);
          h0 = png_get_uint_32(chunk.p + 16);
          x0 = png_get_uint_32(chunk.p + 20);
          y0 = png_get_uint_32(chunk.p + 24);
          delay_num = png_get_uint_16(chunk.p + 28);
          delay_den = png_get_uint_16(chunk.p + 30);
          dop = chunk.p[32];
          bop = chunk.p[33];

          if (!delay_den) delay_den = 100;

          if (w0 > cMaxPNGSize || h0 > cMaxPNGSize || x0 > cMaxPNGSize ||
              y0 > cMaxPNGSize || x0 + w0 > w || y0 + h0 > h || dop > 2 ||
              bop > 1) {
            delete[] chunk.p;
            break;
          }

          if (hasInfo) {
            memcpy(chunkIHDR.p + 8, chunk.p + 12, 8);
            if (processing_start(png_ptr, info_ptr, (void*)&frameRaw, hasInfo,
                                 chunkIHDR, chunksInfo)) {
              delete[] chunk.p;
              break;
            }
          } else
            skipFirst = false;

          if (io->frames.size() == (skipFirst ? 1 : 0)) {
            bop = 0;
            if (dop == 2) dop = 1;
          }
        } else if (id == kId_IDAT) {
          hasInfo = true;
          if (processing_data(png_ptr, info_ptr, chunk.p, chunk.size)) {
            delete[] chunk.p;
            break;
          }
        } else if (id == kId_fdAT && isAnimated) {
          png_save_uint_32(chunk.p + 4, chunk.size - 16);
          memcpy(chunk.p + 8, "IDAT", 4);
          if (processing_data(png_ptr, info_ptr, chunk.p + 4, chunk.size - 4)) {
            delete[] chunk.p;
            break;
          }
        } else if (!isAbc(chunk.p[4]) || !isAbc(chunk.p[5]) ||
                   !isAbc(chunk.p[6]) || !isAbc(chunk.p[7])) {
          delete[] chunk.p;
          break;
        } else if (!hasInfo) {
          if (processing_data(png_ptr, info_ptr, chunk.p, chunk.size)) {
            delete[] chunk.p;
            break;
          }
          chunksInfo.push_back(chunk);
          continue;
        }
        delete[] chunk.p;
      }
    }
    delete[] frameRaw.rows;
    delete[] frameRaw.p;
  }

  for (i = 0; i < chunksInfo.size(); i++) delete[] chunksInfo[i].p;

  chunksInfo.clear();
  delete[] chunkIHDR.p;

  if (errorstate) return false;
  SetIntensityTarget(io);
  return true;
}

libjxl v0.3.7 is affected by a Divide By Zero in issue in lib/extras/codec_apng.cc jxl::DecodeImageAPNG(). When encoding a malicous APNG file using cjxl, an attacker can trigger a denial of service.


constexpr bool isAbc(char c) {
  return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}


  bool Read(void* data, size_t len) {
    size_t cap = last - next;
    size_t to_copy = std::min(cap, len);
    memcpy(data, next, to_copy);
    next += to_copy;
    return (len == to_copy);
  }


void info_fn(png_structp png_ptr, png_infop info_ptr) {
  png_set_expand(png_ptr);
  png_set_strip_16(png_ptr);
  png_set_gray_to_rgb(png_ptr);
  png_set_palette_to_rgb(png_ptr);
  png_set_add_alpha(png_ptr, 0xff, PNG_FILLER_AFTER);
  (void)png_set_interlace_handling(png_ptr);
  png_read_update_info(png_ptr, info_ptr);
}


int processing_start(png_structp& png_ptr, png_infop& info_ptr, void* frame_ptr,
                     bool hasInfo, CHUNK& chunkIHDR,
                     std::vector<CHUNK>& chunksInfo) {
  unsigned char header[8] = {137, 80, 78, 71, 13, 10, 26, 10};

  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
  info_ptr = png_create_info_struct(png_ptr);
  if (!png_ptr || !info_ptr) return 1;

  if (setjmp(png_jmpbuf(png_ptr))) {
    png_destroy_read_struct(&png_ptr, &info_ptr, 0);
    return 1;
  }

  png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
  png_set_progressive_read_fn(png_ptr, frame_ptr, info_fn, row_fn, NULL);

  png_process_data(png_ptr, info_ptr, header, 8);
  png_process_data(png_ptr, info_ptr, chunkIHDR.p, chunkIHDR.size);

  if (hasInfo) {
    for (unsigned int i = 0; i < chunksInfo.size(); i++) {
      png_process_data(png_ptr, info_ptr, chunksInfo[i].p, chunksInfo[i].size);
    }
  }
  return 0;
}


int processing_data(png_structp png_ptr, png_infop info_ptr, unsigned char* p,
                    unsigned int size) {
  if (!png_ptr || !info_ptr) return 1;

  if (setjmp(png_jmpbuf(png_ptr))) {
    png_destroy_read_struct(&png_ptr, &info_ptr, 0);
    return 1;
  }

  png_process_data(png_ptr, info_ptr, p, size);
  return 0;
}


inline unsigned int read_chunk(Reader* r, CHUNK* pChunk) {
  unsigned char len[4];
  pChunk->size = 0;
  pChunk->p = 0;
  if (r->Read(&len, 4)) {
    const auto size = png_get_uint_32(len);
    // Check first, to avoid overflow.
    if (size > kMaxPNGChunkSize) {
      JXL_WARNING("APNG chunk size is too big");
      return 0;
    }
    pChunk->size = size + 12;
    pChunk->p = new unsigned char[pChunk->size];
    memcpy(pChunk->p, len, 4);
    if (r->Read(pChunk->p + 4, pChunk->size - 4)) {
      return *(unsigned int*)(pChunk->p + 4);
    }
  }
  return 0;
}


int processing_finish(png_structp png_ptr, png_infop info_ptr) {
  unsigned char footer[12] = {0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130};

  if (!png_ptr || !info_ptr) return 1;

  if (setjmp(png_jmpbuf(png_ptr))) {
    png_destroy_read_struct(&png_ptr, &info_ptr, 0);
    return 1;
  }

  png_process_data(png_ptr, info_ptr, footer, 12);
  png_destroy_read_struct(&png_ptr, &info_ptr, 0);

  return 0;
}


void row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num,
            int pass) {
  APNGFrame* frame = (APNGFrame*)png_get_progressive_ptr(png_ptr);
  png_progressive_combine_row(png_ptr, frame->rows[row_num], new_row);
}


realpath: Set errno to ENAMETOOLONG for result larger than PATH_MAX [BZ #28770]

realpath returns an allocated string when the result exceeds PATH_MAX,
which is unexpected when its second argument is not NULL.  This results
in the second argument (resolved) being uninitialized and also results
in a memory leak since the caller expects resolved to be the same as the
returned value.

Return NULL and set errno to ENAMETOOLONG if the result exceeds
PATH_MAX.  This fixes [BZ #28770], which is CVE-2021-3998.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
Signed-off-by: Siddhesh Poyarekar <siddhesh@sourceware.org>

__realpath (const char *name, char *resolved)
{
  #ifdef GCC_BOGUS_WRETURN_LOCAL_ADDR
   #warning "GCC might issue a bogus -Wreturn-local-addr warning here."
   #warning "See <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93644>."
  #endif
  struct scratch_buffer rname_buffer;
  return realpath_stk (name, resolved, &rname_buffer);
}


file_accessible (char const *file)
{
# if defined _LIBC || HAVE_FACCESSAT
  return __faccessat (AT_FDCWD, file, F_OK, AT_EACCESS) == 0;
# else
  struct stat st;
  return __stat (file, &st) == 0 || errno == EOVERFLOW;
# endif
}


do_test (void)
{
  char *base = support_create_and_chdir_toolong_temp_directory (BASENAME);

  char buf[PATH_MAX + 1];
  const char *res = realpath (".", buf);

  /* canonicalize.c states that if the real path is >= PATH_MAX, then
     realpath returns NULL and sets ENAMETOOLONG.  */
  TEST_VERIFY (res == NULL);
  TEST_VERIFY (errno == ENAMETOOLONG);

  free (base);
  return 0;
}


__canonicalize_file_name (const char *name)
{
  return __realpath (name, NULL);
}


suffix_requires_dir_check (char const *end)
{
  /* If END does not start with a slash, the suffix is OK.  */
  while (ISSLASH (*end))
    {
      /* Two or more slashes act like a single slash.  */
      do
        end++;
      while (ISSLASH (*end));

      switch (*end++)
        {
        default: return false;  /* An ordinary file name component is OK.  */
        case '\0': return true; /* Trailing "/" is trouble.  */
        case '.': break;        /* Possibly "." or "..".  */
        }
      /* Trailing "/.", or "/.." even if not trailing, is trouble.  */
      if (!*end || (*end == '.' && (!end[1] || ISSLASH (end[1]))))
        return true;
    }

  return false;
}


__old_realpath (const char *name, char *resolved)
{
  if (resolved == NULL)
    {
      __set_errno (EINVAL);
      return NULL;
    }

  return __realpath (name, resolved);
}


realpath_stk (const char *name, char *resolved,
              struct scratch_buffer *rname_buf)
{
  char *dest;
  char const *start;
  char const *end;
  int num_links = 0;

  if (name == NULL)
    {
      /* As per Single Unix Specification V2 we must return an error if
         either parameter is a null pointer.  We extend this to allow
         the RESOLVED parameter to be NULL in case the we are expected to
         allocate the room for the return value.  */
      __set_errno (EINVAL);
      return NULL;
    }

  if (name[0] == '\0')
    {
      /* As per Single Unix Specification V2 we must return an error if
         the name argument points to an empty string.  */
      __set_errno (ENOENT);
      return NULL;
    }

  struct scratch_buffer extra_buffer, link_buffer;
  scratch_buffer_init (&extra_buffer);
  scratch_buffer_init (&link_buffer);
  scratch_buffer_init (rname_buf);
  char *rname_on_stack = rname_buf->data;
  char *rname = rname_on_stack;
  bool end_in_extra_buffer = false;
  bool failed = true;

  /* This is always zero for Posix hosts, but can be 2 for MS-Windows
     and MS-DOS X:/foo/bar file names.  */
  idx_t prefix_len = FILE_SYSTEM_PREFIX_LEN (name);

  if (!IS_ABSOLUTE_FILE_NAME (name))
    {
      while (!__getcwd (rname, rname_buf->length))
        {
          if (errno != ERANGE)
            {
              dest = rname;
              goto error;
            }
          if (!scratch_buffer_grow (rname_buf))
            goto error_nomem;
          rname = rname_buf->data;
        }
      dest = __rawmemchr (rname, '\0');
      start = name;
      prefix_len = FILE_SYSTEM_PREFIX_LEN (rname);
    }
  else
    {
      dest = __mempcpy (rname, name, prefix_len);
      *dest++ = '/';
      if (DOUBLE_SLASH_IS_DISTINCT_ROOT)
        {
          if (prefix_len == 0 /* implies ISSLASH (name[0]) */
              && ISSLASH (name[1]) && !ISSLASH (name[2]))
            *dest++ = '/';
          *dest = '\0';
        }
      start = name + prefix_len;
    }

  for ( ; *start; start = end)
    {
      /* Skip sequence of multiple file name separators.  */
      while (ISSLASH (*start))
        ++start;

      /* Find end of component.  */
      for (end = start; *end && !ISSLASH (*end); ++end)
        /* Nothing.  */;

      /* Length of this file name component; it can be zero if a file
         name ends in '/'.  */
      idx_t startlen = end - start;

      if (startlen == 0)
        break;
      else if (startlen == 1 && start[0] == '.')
        /* nothing */;
      else if (startlen == 2 && start[0] == '.' && start[1] == '.')
        {
          /* Back up to previous component, ignore if at root already.  */
          if (dest > rname + prefix_len + 1)
            for (--dest; dest > rname && !ISSLASH (dest[-1]); --dest)
              continue;
          if (DOUBLE_SLASH_IS_DISTINCT_ROOT
              && dest == rname + 1 && !prefix_len
              && ISSLASH (*dest) && !ISSLASH (dest[1]))
            dest++;
        }
      else
        {
          if (!ISSLASH (dest[-1]))
            *dest++ = '/';

          while (rname + rname_buf->length - dest
                 < startlen + sizeof dir_suffix)
            {
              idx_t dest_offset = dest - rname;
              if (!scratch_buffer_grow_preserve (rname_buf))
                goto error_nomem;
              rname = rname_buf->data;
              dest = rname + dest_offset;
            }

          dest = __mempcpy (dest, start, startlen);
          *dest = '\0';

          char *buf;
          ssize_t n;
          while (true)
            {
              buf = link_buffer.data;
              idx_t bufsize = link_buffer.length;
              n = __readlink (rname, buf, bufsize - 1);
              if (n < bufsize - 1)
                break;
              if (!scratch_buffer_grow (&link_buffer))
                goto error_nomem;
            }
          if (0 <= n)
            {
              if (++num_links > __eloop_threshold ())
                {
                  __set_errno (ELOOP);
                  goto error;
                }

              buf[n] = '\0';

              char *extra_buf = extra_buffer.data;
              idx_t end_idx IF_LINT (= 0);
              if (end_in_extra_buffer)
                end_idx = end - extra_buf;
              size_t len = strlen (end);
              if (INT_ADD_OVERFLOW (len, n))
                {
                  __set_errno (ENOMEM);
                  goto error_nomem;
                }
              while (extra_buffer.length <= len + n)
                {
                  if (!scratch_buffer_grow_preserve (&extra_buffer))
                    goto error_nomem;
                  extra_buf = extra_buffer.data;
                }
              if (end_in_extra_buffer)
                end = extra_buf + end_idx;

              /* Careful here, end may be a pointer into extra_buf... */
              memmove (&extra_buf[n], end, len + 1);
              name = end = memcpy (extra_buf, buf, n);
              end_in_extra_buffer = true;

              if (IS_ABSOLUTE_FILE_NAME (buf))
                {
                  idx_t pfxlen = FILE_SYSTEM_PREFIX_LEN (buf);

                  dest = __mempcpy (rname, buf, pfxlen);
                  *dest++ = '/'; /* It's an absolute symlink */
                  if (DOUBLE_SLASH_IS_DISTINCT_ROOT)
                    {
                      if (ISSLASH (buf[1]) && !ISSLASH (buf[2]) && !pfxlen)
                        *dest++ = '/';
                      *dest = '\0';
                    }
                  /* Install the new prefix to be in effect hereafter.  */
                  prefix_len = pfxlen;
                }
              else
                {
                  /* Back up to previous component, ignore if at root
                     already: */
                  if (dest > rname + prefix_len + 1)
                    for (--dest; dest > rname && !ISSLASH (dest[-1]); --dest)
                      continue;
                  if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1
                      && ISSLASH (*dest) && !ISSLASH (dest[1]) && !prefix_len)
                    dest++;
                }
            }
          else if (! (suffix_requires_dir_check (end)
                      ? dir_check (rname, dest)
                      : errno == EINVAL))
            goto error;
        }
    }
  if (dest > rname + prefix_len + 1 && ISSLASH (dest[-1]))
    --dest;
  if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1 && !prefix_len
      && ISSLASH (*dest) && !ISSLASH (dest[1]))
    dest++;
  failed = false;

error:
  *dest++ = '\0';
  if (resolved != NULL)
    {
      if (dest - rname <= get_path_max ())
	rname = strcpy (resolved, rname);
      else
	{
	  failed = true;
	  __set_errno (ENAMETOOLONG);
	}
    }

error_nomem:
  scratch_buffer_free (&extra_buffer);
  scratch_buffer_free (&link_buffer);

  if (failed || rname == resolved)
    {
      scratch_buffer_free (rname_buf);
      return failed ? NULL : resolved;
    }

  return scratch_buffer_dupfree (rname_buf, dest - rname);
}


dir_check (char *dir, char *dirend)
{
  strcpy (dirend, dir_suffix);
  return file_accessible (dir);
}


get_path_max (void)
{
# ifdef PATH_MAX
  long int path_max = PATH_MAX;
# else
  /* The caller invoked realpath with a null RESOLVED, even though
     PATH_MAX is not defined as a constant.  The glibc manual says
     programs should not do this, and POSIX says the behavior is undefined.
     Historically, glibc here used the result of pathconf, or 1024 if that
     failed; stay consistent with this (dubious) historical practice.  */
  int err = errno;
  long int path_max = __pathconf ("/", _PC_PATH_MAX);
  __set_errno (err);
# endif
  return path_max < 0 ? 1024 : path_max <= IDX_MAX ? path_max : IDX_MAX;
}


static GF_Err store_meta_item_sample_ref_offsets(GF_ISOFile *movie, GF_List *writers, GF_MetaBox *meta)
{
	u32 i, count;
	TrackWriter *writer;
	GF_Box *stco;
	GF_SampleToChunkBox *stsc;

	if (!movie->moov) return GF_OK;
	if (!meta->item_locations) return GF_OK;
	if (!meta->use_item_sample_sharing) return GF_OK;

	//switch all our tables
	i=0;
	while ((writer = (TrackWriter*)gf_list_enum(writers, &i))) {
		//don't delete them !!!
		stsc = writer->stbl->SampleToChunk;
		stco = writer->stbl->ChunkOffset;
		s32 stsc_pos = gf_list_del_item(writer->stbl->child_boxes, stsc);
		s32 stco_pos = gf_list_del_item(writer->stbl->child_boxes, stco);
		writer->stbl->SampleToChunk = writer->stsc;
		writer->stbl->ChunkOffset = writer->stco;
		gf_list_insert(writer->stbl->child_boxes, writer->stsc, stsc_pos);
		gf_list_insert(writer->stbl->child_boxes, writer->stco, stco_pos);
		writer->stco = stco;
		writer->stsc = stsc;
	}

	count = gf_list_count(meta->item_locations->location_entries);
	for (i=0; i<count; i++) {
		u32 j;
		GF_ItemExtentEntry *entry;
		GF_ItemLocationEntry *iloc = (GF_ItemLocationEntry *)gf_list_get(meta->item_locations->location_entries, i);
		/*get item info*/
		GF_ItemInfoEntryBox *iinf = NULL;
		j=0;
		while ((iinf = (GF_ItemInfoEntryBox *)gf_list_enum(meta->item_infos->item_infos, &j))) {
			if (iinf->item_ID==iloc->item_ID) break;
		}
		if (!iinf || !iinf->tk_id || !iinf->sample_num) continue;

		entry = (GF_ItemExtentEntry *)gf_list_get(iloc->extent_entries, 0);
		if (!entry) continue;

		GF_ISOSample *samp = gf_isom_get_sample_info(movie, gf_isom_get_track_by_id(movie, iinf->tk_id), iinf->sample_num, NULL, &entry->extent_offset);
		if (samp) gf_isom_sample_del(&samp);
		entry->extent_offset -= iloc->base_offset;
	}

	//and re-switch our table. We have to do it that way because it is
	//needed when the moov is written first
	i=0;
	while ((writer = (TrackWriter*)gf_list_enum(writers, &i))) {
		//don't delete them !!!
		stsc = writer->stsc;
		stco = writer->stco;
		writer->stsc = writer->stbl->SampleToChunk;
		writer->stco = writer->stbl->ChunkOffset;
		s32 stsc_pos = gf_list_del_item(writer->stbl->child_boxes, writer->stsc);
		s32 stco_pos = gf_list_del_item(writer->stbl->child_boxes, writer->stco);

		writer->stbl->SampleToChunk = stsc;
		writer->stbl->ChunkOffset = stco;
		gf_list_insert(writer->stbl->child_boxes, stsc, stsc_pos);
		gf_list_insert(writer->stbl->child_boxes, stco, stco_pos);
	}
	return GF_OK;
}

An issue was discovered in GPAC version 0.8.0 and 1.0.1. There is an invalid pointer dereference in the function SetupWriters() in isomedia/isom_store.c.


static void muxer_report_progress(MovieWriter *mw)
{
	if (mw->movie->progress_cbk) {
		mw->movie->progress_cbk(mw->movie->progress_cbk_udta, mw->nb_done, mw->total_samples);
	} else {
		gf_set_progress("ISO File Writing", mw->nb_done, mw->total_samples);
	}
}


GF_Err DoWrite(MovieWriter *mw, GF_List *writers, GF_BitStream *bs, u8 Emulation, u64 StartOffset)
{
	u32 i;
	GF_Err e;
	TrackWriter *writer;
	u64 offset, sampOffset, predOffset;
	u32 chunkNumber, descIndex, sampSize;
	Bool force;
	GF_StscEntry *stsc_ent;
	u64 size, mdatSize = 0;
	GF_ISOFile *movie = mw->movie;

	/*write meta content first - WE DON'T support fragmentation of resources in ISOM atm*/
	if (movie->openMode != GF_ISOM_OPEN_WRITE) {
		if (movie->meta) {
			e = DoWriteMeta(movie, movie->meta, bs, Emulation, StartOffset, &size);
			if (e) return e;
			mdatSize += size;
			StartOffset += size;
		}
		if (movie->moov && movie->moov->meta) {
			e = DoWriteMeta(movie, movie->meta, bs, Emulation, StartOffset, &size);
			if (e) return e;
			mdatSize += size;
			StartOffset += size;
		}
		i=0;
		while ((writer = (TrackWriter*)gf_list_enum(writers, &i))) {
			if (writer->mdia->mediaTrack->meta) {
				e = DoWriteMeta(movie, movie->meta, bs, Emulation, StartOffset, &size);
				if (e) return e;
				mdatSize += size;
				StartOffset += size;
			}
		}
	}

	offset = StartOffset;
	predOffset = 0;
	i=0;
	while ((writer = (TrackWriter*)gf_list_enum(writers, &i))) {
		while (!writer->isDone) {
			Bool self_contained;
			u32 nb_samp=1;
			//To Check: are empty sample tables allowed ???
			if (writer->sampleNumber > writer->stbl->SampleSize->sampleCount) {
				writer->isDone = 1;
				continue;
			}
			e = stbl_GetSampleInfos(writer->stbl, writer->sampleNumber, &sampOffset, &chunkNumber, &descIndex, &stsc_ent);
			if (e) return e;
			e = stbl_GetSampleSize(writer->stbl->SampleSize, writer->sampleNumber, &sampSize);
			if (e) return e;

			update_writer_constant_dur(movie, writer, stsc_ent, &nb_samp, &sampSize, GF_TRUE);

			//update our chunks.
			force = 0;
			if (movie->openMode == GF_ISOM_OPEN_WRITE) {
				offset = sampOffset;
				if (predOffset != offset)
					force = 1;
			}

			if (writer->stbl->MaxChunkSize && (writer->chunkSize + sampSize > writer->stbl->MaxChunkSize)) {
				writer->chunkSize = 0;
				force = 1;
			}
			writer->chunkSize += sampSize;

			self_contained = ((writer->all_dref_mode==ISOM_DREF_SELF) || Media_IsSelfContained(writer->mdia, descIndex) ) ? GF_TRUE : GF_FALSE;

			//update our global offset...
			if (self_contained) {
				e = stbl_SetChunkAndOffset(writer->stbl, writer->sampleNumber, descIndex, writer->stsc, &writer->stco, offset, force, nb_samp);
				if (e) return e;
				if (movie->openMode == GF_ISOM_OPEN_WRITE) {
					predOffset = sampOffset + sampSize;
				} else {
					offset += sampSize;
					mdatSize += sampSize;
				}
			} else {
				if (predOffset != offset) force = 1;
				predOffset = sampOffset + sampSize;
				//we have a DataRef, so use the offset idicated in sampleToChunk and ChunkOffset tables...
				e = stbl_SetChunkAndOffset(writer->stbl, writer->sampleNumber, descIndex, writer->stsc, &writer->stco, sampOffset, force, nb_samp);
				if (e) return e;
			}
			//we write the sample if not emulation
			if (!Emulation) {
				if (self_contained) {
					e = WriteSample(mw, sampSize, sampOffset, stsc_ent->isEdited, bs, 1);
					if (e) return e;
				}
			}
			//ok, the track is done
			if (writer->sampleNumber >= writer->stbl->SampleSize->sampleCount) {
				writer->isDone = 1;
			} else {
				writer->sampleNumber += nb_samp;
			}
		}
	}
	//set the mdatSize...
	movie->mdat->dataSize = mdatSize;
	return GF_OK;
}


void update_writer_constant_dur(GF_ISOFile *movie, TrackWriter *tkw, GF_StscEntry *stsc_ent, u32 *nb_samp, u32 *samp_size, Bool is_flat)
{
	u64 chunk_dur;
	u32 nb_in_run;
	u32 samp_idx_in_chunk, nb_samp_left_in_src_chunk;
	if (!tkw->constant_dur) return;

	samp_idx_in_chunk = tkw->sampleNumber - tkw->stbl->SampleToChunk->firstSampleInCurrentChunk;
	nb_samp_left_in_src_chunk = stsc_ent->samplesPerChunk - samp_idx_in_chunk;

	if (nb_samp_left_in_src_chunk<=1) return;

	if (is_flat) {
		nb_in_run = nb_samp_left_in_src_chunk;
	} else {

		chunk_dur = movie->interleavingTime * tkw->timeScale;
		if (movie->moov && movie->moov->mvhd && movie->moov->mvhd->timeScale)
			chunk_dur /= movie->moov->mvhd->timeScale;

		chunk_dur -= tkw->chunkDur;

		if (chunk_dur <= tkw->chunkDur) return;
		chunk_dur -= tkw->constant_dur;

		nb_in_run = (u32) (chunk_dur / tkw->constant_dur);

		if (nb_in_run > nb_samp_left_in_src_chunk) {
			nb_in_run = nb_samp_left_in_src_chunk;
		}
	}
	if (tkw->sampleNumber + nb_in_run >= tkw->stbl->SampleSize->sampleCount) {
		nb_in_run = tkw->stbl->SampleSize->sampleCount - tkw->sampleNumber;
	}

	chunk_dur = nb_in_run * tkw->constant_dur;

	tkw->chunkDur += (u32) chunk_dur - tkw->constant_dur; //because tkw->chunkDur already include duration of first sample of chunk
	tkw->DTSprev += chunk_dur - tkw->constant_dur; //because nb_samp += nb_in_run-1

	*nb_samp = nb_in_run;
	*samp_size = nb_in_run * tkw->constant_size;
}


GF_Err DoInterleave(MovieWriter *mw, GF_List *writers, GF_BitStream *bs, u8 Emulation, u64 StartOffset, Bool drift_inter)
{
	u32 i, tracksDone;
	TrackWriter *tmp, *curWriter;
	GF_Err e;
	u32 descIndex, sampSize, chunkNumber;
	u64 DTS;
	u32 moov_timescale;
	u16 curGroupID;
	Bool forceNewChunk, writeGroup;
	GF_StscEntry *stsc_ent;
	//this is used to emulate the write ...
	u64 offset, sampOffset, size, mdatSize;
	u32 count;
	GF_ISOFile *movie = mw->movie;

	mdatSize = 0;

#ifdef TEST_LARGE_FILES
	if (!Emulation) {
		char *blank;
		u32 count, i;
		i = count = 0;
		blank = gf_malloc(sizeof(char)*1024*1024);
		memset(blank, 0, sizeof(char)*1024*1024);
		count = 4096;
		memset(blank, 0, sizeof(char)*1024*1024);
		while (i<count) {
			u32 res = gf_bs_write_data(bs, blank, 1024*1024);
			if (res != 1024*1024) {
				GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("error writing to disk: only %d bytes written\n", res));
			}
			i++;
			GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("writing blank block: %.02f done - %d/%d \r", (100.0*i)/count , i, count));
		}
		gf_free(blank);
	}
	mdatSize = 4096*1024;
	mdatSize *= 1024;
#endif

	/*write meta content first - WE DON'T support fragmentation of resources in ISOM atm*/
	if (movie->meta) {
		e = DoWriteMeta(movie, movie->meta, bs, Emulation, StartOffset, &size);
		if (e) return e;
		mdatSize += size;
		StartOffset += (u32) size;
	}
	if (movie->moov) {
		if (movie->moov->meta) {
			e = DoWriteMeta(movie, movie->moov->meta, bs, Emulation, StartOffset, &size);
			if (e) return e;
			mdatSize += size;
			StartOffset += (u32) size;
		}
		i=0;
		while ((tmp = (TrackWriter*)gf_list_enum(writers, &i))) {
			if (tmp->mdia->mediaTrack->meta) {
				e = DoWriteMeta(movie, tmp->mdia->mediaTrack->meta, bs, Emulation, StartOffset, &size);
				if (e) return e;
				mdatSize += size;
				StartOffset += (u32) size;
			}
		}
	}

	if (movie->storageMode == GF_ISOM_STORE_TIGHT)
		return DoFullInterleave(mw, writers, bs, Emulation, StartOffset);

	curGroupID = 1;
	//we emulate a write from this offset...
	offset = StartOffset;
	tracksDone = 0;

#ifdef TEST_LARGE_FILES
	offset += mdatSize;
#endif

	moov_timescale = movie->moov && movie->moov->mvhd ? movie->moov->mvhd->timeScale : 1000;

	count = gf_list_count(writers);
	//browse each groups
	while (1) {
		/*the max DTS the chunk of the current writer*/
		u64 chunkLastDTS = 0;
		/*the timescale related to the max DTS*/
		u32 chunkLastScale = 0;

		writeGroup = 1;

		//proceed a group
		while (writeGroup) {
			curWriter = NULL;
			for (i=0 ; i < count; i++) {
				tmp = (TrackWriter*)gf_list_get(writers, i);

				//is it done writing ?
				if (tmp->isDone) continue;

				//is it in our group ??
				if (tmp->stbl->groupID != curGroupID) continue;

				//write till this chunk is full on this track...
				while (1) {
					Bool self_contained;
					u32 nb_samp = 1;
					u32 sample_dur;
					u64 chunk_prev_dur;
					//To Check: are empty sample tables allowed ???
					if (tmp->sampleNumber > tmp->stbl->SampleSize->sampleCount) {
						tmp->isDone = 1;
						tracksDone ++;
						break;
					}

					//OK, get the current sample in this track
					stbl_GetSampleDTS_and_Duration(tmp->stbl->TimeToSample, tmp->sampleNumber, &DTS, &sample_dur);

					//can this sample fit in our chunk ?
					if ( ( (DTS - tmp->DTSprev) + tmp->chunkDur) * moov_timescale > movie->interleavingTime * tmp->timeScale
					        /*drift check: reject sample if outside our check window*/
					        || (drift_inter && chunkLastDTS && ( ((u64)tmp->DTSprev*chunkLastScale) > ((u64)chunkLastDTS*tmp->timeScale)) )
					   ) {
						//in case the sample is longer than InterleaveTime
						if (!tmp->chunkDur) {
							forceNewChunk = 1;
						} else {
							//this one is full. go to next one (exit the loop)
							tmp->chunkDur = 0;
							//forceNewChunk = 0;
							break;
						}
					} else {
						forceNewChunk = tmp->chunkDur ? 0 : 1;
					}
					//OK, we can write this track
					curWriter = tmp;

					//small check for first 2 samples (DTS = 0)
					//only in the old mode can chunkdur be 0 for dts 0
					if (tmp->sampleNumber == 2 && !tmp->chunkDur && gf_sys_old_arch_compat() ) {
						forceNewChunk = 0;
					}

					chunk_prev_dur = tmp->chunkDur;
					//FIXME we do not apply patch in test mode for now since this breaks all our hashes, remove this
					//once we move to filters permanently
					if (!gf_sys_old_arch_compat()) {
						tmp->chunkDur += sample_dur;
					} else {
						//old style, compute based on DTS diff
						tmp->chunkDur += (u32) (DTS - tmp->DTSprev);
					}
					tmp->DTSprev = DTS;

					e = stbl_GetSampleInfos(curWriter->stbl, curWriter->sampleNumber, &sampOffset, &chunkNumber, &descIndex, &stsc_ent);
					if (e)
						return e;
					e = stbl_GetSampleSize(curWriter->stbl->SampleSize, curWriter->sampleNumber, &sampSize);
					if (e)
						return e;

					self_contained = ((curWriter->all_dref_mode==ISOM_DREF_SELF) || Media_IsSelfContained(curWriter->mdia, descIndex)) ? GF_TRUE : GF_FALSE;

					update_writer_constant_dur(movie, curWriter, stsc_ent, &nb_samp, &sampSize, GF_FALSE);

					if (curWriter->stbl->MaxChunkSize && (curWriter->chunkSize + sampSize > curWriter->stbl->MaxChunkSize)) {
						curWriter->chunkSize = 0;
						tmp->chunkDur -= chunk_prev_dur;
						forceNewChunk = 1;
					}
					curWriter->chunkSize += sampSize;

					//do we actually write, or do we emulate ?
					if (Emulation) {
						//update our offsets...
						if (self_contained) {
							e = stbl_SetChunkAndOffset(curWriter->stbl, curWriter->sampleNumber, descIndex, curWriter->stsc, &curWriter->stco, offset, forceNewChunk, nb_samp);
							if (e)
								return e;
							offset += sampSize;
							mdatSize += sampSize;
						} else {
							if (curWriter->prev_offset != sampOffset) forceNewChunk = 1;
							curWriter->prev_offset = sampOffset + sampSize;

							//we have a DataRef, so use the offset idicated in sampleToChunk
							//and ChunkOffset tables...
							e = stbl_SetChunkAndOffset(curWriter->stbl, curWriter->sampleNumber, descIndex, curWriter->stsc, &curWriter->stco, sampOffset, forceNewChunk, nb_samp);
							if (e) return e;
						}
					} else {
						//we're writing ....
						if (self_contained) {
							e = WriteSample(mw, sampSize, sampOffset, stsc_ent->isEdited, bs, nb_samp);
							if (e)
								return e;
						}
					}
					//ok, the sample is done
					if (curWriter->sampleNumber >= curWriter->stbl->SampleSize->sampleCount) {
						curWriter->isDone = 1;
						//one more track done...
						tracksDone ++;
						break;
					} else {
						curWriter->sampleNumber += nb_samp;
					}
				}
				/*record chunk end-time & track timescale for drift-controled interleaving*/
				if (drift_inter && curWriter) {
					chunkLastScale = curWriter->timeScale;
					chunkLastDTS = curWriter->DTSprev;
					/*add one interleave window drift - since the "maxDTS" is the previously written one, we will
					have the following cases:
					- sample doesn't fit: post-pone and force new chunk
					- sample time larger than previous chunk time + interleave: post-pone and force new chunk
					- otherwise store and track becomes current reference

					this ensures a proper drift regulation (max DTS diff is less than the interleaving window)
					*/
					chunkLastDTS += curWriter->timeScale * movie->interleavingTime / moov_timescale;
				}
			}
			//no sample found, we're done with this group
			if (!curWriter) {
				writeGroup = 0;
				continue;
			}
		}
		//if all our track are done, break
		if (tracksDone == gf_list_count(writers)) break;
		//go to next group
		curGroupID ++;
	}
	if (movie->mdat) movie->mdat->dataSize = mdatSize;
	return GF_OK;
}


static GF_Err ShiftOffset(GF_ISOFile *file, GF_List *writers, u64 offset)
{
	u32 i, j, k, l, last;
	TrackWriter *writer;
	GF_StscEntry *ent;

	if (file->meta) ShiftMetaOffset(file->meta, offset);
	if (file->moov && file->moov->meta) ShiftMetaOffset(file->moov->meta, offset);

	i=0;
	while ((writer = (TrackWriter *)gf_list_enum(writers, &i))) {
		if (writer->mdia->mediaTrack->meta) ShiftMetaOffset(writer->mdia->mediaTrack->meta, offset);

		//we have to proceed entry by entry in case a part of the media is not self-contained...
		for (j=0; j<writer->stsc->nb_entries; j++) {
			ent = &writer->stsc->entries[j];
			if ((writer->all_dref_mode==ISOM_DREF_EXT) || !Media_IsSelfContained(writer->mdia, ent->sampleDescriptionIndex))
				continue;

			//OK, get the chunk(s) number(s) and "shift" its (their) offset(s).
			if (writer->stco->type == GF_ISOM_BOX_TYPE_STCO) {
				GF_ChunkLargeOffsetBox *new_stco64 = NULL;
				GF_ChunkOffsetBox *stco = (GF_ChunkOffsetBox *) writer->stco;

				//be carefull for the last entry, nextChunk is set to 0 in edit mode...
				last = ent->nextChunk ? ent->nextChunk : stco->nb_entries + 1;
				for (k = ent->firstChunk; k < last; k++) {

					//we need to rewrite the table: only allocate co64 if not done previously and convert all offsets
					//to co64. Then (whether co64 was created or not) adjust the offset
					//Do not reassign table until we are done with the current sampleToChunk processing
					//since we have a test on stco->offsets[k-1], we need to keep stco untouched
					if (new_stco64 || file->force_co64 || (stco->offsets[k-1] + offset > 0xFFFFFFFF)) {
						if (!new_stco64) {
							new_stco64 = (GF_ChunkLargeOffsetBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_CO64);
							if (!new_stco64) return GF_OUT_OF_MEM;
							new_stco64->nb_entries = stco->nb_entries;
							new_stco64->offsets = (u64 *) gf_malloc(new_stco64->nb_entries * sizeof(u64));
							if (!new_stco64->offsets) return GF_OUT_OF_MEM;
							//copy over the stco table
							for (l = 0; l < new_stco64->nb_entries; l++) {
								new_stco64->offsets[l] = (u64) stco->offsets[l];
							}
						}
						new_stco64->offsets[k-1] += offset;
					} else {
						stco->offsets[k-1] += (u32) offset;
					}
				}
				if (new_stco64) {
					//done with this sampleToChunk entry, replace the box if we moved to co64
					gf_isom_box_del(writer->stco);
					writer->stco = (GF_Box *)new_stco64;
					new_stco64 = NULL;
				}
			} else {
				GF_ChunkLargeOffsetBox *stco64 = (GF_ChunkLargeOffsetBox *) writer->stco;
				//be carefull for the last entry ...
				last = ent->nextChunk ? ent->nextChunk : stco64->nb_entries + 1;
				for (k = ent->firstChunk; k < last; k++) {
					stco64->offsets[k-1] += offset;
				}
			}
		}
	}

	return GF_OK;

}


static GF_Err WriteMoovAndMeta(GF_ISOFile *movie, GF_List *writers, GF_BitStream *bs)
{
	u32 i;
	TrackWriter *writer;
	GF_Err e;
	GF_Box *stco;
	GF_SampleToChunkBox *stsc;

	if (movie->meta) {
		//write the moov box...
		e = gf_isom_box_size((GF_Box *)movie->meta);
		if (e) return e;
		e = gf_isom_box_write((GF_Box *)movie->meta, bs);
		if (e) return e;
	}

	if (movie->moov) {
		Bool prevent_dispatch = GF_FALSE;
		//switch all our tables
		i=0;
		while ((writer = (TrackWriter*)gf_list_enum(writers, &i))) {
			//don't delete them !!!
			stsc = writer->stbl->SampleToChunk;
			stco = writer->stbl->ChunkOffset;
			s32 stsc_pos = gf_list_del_item(writer->stbl->child_boxes, stsc);
			s32 stco_pos = gf_list_del_item(writer->stbl->child_boxes, stco);
			writer->stbl->SampleToChunk = writer->stsc;
			writer->stbl->ChunkOffset = writer->stco;
			gf_list_insert(writer->stbl->child_boxes, writer->stsc, stsc_pos);
			gf_list_insert(writer->stbl->child_boxes, writer->stco, stco_pos);
			writer->stco = stco;
			writer->stsc = stsc;
			if (writer->prevent_dispatch)
				prevent_dispatch = GF_TRUE;
		}
		if (prevent_dispatch) {
			gf_bs_prevent_dispatch(bs, GF_TRUE);
		}
		//write the moov box...
		e = gf_isom_box_size((GF_Box *)movie->moov);
		if (e) return e;

		if ((movie->compress_mode==GF_ISO_COMP_ALL) || (movie->compress_mode==GF_ISO_COMP_MOOV)) {
			e = gf_isom_write_compressed_box(movie, (GF_Box *) movie->moov, GF_4CC('!', 'm', 'o', 'v'), bs, NULL);
		} else {
			e = gf_isom_box_write((GF_Box *)movie->moov, bs);
		}

		if (prevent_dispatch) {
			gf_bs_prevent_dispatch(bs, GF_FALSE);
		}

		//and re-switch our table. We have to do it that way because it is
		//needed when the moov is written first
		i=0;
		while ((writer = (TrackWriter*)gf_list_enum(writers, &i))) {
			//don't delete them !!!
			stsc = writer->stsc;
			stco = writer->stco;
			writer->stsc = writer->stbl->SampleToChunk;
			writer->stco = writer->stbl->ChunkOffset;
			s32 stsc_pos = gf_list_del_item(writer->stbl->child_boxes, writer->stsc);
			s32 stco_pos = gf_list_del_item(writer->stbl->child_boxes, writer->stco);

			writer->stbl->SampleToChunk = stsc;
			writer->stbl->ChunkOffset = stco;
			gf_list_insert(writer->stbl->child_boxes, stsc, stsc_pos);
			gf_list_insert(writer->stbl->child_boxes, stco, stco_pos);
		}
		if (e) return e;
	}
	return GF_OK;
}