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