| /* | 
 |  * Copyright (C) the libgit2 contributors. All rights reserved. | 
 |  * | 
 |  * This file is part of libgit2, distributed under the GNU GPL v2 with | 
 |  * a Linking Exception. For full terms see the included COPYING file. | 
 |  */ | 
 |  | 
 | #include "git2/attr.h" | 
 | #include "git2/blob.h" | 
 | #include "git2/index.h" | 
 |  | 
 | #include "common.h" | 
 | #include "fileops.h" | 
 | #include "hash.h" | 
 | #include "filter.h" | 
 | #include "buf_text.h" | 
 | #include "repository.h" | 
 |  | 
 | struct crlf_attrs { | 
 | 	int crlf_action; | 
 | 	int eol; | 
 | }; | 
 |  | 
 | struct crlf_filter { | 
 | 	git_filter f; | 
 | 	struct crlf_attrs attrs; | 
 | 	git_repository *repo; | 
 | 	char path[GIT_FLEX_ARRAY]; | 
 | }; | 
 |  | 
 | static int check_crlf(const char *value) | 
 | { | 
 | 	if (GIT_ATTR_TRUE(value)) | 
 | 		return GIT_CRLF_TEXT; | 
 |  | 
 | 	if (GIT_ATTR_FALSE(value)) | 
 | 		return GIT_CRLF_BINARY; | 
 |  | 
 | 	if (GIT_ATTR_UNSPECIFIED(value)) | 
 | 		return GIT_CRLF_GUESS; | 
 |  | 
 | 	if (strcmp(value, "input") == 0) | 
 | 		return GIT_CRLF_INPUT; | 
 |  | 
 | 	if (strcmp(value, "auto") == 0) | 
 | 		return GIT_CRLF_AUTO; | 
 |  | 
 | 	return GIT_CRLF_GUESS; | 
 | } | 
 |  | 
 | static int check_eol(const char *value) | 
 | { | 
 | 	if (GIT_ATTR_UNSPECIFIED(value)) | 
 | 		return GIT_EOL_UNSET; | 
 |  | 
 | 	if (strcmp(value, "lf") == 0) | 
 | 		return GIT_EOL_LF; | 
 |  | 
 | 	if (strcmp(value, "crlf") == 0) | 
 | 		return GIT_EOL_CRLF; | 
 |  | 
 | 	return GIT_EOL_UNSET; | 
 | } | 
 |  | 
 | static int crlf_input_action(struct crlf_attrs *ca) | 
 | { | 
 | 	if (ca->crlf_action == GIT_CRLF_BINARY) | 
 | 		return GIT_CRLF_BINARY; | 
 |  | 
 | 	if (ca->eol == GIT_EOL_LF) | 
 | 		return GIT_CRLF_INPUT; | 
 |  | 
 | 	if (ca->eol == GIT_EOL_CRLF) | 
 | 		return GIT_CRLF_CRLF; | 
 |  | 
 | 	return ca->crlf_action; | 
 | } | 
 |  | 
 | static int crlf_load_attributes(struct crlf_attrs *ca, git_repository *repo, const char *path) | 
 | { | 
 | #define NUM_CONV_ATTRS 3 | 
 |  | 
 | 	static const char *attr_names[NUM_CONV_ATTRS] = { | 
 | 		"crlf", "eol", "text", | 
 | 	}; | 
 |  | 
 | 	const char *attr_vals[NUM_CONV_ATTRS]; | 
 | 	int error; | 
 |  | 
 | 	error = git_attr_get_many(attr_vals, | 
 | 		repo, 0, path, NUM_CONV_ATTRS, attr_names); | 
 |  | 
 | 	if (error == GIT_ENOTFOUND) { | 
 | 		ca->crlf_action = GIT_CRLF_GUESS; | 
 | 		ca->eol = GIT_EOL_UNSET; | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	if (error == 0) { | 
 | 		ca->crlf_action = check_crlf(attr_vals[2]); /* text */ | 
 | 		if (ca->crlf_action == GIT_CRLF_GUESS) | 
 | 			ca->crlf_action = check_crlf(attr_vals[0]); /* clrf */ | 
 |  | 
 | 		ca->eol = check_eol(attr_vals[1]); /* eol */ | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	return -1; | 
 | } | 
 |  | 
 | static int has_cr_in_index(git_filter *self) | 
 | { | 
 | 	struct crlf_filter *filter = (struct crlf_filter *)self; | 
 | 	git_index *index; | 
 | 	const git_index_entry *entry; | 
 | 	git_blob *blob; | 
 | 	const void *blobcontent; | 
 | 	git_off_t blobsize; | 
 | 	bool found_cr; | 
 |  | 
 | 	if (git_repository_index__weakptr(&index, filter->repo) < 0) { | 
 | 		giterr_clear(); | 
 | 		return false; | 
 | 	} | 
 |  | 
 | 	if (!(entry = git_index_get_bypath(index, filter->path, 0)) && | 
 | 		!(entry = git_index_get_bypath(index, filter->path, 1))) | 
 | 		return false; | 
 |  | 
 | 	if (!S_ISREG(entry->mode)) /* don't crlf filter non-blobs */ | 
 | 		return true; | 
 |  | 
 | 	if (git_blob_lookup(&blob, filter->repo, &entry->oid) < 0) | 
 | 		return false; | 
 |  | 
 | 	blobcontent = git_blob_rawcontent(blob); | 
 | 	blobsize    = git_blob_rawsize(blob); | 
 | 	if (!git__is_sizet(blobsize)) | 
 | 		blobsize = (size_t)-1; | 
 |  | 
 | 	found_cr = (blobcontent != NULL && | 
 | 		blobsize > 0 && | 
 | 		memchr(blobcontent, '\r', (size_t)blobsize) != NULL); | 
 |  | 
 | 	git_blob_free(blob); | 
 | 	return found_cr; | 
 | } | 
 |  | 
 | static int crlf_apply_to_odb( | 
 | 	git_filter *self, git_buf *dest, const git_buf *source) | 
 | { | 
 | 	struct crlf_filter *filter = (struct crlf_filter *)self; | 
 |  | 
 | 	assert(self && dest && source); | 
 |  | 
 | 	/* Empty file? Nothing to do */ | 
 | 	if (git_buf_len(source) == 0) | 
 | 		return 0; | 
 |  | 
 | 	/* Heuristics to see if we can skip the conversion. | 
 | 	 * Straight from Core Git. | 
 | 	 */ | 
 | 	if (filter->attrs.crlf_action == GIT_CRLF_AUTO || | 
 | 		filter->attrs.crlf_action == GIT_CRLF_GUESS) { | 
 |  | 
 | 		git_buf_text_stats stats; | 
 |  | 
 | 		/* Check heuristics for binary vs text... */ | 
 | 		if (git_buf_text_gather_stats(&stats, source, false)) | 
 | 			return -1; | 
 |  | 
 | 		/* | 
 | 		 * We're currently not going to even try to convert stuff | 
 | 		 * that has bare CR characters. Does anybody do that crazy | 
 | 		 * stuff? | 
 | 		 */ | 
 | 		if (stats.cr != stats.crlf) | 
 | 			return -1; | 
 |  | 
 | 		if (filter->attrs.crlf_action == GIT_CRLF_GUESS) { | 
 | 			/* | 
 | 			 * If the file in the index has any CR in it, do not convert. | 
 | 			 * This is the new safer autocrlf handling. | 
 | 			 */ | 
 | 			if (has_cr_in_index(self)) | 
 | 				return -1; | 
 | 		} | 
 |  | 
 | 		if (!stats.cr) | 
 | 			return -1; | 
 | 	} | 
 |  | 
 | 	/* Actually drop the carriage returns */ | 
 | 	return git_buf_text_crlf_to_lf(dest, source); | 
 | } | 
 |  | 
 | static const char *line_ending(struct crlf_filter *filter) | 
 | { | 
 | 	switch (filter->attrs.crlf_action) { | 
 | 	case GIT_CRLF_BINARY: | 
 | 	case GIT_CRLF_INPUT: | 
 | 		return "\n"; | 
 |  | 
 | 	case GIT_CRLF_CRLF: | 
 | 		return "\r\n"; | 
 |  | 
 | 	case GIT_CRLF_AUTO: | 
 | 	case GIT_CRLF_TEXT: | 
 | 	case GIT_CRLF_GUESS: | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		goto line_ending_error; | 
 | 	} | 
 |  | 
 | 	switch (filter->attrs.eol) { | 
 | 	case GIT_EOL_UNSET: | 
 | 		return GIT_EOL_NATIVE == GIT_EOL_CRLF | 
 | 			? "\r\n" | 
 | 			: "\n"; | 
 |  | 
 | 	case GIT_EOL_CRLF: | 
 | 		return "\r\n"; | 
 |  | 
 | 	case GIT_EOL_LF: | 
 | 		return "\n"; | 
 |  | 
 | 	default: | 
 | 		goto line_ending_error; | 
 | 	} | 
 |  | 
 | line_ending_error: | 
 | 	giterr_set(GITERR_INVALID, "Invalid input to line ending filter"); | 
 | 	return NULL; | 
 | } | 
 |  | 
 | static int crlf_apply_to_workdir( | 
 | 	git_filter *self, git_buf *dest, const git_buf *source) | 
 | { | 
 | 	struct crlf_filter *filter = (struct crlf_filter *)self; | 
 | 	const char *workdir_ending = NULL; | 
 |  | 
 | 	assert(self && dest && source); | 
 |  | 
 | 	/* Empty file? Nothing to do. */ | 
 | 	if (git_buf_len(source) == 0) | 
 | 		return -1; | 
 |  | 
 | 	/* Determine proper line ending */ | 
 | 	workdir_ending = line_ending(filter); | 
 | 	if (!workdir_ending) | 
 | 		return -1; | 
 | 	if (!strcmp("\n", workdir_ending)) /* do nothing for \n ending */ | 
 | 		return -1; | 
 |  | 
 | 	/* for now, only lf->crlf conversion is supported here */ | 
 | 	assert(!strcmp("\r\n", workdir_ending)); | 
 | 	return git_buf_text_lf_to_crlf(dest, source); | 
 | } | 
 |  | 
 | static int find_and_add_filter( | 
 | 	git_vector *filters, git_repository *repo, const char *path, | 
 | 	int (*apply)(struct git_filter *self, git_buf *dest, const git_buf *source)) | 
 | { | 
 | 	struct crlf_attrs ca; | 
 | 	struct crlf_filter *filter; | 
 | 	size_t pathlen; | 
 | 	int error; | 
 |  | 
 | 	/* Load gitattributes for the path */ | 
 | 	if ((error = crlf_load_attributes(&ca, repo, path)) < 0) | 
 | 		return error; | 
 |  | 
 | 	/* | 
 | 	 * Use the core Git logic to see if we should perform CRLF for this file | 
 | 	 * based on its attributes & the value of `core.autocrlf` | 
 | 	 */ | 
 | 	ca.crlf_action = crlf_input_action(&ca); | 
 |  | 
 | 	if (ca.crlf_action == GIT_CRLF_BINARY) | 
 | 		return 0; | 
 |  | 
 | 	if (ca.crlf_action == GIT_CRLF_GUESS) { | 
 | 		int auto_crlf; | 
 |  | 
 | 		if ((error = git_repository__cvar(&auto_crlf, repo, GIT_CVAR_AUTO_CRLF)) < 0) | 
 | 			return error; | 
 |  | 
 | 		if (auto_crlf == GIT_AUTO_CRLF_FALSE) | 
 | 			return 0; | 
 | 	} | 
 |  | 
 | 	/* If we're good, we create a new filter object and push it | 
 | 	 * into the filters array */ | 
 | 	pathlen = strlen(path); | 
 | 	filter = git__malloc(sizeof(struct crlf_filter) + pathlen + 1); | 
 | 	GITERR_CHECK_ALLOC(filter); | 
 |  | 
 | 	filter->f.apply = apply; | 
 | 	filter->f.do_free = NULL; | 
 | 	memcpy(&filter->attrs, &ca, sizeof(struct crlf_attrs)); | 
 | 	filter->repo = repo; | 
 | 	memcpy(filter->path, path, pathlen + 1); | 
 |  | 
 | 	return git_vector_insert(filters, filter); | 
 | } | 
 |  | 
 | int git_filter_add__crlf_to_odb( | 
 | 	git_vector *filters, git_repository *repo, const char *path) | 
 | { | 
 | 	return find_and_add_filter(filters, repo, path, &crlf_apply_to_odb); | 
 | } | 
 |  | 
 | int git_filter_add__crlf_to_workdir( | 
 | 	git_vector *filters, git_repository *repo, const char *path) | 
 | { | 
 | 	return find_and_add_filter(filters, repo, path, &crlf_apply_to_workdir); | 
 | } |