1 /*
2  * Copyright (C) the libgit2 contributors. All rights reserved.
3  *
4  * This file is part of libgit2, distributed under the GNU GPL v2 with
5  * a Linking Exception. For full terms see the included COPYING file.
6  */
7 
8 #include "filter.h"
9 
10 #include "common.h"
11 #include "fileops.h"
12 #include "hash.h"
13 #include "repository.h"
14 #include "global.h"
15 #include "git2/sys/filter.h"
16 #include "git2/config.h"
17 #include "blob.h"
18 #include "attr_file.h"
19 #include "array.h"
20 
21 struct git_filter_source {
22 	git_repository *repo;
23 	const char     *path;
24 	git_oid         oid;  /* zero if unknown (which is likely) */
25 	uint16_t        filemode; /* zero if unknown */
26 	git_filter_mode_t mode;
27 	uint32_t        flags;
28 };
29 
30 typedef struct {
31 	const char *filter_name;
32 	git_filter *filter;
33 	void *payload;
34 } git_filter_entry;
35 
36 struct git_filter_list {
37 	git_array_t(git_filter_entry) filters;
38 	git_filter_source source;
39 	git_buf *temp_buf;
40 	char path[GIT_FLEX_ARRAY];
41 };
42 
43 typedef struct {
44 	char *filter_name;
45 	git_filter *filter;
46 	int priority;
47 	int initialized;
48 	size_t nattrs, nmatches;
49 	char *attrdata;
50 	const char *attrs[GIT_FLEX_ARRAY];
51 } git_filter_def;
52 
filter_def_priority_cmp(const void * a,const void * b)53 static int filter_def_priority_cmp(const void *a, const void *b)
54 {
55 	int pa = ((const git_filter_def *)a)->priority;
56 	int pb = ((const git_filter_def *)b)->priority;
57 	return (pa < pb) ? -1 : (pa > pb) ? 1 : 0;
58 }
59 
60 struct git_filter_registry {
61 	git_rwlock lock;
62 	git_vector filters;
63 };
64 
65 static struct git_filter_registry filter_registry;
66 
67 static void git_filter_global_shutdown(void);
68 
69 
filter_def_scan_attrs(git_buf * attrs,size_t * nattr,size_t * nmatch,const char * attr_str)70 static int filter_def_scan_attrs(
71 	git_buf *attrs, size_t *nattr, size_t *nmatch, const char *attr_str)
72 {
73 	const char *start, *scan = attr_str;
74 	int has_eq;
75 
76 	*nattr = *nmatch = 0;
77 
78 	if (!scan)
79 		return 0;
80 
81 	while (*scan) {
82 		while (git__isspace(*scan)) scan++;
83 
84 		for (start = scan, has_eq = 0; *scan && !git__isspace(*scan); ++scan) {
85 			if (*scan == '=')
86 				has_eq = 1;
87 		}
88 
89 		if (scan > start) {
90 			(*nattr)++;
91 			if (has_eq || *start == '-' || *start == '+' || *start == '!')
92 				(*nmatch)++;
93 
94 			if (has_eq)
95 				git_buf_putc(attrs, '=');
96 			git_buf_put(attrs, start, scan - start);
97 			git_buf_putc(attrs, '\0');
98 		}
99 	}
100 
101 	return 0;
102 }
103 
filter_def_set_attrs(git_filter_def * fdef)104 static void filter_def_set_attrs(git_filter_def *fdef)
105 {
106 	char *scan = fdef->attrdata;
107 	size_t i;
108 
109 	for (i = 0; i < fdef->nattrs; ++i) {
110 		const char *name, *value;
111 
112 		switch (*scan) {
113 		case '=':
114 			name = scan + 1;
115 			for (scan++; *scan != '='; scan++) /* find '=' */;
116 			*scan++ = '\0';
117 			value = scan;
118 			break;
119 		case '-':
120 			name = scan + 1; value = git_attr__false; break;
121 		case '+':
122 			name = scan + 1; value = git_attr__true;  break;
123 		case '!':
124 			name = scan + 1; value = git_attr__unset; break;
125 		default:
126 			name = scan;     value = NULL; break;
127 		}
128 
129 		fdef->attrs[i] = name;
130 		fdef->attrs[i + fdef->nattrs] = value;
131 
132 		scan += strlen(scan) + 1;
133 	}
134 }
135 
filter_def_name_key_check(const void * key,const void * fdef)136 static int filter_def_name_key_check(const void *key, const void *fdef)
137 {
138 	const char *name =
139 		fdef ? ((const git_filter_def *)fdef)->filter_name : NULL;
140 	return name ? git__strcmp(key, name) : -1;
141 }
142 
filter_def_filter_key_check(const void * key,const void * fdef)143 static int filter_def_filter_key_check(const void *key, const void *fdef)
144 {
145 	const void *filter = fdef ? ((const git_filter_def *)fdef)->filter : NULL;
146 	return (key == filter) ? 0 : -1;
147 }
148 
149 /* Note: callers must lock the registry before calling this function */
filter_registry_insert(const char * name,git_filter * filter,int priority)150 static int filter_registry_insert(
151 	const char *name, git_filter *filter, int priority)
152 {
153 	git_filter_def *fdef;
154 	size_t nattr = 0, nmatch = 0, alloc_len;
155 	git_buf attrs = GIT_BUF_INIT;
156 
157 	if (filter_def_scan_attrs(&attrs, &nattr, &nmatch, filter->attributes) < 0)
158 		return -1;
159 
160 	GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_len, nattr, 2);
161 	GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_len, alloc_len, sizeof(char *));
162 	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, sizeof(git_filter_def));
163 
164 	fdef = git__calloc(1, alloc_len);
165 	GIT_ERROR_CHECK_ALLOC(fdef);
166 
167 	fdef->filter_name = git__strdup(name);
168 	GIT_ERROR_CHECK_ALLOC(fdef->filter_name);
169 
170 	fdef->filter      = filter;
171 	fdef->priority    = priority;
172 	fdef->nattrs      = nattr;
173 	fdef->nmatches    = nmatch;
174 	fdef->attrdata    = git_buf_detach(&attrs);
175 
176 	filter_def_set_attrs(fdef);
177 
178 	if (git_vector_insert(&filter_registry.filters, fdef) < 0) {
179 		git__free(fdef->filter_name);
180 		git__free(fdef->attrdata);
181 		git__free(fdef);
182 		return -1;
183 	}
184 
185 	git_vector_sort(&filter_registry.filters);
186 	return 0;
187 }
188 
git_filter_global_init(void)189 int git_filter_global_init(void)
190 {
191 	git_filter *crlf = NULL, *ident = NULL;
192 	int error = 0;
193 
194 	if (git_rwlock_init(&filter_registry.lock) < 0)
195 		return -1;
196 
197 	if ((error = git_vector_init(&filter_registry.filters, 2,
198 		filter_def_priority_cmp)) < 0)
199 		goto done;
200 
201 	if ((crlf = git_crlf_filter_new()) == NULL ||
202 		filter_registry_insert(
203 			GIT_FILTER_CRLF, crlf, GIT_FILTER_CRLF_PRIORITY) < 0 ||
204 		(ident = git_ident_filter_new()) == NULL ||
205 		filter_registry_insert(
206 			GIT_FILTER_IDENT, ident, GIT_FILTER_IDENT_PRIORITY) < 0)
207 		error = -1;
208 
209 	git__on_shutdown(git_filter_global_shutdown);
210 
211 done:
212 	if (error) {
213 		git_filter_free(crlf);
214 		git_filter_free(ident);
215 	}
216 
217 	return error;
218 }
219 
git_filter_global_shutdown(void)220 static void git_filter_global_shutdown(void)
221 {
222 	size_t pos;
223 	git_filter_def *fdef;
224 
225 	if (git_rwlock_wrlock(&filter_registry.lock) < 0)
226 		return;
227 
228 	git_vector_foreach(&filter_registry.filters, pos, fdef) {
229 		if (fdef->filter && fdef->filter->shutdown) {
230 			fdef->filter->shutdown(fdef->filter);
231 			fdef->initialized = false;
232 		}
233 
234 		git__free(fdef->filter_name);
235 		git__free(fdef->attrdata);
236 		git__free(fdef);
237 	}
238 
239 	git_vector_free(&filter_registry.filters);
240 
241 	git_rwlock_wrunlock(&filter_registry.lock);
242 	git_rwlock_free(&filter_registry.lock);
243 }
244 
245 /* Note: callers must lock the registry before calling this function */
filter_registry_find(size_t * pos,const char * name)246 static int filter_registry_find(size_t *pos, const char *name)
247 {
248 	return git_vector_search2(
249 		pos, &filter_registry.filters, filter_def_name_key_check, name);
250 }
251 
252 /* Note: callers must lock the registry before calling this function */
filter_registry_lookup(size_t * pos,const char * name)253 static git_filter_def *filter_registry_lookup(size_t *pos, const char *name)
254 {
255 	git_filter_def *fdef = NULL;
256 
257 	if (!filter_registry_find(pos, name))
258 		fdef = git_vector_get(&filter_registry.filters, *pos);
259 
260 	return fdef;
261 }
262 
263 
git_filter_register(const char * name,git_filter * filter,int priority)264 int git_filter_register(
265 	const char *name, git_filter *filter, int priority)
266 {
267 	int error;
268 
269 	assert(name && filter);
270 
271 	if (git_rwlock_wrlock(&filter_registry.lock) < 0) {
272 		git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
273 		return -1;
274 	}
275 
276 	if (!filter_registry_find(NULL, name)) {
277 		git_error_set(
278 			GIT_ERROR_FILTER, "attempt to reregister existing filter '%s'", name);
279 		error = GIT_EEXISTS;
280 		goto done;
281 	}
282 
283 	error = filter_registry_insert(name, filter, priority);
284 
285 done:
286 	git_rwlock_wrunlock(&filter_registry.lock);
287 	return error;
288 }
289 
git_filter_unregister(const char * name)290 int git_filter_unregister(const char *name)
291 {
292 	size_t pos;
293 	git_filter_def *fdef;
294 	int error = 0;
295 
296 	assert(name);
297 
298 	/* cannot unregister default filters */
299 	if (!strcmp(GIT_FILTER_CRLF, name) || !strcmp(GIT_FILTER_IDENT, name)) {
300 		git_error_set(GIT_ERROR_FILTER, "cannot unregister filter '%s'", name);
301 		return -1;
302 	}
303 
304 	if (git_rwlock_wrlock(&filter_registry.lock) < 0) {
305 		git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
306 		return -1;
307 	}
308 
309 	if ((fdef = filter_registry_lookup(&pos, name)) == NULL) {
310 		git_error_set(GIT_ERROR_FILTER, "cannot find filter '%s' to unregister", name);
311 		error = GIT_ENOTFOUND;
312 		goto done;
313 	}
314 
315 	git_vector_remove(&filter_registry.filters, pos);
316 
317 	if (fdef->initialized && fdef->filter && fdef->filter->shutdown) {
318 		fdef->filter->shutdown(fdef->filter);
319 		fdef->initialized = false;
320 	}
321 
322 	git__free(fdef->filter_name);
323 	git__free(fdef->attrdata);
324 	git__free(fdef);
325 
326 done:
327 	git_rwlock_wrunlock(&filter_registry.lock);
328 	return error;
329 }
330 
filter_initialize(git_filter_def * fdef)331 static int filter_initialize(git_filter_def *fdef)
332 {
333 	int error = 0;
334 
335 	if (!fdef->initialized && fdef->filter && fdef->filter->initialize) {
336 		if ((error = fdef->filter->initialize(fdef->filter)) < 0)
337 			return error;
338 	}
339 
340 	fdef->initialized = true;
341 	return 0;
342 }
343 
git_filter_lookup(const char * name)344 git_filter *git_filter_lookup(const char *name)
345 {
346 	size_t pos;
347 	git_filter_def *fdef;
348 	git_filter *filter = NULL;
349 
350 	if (git_rwlock_rdlock(&filter_registry.lock) < 0) {
351 		git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
352 		return NULL;
353 	}
354 
355 	if ((fdef = filter_registry_lookup(&pos, name)) == NULL ||
356 		(!fdef->initialized && filter_initialize(fdef) < 0))
357 		goto done;
358 
359 	filter = fdef->filter;
360 
361 done:
362 	git_rwlock_rdunlock(&filter_registry.lock);
363 	return filter;
364 }
365 
git_filter_free(git_filter * filter)366 void git_filter_free(git_filter *filter)
367 {
368 	git__free(filter);
369 }
370 
git_filter_source_repo(const git_filter_source * src)371 git_repository *git_filter_source_repo(const git_filter_source *src)
372 {
373 	return src->repo;
374 }
375 
git_filter_source_path(const git_filter_source * src)376 const char *git_filter_source_path(const git_filter_source *src)
377 {
378 	return src->path;
379 }
380 
git_filter_source_filemode(const git_filter_source * src)381 uint16_t git_filter_source_filemode(const git_filter_source *src)
382 {
383 	return src->filemode;
384 }
385 
git_filter_source_id(const git_filter_source * src)386 const git_oid *git_filter_source_id(const git_filter_source *src)
387 {
388 	return git_oid_iszero(&src->oid) ? NULL : &src->oid;
389 }
390 
git_filter_source_mode(const git_filter_source * src)391 git_filter_mode_t git_filter_source_mode(const git_filter_source *src)
392 {
393 	return src->mode;
394 }
395 
git_filter_source_flags(const git_filter_source * src)396 uint32_t git_filter_source_flags(const git_filter_source *src)
397 {
398 	return src->flags;
399 }
400 
filter_list_new(git_filter_list ** out,const git_filter_source * src)401 static int filter_list_new(
402 	git_filter_list **out, const git_filter_source *src)
403 {
404 	git_filter_list *fl = NULL;
405 	size_t pathlen = src->path ? strlen(src->path) : 0, alloclen;
406 
407 	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_filter_list), pathlen);
408 	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
409 
410 	fl = git__calloc(1, alloclen);
411 	GIT_ERROR_CHECK_ALLOC(fl);
412 
413 	if (src->path)
414 		memcpy(fl->path, src->path, pathlen);
415 	fl->source.repo = src->repo;
416 	fl->source.path = fl->path;
417 	fl->source.mode = src->mode;
418 	fl->source.flags = src->flags;
419 
420 	*out = fl;
421 	return 0;
422 }
423 
filter_list_check_attributes(const char *** out,git_repository * repo,git_attr_session * attr_session,git_filter_def * fdef,const git_filter_source * src)424 static int filter_list_check_attributes(
425 	const char ***out,
426 	git_repository *repo,
427 	git_attr_session *attr_session,
428 	git_filter_def *fdef,
429 	const git_filter_source *src)
430 {
431 	int error;
432 	size_t i;
433 	const char **strs = git__calloc(fdef->nattrs, sizeof(const char *));
434 	GIT_ERROR_CHECK_ALLOC(strs);
435 
436 	error = git_attr_get_many_with_session(
437 		strs, repo, attr_session, 0, src->path, fdef->nattrs, fdef->attrs);
438 
439 	/* if no values were found but no matches are needed, it's okay! */
440 	if (error == GIT_ENOTFOUND && !fdef->nmatches) {
441 		git_error_clear();
442 		git__free((void *)strs);
443 		return 0;
444 	}
445 
446 	for (i = 0; !error && i < fdef->nattrs; ++i) {
447 		const char *want = fdef->attrs[fdef->nattrs + i];
448 		git_attr_t want_type, found_type;
449 
450 		if (!want)
451 			continue;
452 
453 		want_type  = git_attr_value(want);
454 		found_type = git_attr_value(strs[i]);
455 
456 		if (want_type != found_type)
457 			error = GIT_ENOTFOUND;
458 		else if (want_type == GIT_ATTR_VALUE_T &&
459 				strcmp(want, strs[i]) &&
460 				strcmp(want, "*"))
461 			error = GIT_ENOTFOUND;
462 	}
463 
464 	if (error)
465 		git__free((void *)strs);
466 	else
467 		*out = strs;
468 
469 	return error;
470 }
471 
git_filter_list_new(git_filter_list ** out,git_repository * repo,git_filter_mode_t mode,uint32_t flags)472 int git_filter_list_new(
473 	git_filter_list **out,
474 	git_repository *repo,
475 	git_filter_mode_t mode,
476 	uint32_t flags)
477 {
478 	git_filter_source src = { 0 };
479 	src.repo = repo;
480 	src.path = NULL;
481 	src.mode = mode;
482 	src.flags = flags;
483 	return filter_list_new(out, &src);
484 }
485 
git_filter_list__load_ext(git_filter_list ** filters,git_repository * repo,git_blob * blob,const char * path,git_filter_mode_t mode,git_filter_options * filter_opts)486 int git_filter_list__load_ext(
487 	git_filter_list **filters,
488 	git_repository *repo,
489 	git_blob *blob, /* can be NULL */
490 	const char *path,
491 	git_filter_mode_t mode,
492 	git_filter_options *filter_opts)
493 {
494 	int error = 0;
495 	git_filter_list *fl = NULL;
496 	git_filter_source src = { 0 };
497 	git_filter_entry *fe;
498 	size_t idx;
499 	git_filter_def *fdef;
500 
501 	if (git_rwlock_rdlock(&filter_registry.lock) < 0) {
502 		git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
503 		return -1;
504 	}
505 
506 	src.repo = repo;
507 	src.path = path;
508 	src.mode = mode;
509 	src.flags = filter_opts->flags;
510 
511 	if (blob)
512 		git_oid_cpy(&src.oid, git_blob_id(blob));
513 
514 	git_vector_foreach(&filter_registry.filters, idx, fdef) {
515 		const char **values = NULL;
516 		void *payload = NULL;
517 
518 		if (!fdef || !fdef->filter)
519 			continue;
520 
521 		if (fdef->nattrs > 0) {
522 			error = filter_list_check_attributes(
523 				&values, repo, filter_opts->attr_session, fdef, &src);
524 
525 			if (error == GIT_ENOTFOUND) {
526 				error = 0;
527 				continue;
528 			} else if (error < 0)
529 				break;
530 		}
531 
532 		if (!fdef->initialized && (error = filter_initialize(fdef)) < 0)
533 			break;
534 
535 		if (fdef->filter->check)
536 			error = fdef->filter->check(
537 				fdef->filter, &payload, &src, values);
538 
539 		git__free((void *)values);
540 
541 		if (error == GIT_PASSTHROUGH)
542 			error = 0;
543 		else if (error < 0)
544 			break;
545 		else {
546 			if (!fl) {
547 				if ((error = filter_list_new(&fl, &src)) < 0)
548 					break;
549 
550 				fl->temp_buf = filter_opts->temp_buf;
551 			}
552 
553 			fe = git_array_alloc(fl->filters);
554 			GIT_ERROR_CHECK_ALLOC(fe);
555 
556 			fe->filter = fdef->filter;
557 			fe->filter_name = fdef->filter_name;
558 			fe->payload = payload;
559 		}
560 	}
561 
562 	git_rwlock_rdunlock(&filter_registry.lock);
563 
564 	if (error && fl != NULL) {
565 		git_array_clear(fl->filters);
566 		git__free(fl);
567 		fl = NULL;
568 	}
569 
570 	*filters = fl;
571 	return error;
572 }
573 
git_filter_list_load(git_filter_list ** filters,git_repository * repo,git_blob * blob,const char * path,git_filter_mode_t mode,uint32_t flags)574 int git_filter_list_load(
575 	git_filter_list **filters,
576 	git_repository *repo,
577 	git_blob *blob, /* can be NULL */
578 	const char *path,
579 	git_filter_mode_t mode,
580 	uint32_t flags)
581 {
582 	git_filter_options filter_opts = GIT_FILTER_OPTIONS_INIT;
583 
584 	filter_opts.flags = flags;
585 
586 	return git_filter_list__load_ext(
587 		filters, repo, blob, path, mode, &filter_opts);
588 }
589 
git_filter_list_free(git_filter_list * fl)590 void git_filter_list_free(git_filter_list *fl)
591 {
592 	uint32_t i;
593 
594 	if (!fl)
595 		return;
596 
597 	for (i = 0; i < git_array_size(fl->filters); ++i) {
598 		git_filter_entry *fe = git_array_get(fl->filters, i);
599 		if (fe->filter->cleanup)
600 			fe->filter->cleanup(fe->filter, fe->payload);
601 	}
602 
603 	git_array_clear(fl->filters);
604 	git__free(fl);
605 }
606 
git_filter_list_contains(git_filter_list * fl,const char * name)607 int git_filter_list_contains(
608 	git_filter_list *fl,
609 	const char *name)
610 {
611 	size_t i;
612 
613 	assert(name);
614 
615 	if (!fl)
616 		return 0;
617 
618 	for (i = 0; i < fl->filters.size; i++) {
619 		if (strcmp(fl->filters.ptr[i].filter_name, name) == 0)
620 			return 1;
621 	}
622 
623 	return 0;
624 }
625 
git_filter_list_push(git_filter_list * fl,git_filter * filter,void * payload)626 int git_filter_list_push(
627 	git_filter_list *fl, git_filter *filter, void *payload)
628 {
629 	int error = 0;
630 	size_t pos;
631 	git_filter_def *fdef = NULL;
632 	git_filter_entry *fe;
633 
634 	assert(fl && filter);
635 
636 	if (git_rwlock_rdlock(&filter_registry.lock) < 0) {
637 		git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
638 		return -1;
639 	}
640 
641 	if (git_vector_search2(
642 			&pos, &filter_registry.filters,
643 			filter_def_filter_key_check, filter) == 0)
644 		fdef = git_vector_get(&filter_registry.filters, pos);
645 
646 	git_rwlock_rdunlock(&filter_registry.lock);
647 
648 	if (fdef == NULL) {
649 		git_error_set(GIT_ERROR_FILTER, "cannot use an unregistered filter");
650 		return -1;
651 	}
652 
653 	if (!fdef->initialized && (error = filter_initialize(fdef)) < 0)
654 		return error;
655 
656 	fe = git_array_alloc(fl->filters);
657 	GIT_ERROR_CHECK_ALLOC(fe);
658 	fe->filter  = filter;
659 	fe->payload = payload;
660 
661 	return 0;
662 }
663 
git_filter_list_length(const git_filter_list * fl)664 size_t git_filter_list_length(const git_filter_list *fl)
665 {
666 	return fl ? git_array_size(fl->filters) : 0;
667 }
668 
669 struct buf_stream {
670 	git_writestream parent;
671 	git_buf *target;
672 	bool complete;
673 };
674 
buf_stream_write(git_writestream * s,const char * buffer,size_t len)675 static int buf_stream_write(
676 	git_writestream *s, const char *buffer, size_t len)
677 {
678 	struct buf_stream *buf_stream = (struct buf_stream *)s;
679 	assert(buf_stream);
680 
681 	assert(buf_stream->complete == 0);
682 
683 	return git_buf_put(buf_stream->target, buffer, len);
684 }
685 
buf_stream_close(git_writestream * s)686 static int buf_stream_close(git_writestream *s)
687 {
688 	struct buf_stream *buf_stream = (struct buf_stream *)s;
689 	assert(buf_stream);
690 
691 	assert(buf_stream->complete == 0);
692 	buf_stream->complete = 1;
693 
694 	return 0;
695 }
696 
buf_stream_free(git_writestream * s)697 static void buf_stream_free(git_writestream *s)
698 {
699 	GIT_UNUSED(s);
700 }
701 
buf_stream_init(struct buf_stream * writer,git_buf * target)702 static void buf_stream_init(struct buf_stream *writer, git_buf *target)
703 {
704 	memset(writer, 0, sizeof(struct buf_stream));
705 
706 	writer->parent.write = buf_stream_write;
707 	writer->parent.close = buf_stream_close;
708 	writer->parent.free = buf_stream_free;
709 	writer->target = target;
710 
711 	git_buf_clear(target);
712 }
713 
git_filter_list_apply_to_data(git_buf * tgt,git_filter_list * filters,git_buf * src)714 int git_filter_list_apply_to_data(
715 	git_buf *tgt, git_filter_list *filters, git_buf *src)
716 {
717 	struct buf_stream writer;
718 	int error;
719 
720 	git_buf_sanitize(tgt);
721 	git_buf_sanitize(src);
722 
723 	if (!filters) {
724 		git_buf_attach_notowned(tgt, src->ptr, src->size);
725 		return 0;
726 	}
727 
728 	buf_stream_init(&writer, tgt);
729 
730 	if ((error = git_filter_list_stream_data(filters, src,
731 		&writer.parent)) < 0)
732 			return error;
733 
734 	assert(writer.complete);
735 	return error;
736 }
737 
git_filter_list_apply_to_file(git_buf * out,git_filter_list * filters,git_repository * repo,const char * path)738 int git_filter_list_apply_to_file(
739 	git_buf *out,
740 	git_filter_list *filters,
741 	git_repository *repo,
742 	const char *path)
743 {
744 	struct buf_stream writer;
745 	int error;
746 
747 	buf_stream_init(&writer, out);
748 
749 	if ((error = git_filter_list_stream_file(
750 		filters, repo, path, &writer.parent)) < 0)
751 			return error;
752 
753 	assert(writer.complete);
754 	return error;
755 }
756 
buf_from_blob(git_buf * out,git_blob * blob)757 static int buf_from_blob(git_buf *out, git_blob *blob)
758 {
759 	git_off_t rawsize = git_blob_rawsize(blob);
760 
761 	if (!git__is_sizet(rawsize)) {
762 		git_error_set(GIT_ERROR_OS, "blob is too large to filter");
763 		return -1;
764 	}
765 
766 	git_buf_attach_notowned(out, git_blob_rawcontent(blob), (size_t)rawsize);
767 	return 0;
768 }
769 
git_filter_list_apply_to_blob(git_buf * out,git_filter_list * filters,git_blob * blob)770 int git_filter_list_apply_to_blob(
771 	git_buf *out,
772 	git_filter_list *filters,
773 	git_blob *blob)
774 {
775 	struct buf_stream writer;
776 	int error;
777 
778 	buf_stream_init(&writer, out);
779 
780 	if ((error = git_filter_list_stream_blob(
781 		filters, blob, &writer.parent)) < 0)
782 			return error;
783 
784 	assert(writer.complete);
785 	return error;
786 }
787 
788 struct proxy_stream {
789 	git_writestream parent;
790 	git_filter *filter;
791 	const git_filter_source *source;
792 	void **payload;
793 	git_buf input;
794 	git_buf temp_buf;
795 	git_buf *output;
796 	git_writestream *target;
797 };
798 
proxy_stream_write(git_writestream * s,const char * buffer,size_t len)799 static int proxy_stream_write(
800 	git_writestream *s, const char *buffer, size_t len)
801 {
802 	struct proxy_stream *proxy_stream = (struct proxy_stream *)s;
803 	assert(proxy_stream);
804 
805 	return git_buf_put(&proxy_stream->input, buffer, len);
806 }
807 
proxy_stream_close(git_writestream * s)808 static int proxy_stream_close(git_writestream *s)
809 {
810 	struct proxy_stream *proxy_stream = (struct proxy_stream *)s;
811 	git_buf *writebuf;
812 	git_error_state error_state = {0};
813 	int error;
814 
815 	assert(proxy_stream);
816 
817 	error = proxy_stream->filter->apply(
818 		proxy_stream->filter,
819 		proxy_stream->payload,
820 		proxy_stream->output,
821 		&proxy_stream->input,
822 		proxy_stream->source);
823 
824 	if (error == GIT_PASSTHROUGH) {
825 		writebuf = &proxy_stream->input;
826 	} else if (error == 0) {
827 		git_buf_sanitize(proxy_stream->output);
828 		writebuf = proxy_stream->output;
829 	} else {
830 		/* close stream before erroring out taking care
831 		 * to preserve the original error */
832 		git_error_state_capture(&error_state, error);
833 		proxy_stream->target->close(proxy_stream->target);
834 		git_error_state_restore(&error_state);
835 		return error;
836 	}
837 
838 	if ((error = proxy_stream->target->write(
839 			proxy_stream->target, writebuf->ptr, writebuf->size)) == 0)
840 		error = proxy_stream->target->close(proxy_stream->target);
841 
842 	return error;
843 }
844 
proxy_stream_free(git_writestream * s)845 static void proxy_stream_free(git_writestream *s)
846 {
847 	struct proxy_stream *proxy_stream = (struct proxy_stream *)s;
848 	assert(proxy_stream);
849 
850 	git_buf_dispose(&proxy_stream->input);
851 	git_buf_dispose(&proxy_stream->temp_buf);
852 	git__free(proxy_stream);
853 }
854 
proxy_stream_init(git_writestream ** out,git_filter * filter,git_buf * temp_buf,void ** payload,const git_filter_source * source,git_writestream * target)855 static int proxy_stream_init(
856 	git_writestream **out,
857 	git_filter *filter,
858 	git_buf *temp_buf,
859 	void **payload,
860 	const git_filter_source *source,
861 	git_writestream *target)
862 {
863 	struct proxy_stream *proxy_stream = git__calloc(1, sizeof(struct proxy_stream));
864 	GIT_ERROR_CHECK_ALLOC(proxy_stream);
865 
866 	proxy_stream->parent.write = proxy_stream_write;
867 	proxy_stream->parent.close = proxy_stream_close;
868 	proxy_stream->parent.free = proxy_stream_free;
869 	proxy_stream->filter = filter;
870 	proxy_stream->payload = payload;
871 	proxy_stream->source = source;
872 	proxy_stream->target = target;
873 	proxy_stream->output = temp_buf ? temp_buf : &proxy_stream->temp_buf;
874 
875 	if (temp_buf)
876 		git_buf_clear(temp_buf);
877 
878 	*out = (git_writestream *)proxy_stream;
879 	return 0;
880 }
881 
stream_list_init(git_writestream ** out,git_vector * streams,git_filter_list * filters,git_writestream * target)882 static int stream_list_init(
883 	git_writestream **out,
884 	git_vector *streams,
885 	git_filter_list *filters,
886 	git_writestream *target)
887 {
888 	git_writestream *last_stream = target;
889 	size_t i;
890 	int error = 0;
891 
892 	*out = NULL;
893 
894 	if (!filters) {
895 		*out = target;
896 		return 0;
897 	}
898 
899 	/* Create filters last to first to get the chaining direction */
900 	for (i = 0; i < git_array_size(filters->filters); ++i) {
901 		size_t filter_idx = (filters->source.mode == GIT_FILTER_TO_WORKTREE) ?
902 			git_array_size(filters->filters) - 1 - i : i;
903 		git_filter_entry *fe = git_array_get(filters->filters, filter_idx);
904 		git_writestream *filter_stream;
905 
906 		assert(fe->filter->stream || fe->filter->apply);
907 
908 		/* If necessary, create a stream that proxies the traditional
909 		 * application.
910 		 */
911 		if (fe->filter->stream)
912 			error = fe->filter->stream(&filter_stream, fe->filter,
913 				&fe->payload, &filters->source, last_stream);
914 		else
915 			/* Create a stream that proxies the one-shot apply */
916 			error = proxy_stream_init(&filter_stream, fe->filter,
917 				filters->temp_buf, &fe->payload, &filters->source,
918 				last_stream);
919 
920 		if (error < 0)
921 			goto out;
922 
923 		git_vector_insert(streams, filter_stream);
924 		last_stream = filter_stream;
925 	}
926 
927 out:
928 	if (error)
929 		last_stream->close(last_stream);
930 	else
931 		*out = last_stream;
932 
933 	return error;
934 }
935 
stream_list_free(git_vector * streams)936 void stream_list_free(git_vector *streams)
937 {
938 	git_writestream *stream;
939 	size_t i;
940 
941 	git_vector_foreach(streams, i, stream)
942 		stream->free(stream);
943 	git_vector_free(streams);
944 }
945 
git_filter_list_stream_file(git_filter_list * filters,git_repository * repo,const char * path,git_writestream * target)946 int git_filter_list_stream_file(
947 	git_filter_list *filters,
948 	git_repository *repo,
949 	const char *path,
950 	git_writestream *target)
951 {
952 	char buf[FILTERIO_BUFSIZE];
953 	git_buf abspath = GIT_BUF_INIT;
954 	const char *base = repo ? git_repository_workdir(repo) : NULL;
955 	git_vector filter_streams = GIT_VECTOR_INIT;
956 	git_writestream *stream_start;
957 	ssize_t readlen;
958 	int fd = -1, error, initialized = 0;
959 
960 	if ((error = stream_list_init(
961 			&stream_start, &filter_streams, filters, target)) < 0 ||
962 		(error = git_path_join_unrooted(&abspath, path, base, NULL)) < 0)
963 		goto done;
964 	initialized = 1;
965 
966 	if ((fd = git_futils_open_ro(abspath.ptr)) < 0) {
967 		error = fd;
968 		goto done;
969 	}
970 
971 	while ((readlen = p_read(fd, buf, sizeof(buf))) > 0) {
972 		if ((error = stream_start->write(stream_start, buf, readlen)) < 0)
973 			goto done;
974 	}
975 
976 	if (readlen < 0)
977 		error = -1;
978 
979 done:
980 	if (initialized)
981 		error |= stream_start->close(stream_start);
982 
983 	if (fd >= 0)
984 		p_close(fd);
985 	stream_list_free(&filter_streams);
986 	git_buf_dispose(&abspath);
987 	return error;
988 }
989 
git_filter_list_stream_data(git_filter_list * filters,git_buf * data,git_writestream * target)990 int git_filter_list_stream_data(
991 	git_filter_list *filters,
992 	git_buf *data,
993 	git_writestream *target)
994 {
995 	git_vector filter_streams = GIT_VECTOR_INIT;
996 	git_writestream *stream_start;
997 	int error, initialized = 0;
998 
999 	git_buf_sanitize(data);
1000 
1001 	if ((error = stream_list_init(&stream_start, &filter_streams, filters, target)) < 0)
1002 		goto out;
1003 	initialized = 1;
1004 
1005 	if ((error = stream_start->write(
1006 			stream_start, data->ptr, data->size)) < 0)
1007 		goto out;
1008 
1009 out:
1010 	if (initialized)
1011 		error |= stream_start->close(stream_start);
1012 
1013 	stream_list_free(&filter_streams);
1014 	return error;
1015 }
1016 
git_filter_list_stream_blob(git_filter_list * filters,git_blob * blob,git_writestream * target)1017 int git_filter_list_stream_blob(
1018 	git_filter_list *filters,
1019 	git_blob *blob,
1020 	git_writestream *target)
1021 {
1022 	git_buf in = GIT_BUF_INIT;
1023 
1024 	if (buf_from_blob(&in, blob) < 0)
1025 		return -1;
1026 
1027 	if (filters)
1028 		git_oid_cpy(&filters->source.oid, git_blob_id(blob));
1029 
1030 	return git_filter_list_stream_data(filters, &in, target);
1031 }
1032 
git_filter_init(git_filter * filter,unsigned int version)1033 int git_filter_init(git_filter *filter, unsigned int version)
1034 {
1035 	GIT_INIT_STRUCTURE_FROM_TEMPLATE(filter, version, git_filter, GIT_FILTER_INIT);
1036 	return 0;
1037 }
1038