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