mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-09 13:04:42 +00:00

When no payload is set for `crlf_apply` we try to compute the crlf attributes ourselves with `crlf_check`. When the function determines that the current file does not require any treatment we return the GIT_PASSTHROUGH error code without actually allocating the out-pointer, which indicates the file should not be passed through the filter. The `crlf_apply` function explicitly checks for the GIT_PASSTHROUGH return code and ignores it. This means we will try to apply the crlf-filter to the current file, leading us to dereference the unallocated payload-pointer. Fix this obviously incorrect behavior by not treating GIT_PASSTHROUGH in any special way. This is the correct thing to do anyway, as the code indicates that the file should not be passed through the filter.
383 lines
8.7 KiB
C
383 lines
8.7 KiB
C
/*
|
|
* 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 "git2/sys/filter.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;
|
|
int auto_crlf;
|
|
int safe_crlf;
|
|
};
|
|
|
|
struct crlf_filter {
|
|
git_filter f;
|
|
};
|
|
|
|
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 has_cr_in_index(const git_filter_source *src)
|
|
{
|
|
git_repository *repo = git_filter_source_repo(src);
|
|
const char *path = git_filter_source_path(src);
|
|
git_index *index;
|
|
const git_index_entry *entry;
|
|
git_blob *blob;
|
|
const void *blobcontent;
|
|
git_off_t blobsize;
|
|
bool found_cr;
|
|
|
|
if (!path)
|
|
return false;
|
|
|
|
if (git_repository_index__weakptr(&index, repo) < 0) {
|
|
giterr_clear();
|
|
return false;
|
|
}
|
|
|
|
if (!(entry = git_index_get_bypath(index, path, 0)) &&
|
|
!(entry = git_index_get_bypath(index, path, 1)))
|
|
return false;
|
|
|
|
if (!S_ISREG(entry->mode)) /* don't crlf filter non-blobs */
|
|
return true;
|
|
|
|
if (git_blob_lookup(&blob, repo, &entry->id) < 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(
|
|
struct crlf_attrs *ca,
|
|
git_buf *to,
|
|
const git_buf *from,
|
|
const git_filter_source *src)
|
|
{
|
|
/* Empty file? Nothing to do */
|
|
if (!git_buf_len(from))
|
|
return 0;
|
|
|
|
/* Heuristics to see if we can skip the conversion.
|
|
* Straight from Core Git.
|
|
*/
|
|
if (ca->crlf_action == GIT_CRLF_AUTO || ca->crlf_action == GIT_CRLF_GUESS) {
|
|
git_buf_text_stats stats;
|
|
|
|
/* Check heuristics for binary vs text - returns true if binary */
|
|
if (git_buf_text_gather_stats(&stats, from, false))
|
|
return GIT_PASSTHROUGH;
|
|
|
|
/* If there are no CR characters to filter out, then just pass */
|
|
if (!stats.cr)
|
|
return GIT_PASSTHROUGH;
|
|
|
|
/* If safecrlf is enabled, sanity-check the result. */
|
|
if (stats.cr != stats.crlf || stats.lf != stats.crlf) {
|
|
switch (ca->safe_crlf) {
|
|
case GIT_SAFE_CRLF_FAIL:
|
|
giterr_set(
|
|
GITERR_FILTER, "LF would be replaced by CRLF in '%s'",
|
|
git_filter_source_path(src));
|
|
return -1;
|
|
case GIT_SAFE_CRLF_WARN:
|
|
/* TODO: issue warning when warning API is available */;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 GIT_PASSTHROUGH;
|
|
|
|
if (ca->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(src))
|
|
return GIT_PASSTHROUGH;
|
|
}
|
|
|
|
if (!stats.cr)
|
|
return GIT_PASSTHROUGH;
|
|
}
|
|
|
|
/* Actually drop the carriage returns */
|
|
return git_buf_text_crlf_to_lf(to, from);
|
|
}
|
|
|
|
static const char *line_ending(struct crlf_attrs *ca)
|
|
{
|
|
switch (ca->crlf_action) {
|
|
case GIT_CRLF_BINARY:
|
|
case GIT_CRLF_INPUT:
|
|
return "\n";
|
|
|
|
case GIT_CRLF_CRLF:
|
|
return "\r\n";
|
|
|
|
case GIT_CRLF_GUESS:
|
|
if (ca->auto_crlf == GIT_AUTO_CRLF_FALSE)
|
|
return "\n";
|
|
break;
|
|
|
|
case GIT_CRLF_AUTO:
|
|
case GIT_CRLF_TEXT:
|
|
break;
|
|
|
|
default:
|
|
goto line_ending_error;
|
|
}
|
|
|
|
if (ca->auto_crlf == GIT_AUTO_CRLF_TRUE)
|
|
return "\r\n";
|
|
else if (ca->auto_crlf == GIT_AUTO_CRLF_INPUT)
|
|
return "\n";
|
|
else if (ca->eol == GIT_EOL_UNSET)
|
|
return GIT_EOL_NATIVE == GIT_EOL_CRLF ? "\r\n" : "\n";
|
|
else if (ca->eol == GIT_EOL_LF)
|
|
return "\n";
|
|
else if (ca->eol == GIT_EOL_CRLF)
|
|
return "\r\n";
|
|
|
|
line_ending_error:
|
|
giterr_set(GITERR_INVALID, "Invalid input to line ending filter");
|
|
return NULL;
|
|
}
|
|
|
|
static int crlf_apply_to_workdir(
|
|
struct crlf_attrs *ca, git_buf *to, const git_buf *from)
|
|
{
|
|
git_buf_text_stats stats;
|
|
const char *workdir_ending = NULL;
|
|
bool is_binary;
|
|
|
|
/* Empty file? Nothing to do. */
|
|
if (git_buf_len(from) == 0)
|
|
return 0;
|
|
|
|
/* Determine proper line ending */
|
|
workdir_ending = line_ending(ca);
|
|
if (!workdir_ending)
|
|
return -1;
|
|
|
|
/* only LF->CRLF conversion is supported, do nothing on LF platforms */
|
|
if (strcmp(workdir_ending, "\r\n") != 0)
|
|
return GIT_PASSTHROUGH;
|
|
|
|
/* If there are no LFs, or all LFs are part of a CRLF, nothing to do */
|
|
is_binary = git_buf_text_gather_stats(&stats, from, false);
|
|
|
|
if (stats.lf == 0 || stats.lf == stats.crlf)
|
|
return GIT_PASSTHROUGH;
|
|
|
|
if (ca->crlf_action == GIT_CRLF_AUTO ||
|
|
ca->crlf_action == GIT_CRLF_GUESS) {
|
|
|
|
/* If we have any existing CR or CRLF line endings, do nothing */
|
|
if (ca->crlf_action == GIT_CRLF_GUESS &&
|
|
stats.cr > 0 && stats.crlf > 0)
|
|
return GIT_PASSTHROUGH;
|
|
|
|
/* If we have bare CR characters, do nothing */
|
|
if (stats.cr != stats.crlf)
|
|
return GIT_PASSTHROUGH;
|
|
|
|
/* Don't filter binary files */
|
|
if (is_binary)
|
|
return GIT_PASSTHROUGH;
|
|
}
|
|
|
|
return git_buf_text_lf_to_crlf(to, from);
|
|
}
|
|
|
|
static int crlf_check(
|
|
git_filter *self,
|
|
void **payload, /* points to NULL ptr on entry, may be set */
|
|
const git_filter_source *src,
|
|
const char **attr_values)
|
|
{
|
|
int error;
|
|
struct crlf_attrs ca;
|
|
|
|
GIT_UNUSED(self);
|
|
|
|
if (!attr_values) {
|
|
ca.crlf_action = GIT_CRLF_GUESS;
|
|
ca.eol = GIT_EOL_UNSET;
|
|
} else {
|
|
ca.crlf_action = check_crlf(attr_values[2]); /* text */
|
|
if (ca.crlf_action == GIT_CRLF_GUESS)
|
|
ca.crlf_action = check_crlf(attr_values[0]); /* clrf */
|
|
ca.eol = check_eol(attr_values[1]); /* eol */
|
|
}
|
|
ca.auto_crlf = GIT_AUTO_CRLF_DEFAULT;
|
|
|
|
/*
|
|
* 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 GIT_PASSTHROUGH;
|
|
|
|
if (ca.crlf_action == GIT_CRLF_GUESS ||
|
|
((ca.crlf_action == GIT_CRLF_AUTO || ca.crlf_action == GIT_CRLF_TEXT) &&
|
|
git_filter_source_mode(src) == GIT_FILTER_SMUDGE)) {
|
|
|
|
error = git_repository__cvar(
|
|
&ca.auto_crlf, git_filter_source_repo(src), GIT_CVAR_AUTO_CRLF);
|
|
if (error < 0)
|
|
return error;
|
|
|
|
if (ca.crlf_action == GIT_CRLF_GUESS &&
|
|
ca.auto_crlf == GIT_AUTO_CRLF_FALSE)
|
|
return GIT_PASSTHROUGH;
|
|
|
|
if (ca.auto_crlf == GIT_AUTO_CRLF_INPUT &&
|
|
git_filter_source_mode(src) == GIT_FILTER_SMUDGE)
|
|
return GIT_PASSTHROUGH;
|
|
}
|
|
|
|
if (git_filter_source_mode(src) == GIT_FILTER_CLEAN) {
|
|
error = git_repository__cvar(
|
|
&ca.safe_crlf, git_filter_source_repo(src), GIT_CVAR_SAFE_CRLF);
|
|
if (error < 0)
|
|
return error;
|
|
|
|
/* downgrade FAIL to WARN if ALLOW_UNSAFE option is used */
|
|
if ((git_filter_source_flags(src) & GIT_FILTER_ALLOW_UNSAFE) &&
|
|
ca.safe_crlf == GIT_SAFE_CRLF_FAIL)
|
|
ca.safe_crlf = GIT_SAFE_CRLF_WARN;
|
|
}
|
|
|
|
*payload = git__malloc(sizeof(ca));
|
|
GITERR_CHECK_ALLOC(*payload);
|
|
memcpy(*payload, &ca, sizeof(ca));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int crlf_apply(
|
|
git_filter *self,
|
|
void **payload, /* may be read and/or set */
|
|
git_buf *to,
|
|
const git_buf *from,
|
|
const git_filter_source *src)
|
|
{
|
|
/* initialize payload in case `check` was bypassed */
|
|
if (!*payload) {
|
|
int error = crlf_check(self, payload, src, NULL);
|
|
if (error < 0)
|
|
return error;
|
|
}
|
|
|
|
if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE)
|
|
return crlf_apply_to_workdir(*payload, to, from);
|
|
else
|
|
return crlf_apply_to_odb(*payload, to, from, src);
|
|
}
|
|
|
|
static void crlf_cleanup(
|
|
git_filter *self,
|
|
void *payload)
|
|
{
|
|
GIT_UNUSED(self);
|
|
git__free(payload);
|
|
}
|
|
|
|
git_filter *git_crlf_filter_new(void)
|
|
{
|
|
struct crlf_filter *f = git__calloc(1, sizeof(struct crlf_filter));
|
|
if (f == NULL)
|
|
return NULL;
|
|
|
|
f->f.version = GIT_FILTER_VERSION;
|
|
f->f.attributes = "crlf eol text";
|
|
f->f.initialize = NULL;
|
|
f->f.shutdown = git_filter_free;
|
|
f->f.check = crlf_check;
|
|
f->f.apply = crlf_apply;
|
|
f->f.cleanup = crlf_cleanup;
|
|
|
|
return (git_filter *)f;
|
|
}
|