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 "signature.h"
9 
10 #include "repository.h"
11 #include "git2/common.h"
12 #include "posix.h"
13 
git_signature_free(git_signature * sig)14 void git_signature_free(git_signature *sig)
15 {
16 	if (sig == NULL)
17 		return;
18 
19 	git__free(sig->name);
20 	sig->name = NULL;
21 	git__free(sig->email);
22 	sig->email = NULL;
23 	git__free(sig);
24 }
25 
signature_error(const char * msg)26 static int signature_error(const char *msg)
27 {
28 	git_error_set(GIT_ERROR_INVALID, "failed to parse signature - %s", msg);
29 	return -1;
30 }
31 
contains_angle_brackets(const char * input)32 static bool contains_angle_brackets(const char *input)
33 {
34 	return strchr(input, '<') != NULL || strchr(input, '>') != NULL;
35 }
36 
is_crud(unsigned char c)37 static bool is_crud(unsigned char c)
38 {
39 	return  c <= 32  ||
40 		c == '.' ||
41 		c == ',' ||
42 		c == ':' ||
43 		c == ';' ||
44 		c == '<' ||
45 		c == '>' ||
46 		c == '"' ||
47 		c == '\\' ||
48 		c == '\'';
49 }
50 
extract_trimmed(const char * ptr,size_t len)51 static char *extract_trimmed(const char *ptr, size_t len)
52 {
53 	while (len && is_crud((unsigned char)ptr[0])) {
54 		ptr++; len--;
55 	}
56 
57 	while (len && is_crud((unsigned char)ptr[len - 1])) {
58 		len--;
59 	}
60 
61 	return git__substrdup(ptr, len);
62 }
63 
git_signature_new(git_signature ** sig_out,const char * name,const char * email,git_time_t time,int offset)64 int git_signature_new(git_signature **sig_out, const char *name, const char *email, git_time_t time, int offset)
65 {
66 	git_signature *p = NULL;
67 
68 	GIT_ASSERT_ARG(name);
69 	GIT_ASSERT_ARG(email);
70 
71 	*sig_out = NULL;
72 
73 	if (contains_angle_brackets(name) ||
74 		contains_angle_brackets(email)) {
75 		return signature_error(
76 			"Neither `name` nor `email` should contain angle brackets chars.");
77 	}
78 
79 	p = git__calloc(1, sizeof(git_signature));
80 	GIT_ERROR_CHECK_ALLOC(p);
81 
82 	p->name = extract_trimmed(name, strlen(name));
83 	GIT_ERROR_CHECK_ALLOC(p->name);
84 	p->email = extract_trimmed(email, strlen(email));
85 	GIT_ERROR_CHECK_ALLOC(p->email);
86 
87 	if (p->name[0] == '\0' || p->email[0] == '\0') {
88 		git_signature_free(p);
89 		return signature_error("Signature cannot have an empty name or email");
90 	}
91 
92 	p->when.time = time;
93 	p->when.offset = offset;
94 	p->when.sign = (offset < 0) ? '-' : '+';
95 
96 	*sig_out = p;
97 	return 0;
98 }
99 
git_signature_dup(git_signature ** dest,const git_signature * source)100 int git_signature_dup(git_signature **dest, const git_signature *source)
101 {
102 	git_signature *signature;
103 
104 	if (source == NULL)
105 		return 0;
106 
107 	signature = git__calloc(1, sizeof(git_signature));
108 	GIT_ERROR_CHECK_ALLOC(signature);
109 
110 	signature->name = git__strdup(source->name);
111 	GIT_ERROR_CHECK_ALLOC(signature->name);
112 
113 	signature->email = git__strdup(source->email);
114 	GIT_ERROR_CHECK_ALLOC(signature->email);
115 
116 	signature->when.time = source->when.time;
117 	signature->when.offset = source->when.offset;
118 	signature->when.sign = source->when.sign;
119 
120 	*dest = signature;
121 
122 	return 0;
123 }
124 
git_signature__pdup(git_signature ** dest,const git_signature * source,git_pool * pool)125 int git_signature__pdup(git_signature **dest, const git_signature *source, git_pool *pool)
126 {
127 	git_signature *signature;
128 
129 	if (source == NULL)
130 		return 0;
131 
132 	signature = git_pool_mallocz(pool, sizeof(git_signature));
133 	GIT_ERROR_CHECK_ALLOC(signature);
134 
135 	signature->name = git_pool_strdup(pool, source->name);
136 	GIT_ERROR_CHECK_ALLOC(signature->name);
137 
138 	signature->email = git_pool_strdup(pool, source->email);
139 	GIT_ERROR_CHECK_ALLOC(signature->email);
140 
141 	signature->when.time = source->when.time;
142 	signature->when.offset = source->when.offset;
143 	signature->when.sign = source->when.sign;
144 
145 	*dest = signature;
146 
147 	return 0;
148 }
149 
git_signature_now(git_signature ** sig_out,const char * name,const char * email)150 int git_signature_now(git_signature **sig_out, const char *name, const char *email)
151 {
152 	time_t now;
153 	time_t offset;
154 	struct tm *utc_tm;
155 	git_signature *sig;
156 	struct tm _utc;
157 
158 	*sig_out = NULL;
159 
160 	/*
161 	 * Get the current time as seconds since the epoch and
162 	 * transform that into a tm struct containing the time at
163 	 * UTC. Give that to mktime which considers it a local time
164 	 * (tm_isdst = -1 asks it to take DST into account) and gives
165 	 * us that time as seconds since the epoch. The difference
166 	 * between its return value and 'now' is our offset to UTC.
167 	 */
168 	time(&now);
169 	utc_tm = p_gmtime_r(&now, &_utc);
170 	utc_tm->tm_isdst = -1;
171 	offset = (time_t)difftime(now, mktime(utc_tm));
172 	offset /= 60;
173 
174 	if (git_signature_new(&sig, name, email, now, (int)offset) < 0)
175 		return -1;
176 
177 	*sig_out = sig;
178 
179 	return 0;
180 }
181 
git_signature_default(git_signature ** out,git_repository * repo)182 int git_signature_default(git_signature **out, git_repository *repo)
183 {
184 	int error;
185 	git_config *cfg;
186 	const char *user_name, *user_email;
187 
188 	if ((error = git_repository_config_snapshot(&cfg, repo)) < 0)
189 		return error;
190 
191 	if (!(error = git_config_get_string(&user_name, cfg, "user.name")) &&
192 		!(error = git_config_get_string(&user_email, cfg, "user.email")))
193 		error = git_signature_now(out, user_name, user_email);
194 
195 	git_config_free(cfg);
196 	return error;
197 }
198 
git_signature__parse(git_signature * sig,const char ** buffer_out,const char * buffer_end,const char * header,char ender)199 int git_signature__parse(git_signature *sig, const char **buffer_out,
200 		const char *buffer_end, const char *header, char ender)
201 {
202 	const char *buffer = *buffer_out;
203 	const char *email_start, *email_end;
204 
205 	memset(sig, 0, sizeof(git_signature));
206 
207 	if (ender &&
208 		(buffer_end = memchr(buffer, ender, buffer_end - buffer)) == NULL)
209 		return signature_error("no newline given");
210 
211 	if (header) {
212 		const size_t header_len = strlen(header);
213 
214 		if (buffer + header_len >= buffer_end || memcmp(buffer, header, header_len) != 0)
215 			return signature_error("expected prefix doesn't match actual");
216 
217 		buffer += header_len;
218 	}
219 
220 	email_start = git__memrchr(buffer, '<', buffer_end - buffer);
221 	email_end = git__memrchr(buffer, '>', buffer_end - buffer);
222 
223 	if (!email_start || !email_end || email_end <= email_start)
224 		return signature_error("malformed e-mail");
225 
226 	email_start += 1;
227 	sig->name = extract_trimmed(buffer, email_start - buffer - 1);
228 	sig->email = extract_trimmed(email_start, email_end - email_start);
229 
230 	/* Do we even have a time at the end of the signature? */
231 	if (email_end + 2 < buffer_end) {
232 		const char *time_start = email_end + 2;
233 		const char *time_end;
234 
235 		if (git__strntol64(&sig->when.time, time_start,
236 				   buffer_end - time_start, &time_end, 10) < 0) {
237 			git__free(sig->name);
238 			git__free(sig->email);
239 			sig->name = sig->email = NULL;
240 			return signature_error("invalid Unix timestamp");
241 		}
242 
243 		/* do we have a timezone? */
244 		if (time_end + 1 < buffer_end) {
245 			int offset, hours, mins;
246 			const char *tz_start, *tz_end;
247 
248 			tz_start = time_end + 1;
249 
250 			if ((tz_start[0] != '-' && tz_start[0] != '+') ||
251 			    git__strntol32(&offset, tz_start + 1,
252 					   buffer_end - tz_start - 1, &tz_end, 10) < 0) {
253 				/* malformed timezone, just assume it's zero */
254 				offset = 0;
255 			}
256 
257 			hours = offset / 100;
258 			mins = offset % 100;
259 
260 			/*
261 			 * only store timezone if it's not overflowing;
262 			 * see http://www.worldtimezone.com/faq.html
263 			 */
264 			if (hours <= 14 && mins <= 59) {
265 				sig->when.offset = (hours * 60) + mins;
266 				sig->when.sign = tz_start[0];
267 				if (tz_start[0] == '-')
268 					sig->when.offset = -sig->when.offset;
269 			}
270 		}
271 	}
272 
273 	*buffer_out = buffer_end + 1;
274 	return 0;
275 }
276 
git_signature_from_buffer(git_signature ** out,const char * buf)277 int git_signature_from_buffer(git_signature **out, const char *buf)
278 {
279 	git_signature *sig;
280 	const char *buf_end;
281 	int error;
282 
283 	GIT_ASSERT_ARG(out);
284 	GIT_ASSERT_ARG(buf);
285 
286 	*out = NULL;
287 
288 	sig = git__calloc(1, sizeof(git_signature));
289 	GIT_ERROR_CHECK_ALLOC(sig);
290 
291 	buf_end = buf + strlen(buf);
292 	error = git_signature__parse(sig, &buf, buf_end, NULL, '\0');
293 
294 	if (error)
295 		git__free(sig);
296 	else
297 		*out = sig;
298 
299 	return error;
300 }
301 
git_signature__writebuf(git_buf * buf,const char * header,const git_signature * sig)302 void git_signature__writebuf(git_buf *buf, const char *header, const git_signature *sig)
303 {
304 	int offset, hours, mins;
305 	char sign;
306 
307 	offset = sig->when.offset;
308 	sign = (sig->when.offset < 0 || sig->when.sign == '-') ? '-' : '+';
309 
310 	if (offset < 0)
311 		offset = -offset;
312 
313 	hours = offset / 60;
314 	mins = offset % 60;
315 
316 	git_buf_printf(buf, "%s%s <%s> %u %c%02d%02d\n",
317 			header ? header : "", sig->name, sig->email,
318 			(unsigned)sig->when.time, sign, hours, mins);
319 }
320 
git_signature__equal(const git_signature * one,const git_signature * two)321 bool git_signature__equal(const git_signature *one, const git_signature *two)
322 {
323 	GIT_ASSERT_ARG(one);
324 	GIT_ASSERT_ARG(two);
325 
326 	return
327 		git__strcmp(one->name, two->name) == 0 &&
328 		git__strcmp(one->email, two->email) == 0 &&
329 		one->when.time == two->when.time &&
330 		one->when.offset == two->when.offset &&
331 		one->when.sign == two->when.sign;
332 }
333 
334