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