]>
Commit | Line | Data |
---|---|---|
58519018 | 1 | /* |
bb742ede | 2 | * Copyright (C) 2009-2011 the libgit2 contributors |
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 | ||
8 | #include "common.h" | |
638c2ca4 | 9 | #include "signature.h" |
58519018 | 10 | #include "repository.h" |
44908fe7 | 11 | #include "git2/common.h" |
58519018 | 12 | |
638c2ca4 | 13 | void git_signature_free(git_signature *sig) |
58519018 | 14 | { |
638c2ca4 | 15 | if (sig == NULL) |
58519018 VM |
16 | return; |
17 | ||
3286c408 | 18 | git__free(sig->name); |
97769280 | 19 | sig->name = NULL; |
3286c408 | 20 | git__free(sig->email); |
97769280 | 21 | sig->email = NULL; |
3286c408 | 22 | git__free(sig); |
58519018 VM |
23 | } |
24 | ||
a01acc47 | 25 | static const char *skip_leading_spaces(const char *buffer, const char *buffer_end) |
26 | { | |
27 | while (*buffer == ' ' && buffer < buffer_end) | |
28 | buffer++; | |
29 | ||
30 | return buffer; | |
31 | } | |
32 | ||
33 | static const char *skip_trailing_spaces(const char *buffer_start, const char *buffer_end) | |
34 | { | |
35 | while (*buffer_end == ' ' && buffer_end > buffer_start) | |
36 | buffer_end--; | |
37 | ||
38 | return buffer_end; | |
39 | } | |
40 | ||
41 | static int process_trimming(const char *input, char **storage, const char *input_end, int fail_when_empty) | |
42 | { | |
43 | const char *left, *right; | |
44 | int trimmed_input_length; | |
45 | ||
46 | left = skip_leading_spaces(input, input_end); | |
47 | right = skip_trailing_spaces(input, input_end - 1); | |
48 | ||
5274c31a | 49 | if (right < left) { |
a01acc47 | 50 | if (fail_when_empty) |
51 | return git__throw(GIT_EINVALIDARGS, "Failed to trim. Input is either empty or only contains spaces"); | |
52 | else | |
53 | right = left - 1; | |
9f86ec52 | 54 | } |
a01acc47 | 55 | |
56 | trimmed_input_length = right - left + 1; | |
57 | ||
58 | *storage = git__malloc(trimmed_input_length + 1); | |
59 | if (*storage == NULL) | |
60 | return GIT_ENOMEM; | |
61 | ||
62 | memcpy(*storage, left, trimmed_input_length); | |
63 | (*storage)[trimmed_input_length] = 0; | |
64 | ||
65 | return GIT_SUCCESS; | |
66 | } | |
67 | ||
63396a39 | 68 | int git_signature_new(git_signature **sig_out, const char *name, const char *email, git_time_t time, int offset) |
58519018 | 69 | { |
a01acc47 | 70 | int error; |
638c2ca4 | 71 | git_signature *p = NULL; |
58519018 | 72 | |
a01acc47 | 73 | assert(name && email); |
74 | ||
63396a39 MS |
75 | *sig_out = NULL; |
76 | ||
77 | if ((p = git__malloc(sizeof(git_signature))) == NULL) { | |
78 | error = GIT_ENOMEM; | |
58519018 | 79 | goto cleanup; |
63396a39 | 80 | } |
58519018 | 81 | |
a01acc47 | 82 | memset(p, 0x0, sizeof(git_signature)); |
58519018 | 83 | |
a01acc47 | 84 | error = process_trimming(name, &p->name, name + strlen(name), 1); |
85 | if (error < GIT_SUCCESS) { | |
86 | git__rethrow(GIT_EINVALIDARGS, "Failed to create signature. 'name' argument is invalid"); | |
58519018 | 87 | goto cleanup; |
a01acc47 | 88 | } |
89 | ||
90 | error = process_trimming(email, &p->email, email + strlen(email), 1); | |
91 | if (error < GIT_SUCCESS) { | |
92 | git__rethrow(GIT_EINVALIDARGS, "Failed to create signature. 'email' argument is invalid"); | |
93 | goto cleanup; | |
94 | } | |
95 | ||
96 | p->when.time = time; | |
97 | p->when.offset = offset; | |
58519018 | 98 | |
63396a39 MS |
99 | *sig_out = p; |
100 | ||
101 | return error; | |
58519018 VM |
102 | |
103 | cleanup: | |
638c2ca4 | 104 | git_signature_free(p); |
63396a39 | 105 | return error; |
58519018 VM |
106 | } |
107 | ||
638c2ca4 | 108 | git_signature *git_signature_dup(const git_signature *sig) |
58519018 | 109 | { |
63396a39 MS |
110 | git_signature *new; |
111 | if (git_signature_new(&new, sig->name, sig->email, sig->when.time, sig->when.offset) < GIT_SUCCESS) | |
112 | return NULL; | |
113 | return new; | |
58519018 VM |
114 | } |
115 | ||
63396a39 | 116 | int git_signature_now(git_signature **sig_out, const char *name, const char *email) |
9e9e6ae1 | 117 | { |
63396a39 | 118 | int error; |
9e9e6ae1 | 119 | time_t now; |
53b7560b | 120 | time_t offset; |
14eb94ee | 121 | struct tm *utc_tm, *local_tm; |
63396a39 | 122 | git_signature *sig; |
9e9e6ae1 | 123 | |
14eb94ee VM |
124 | #ifndef GIT_WIN32 |
125 | struct tm _utc, _local; | |
126 | #endif | |
9e9e6ae1 | 127 | |
63396a39 MS |
128 | *sig_out = NULL; |
129 | ||
14eb94ee | 130 | time(&now); |
9e9e6ae1 | 131 | |
14eb94ee VM |
132 | /** |
133 | * On Win32, `gmtime_r` doesn't exist but | |
134 | * `gmtime` is threadsafe, so we can use that | |
135 | */ | |
136 | #ifdef GIT_WIN32 | |
137 | utc_tm = gmtime(&now); | |
138 | local_tm = localtime(&now); | |
139 | #else | |
140 | utc_tm = gmtime_r(&now, &_utc); | |
141 | local_tm = localtime_r(&now, &_local); | |
142 | #endif | |
143 | ||
144 | offset = mktime(local_tm) - mktime(utc_tm); | |
9e9e6ae1 | 145 | offset /= 60; |
14eb94ee | 146 | |
9e9e6ae1 | 147 | /* mktime takes care of setting tm_isdst correctly */ |
14eb94ee | 148 | if (local_tm->tm_isdst) |
9e9e6ae1 CMN |
149 | offset += 60; |
150 | ||
63396a39 MS |
151 | if ((error = git_signature_new(&sig, name, email, now, (int)offset)) < GIT_SUCCESS) |
152 | return error; | |
153 | ||
154 | *sig_out = sig; | |
155 | ||
156 | return error; | |
9e9e6ae1 | 157 | } |
58519018 | 158 | |
42a1b5e1 | 159 | static int parse_timezone_offset(const char *buffer, int *offset_out) |
13710f1e | 160 | { |
ad196c6a | 161 | int dec_offset; |
42a1b5e1 | 162 | int mins, hours, offset; |
13710f1e | 163 | |
c6e65aca VM |
164 | const char *offset_start; |
165 | const char *offset_end; | |
13710f1e | 166 | |
42a1b5e1 | 167 | offset_start = buffer; |
13710f1e | 168 | |
638c2ca4 | 169 | if (*offset_start == '\n') { |
fee065a0 | 170 | *offset_out = 0; |
171 | return GIT_SUCCESS; | |
172 | } | |
173 | ||
13710f1e | 174 | if (offset_start[0] != '-' && offset_start[0] != '+') |
5de24ec7 | 175 | return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. It doesn't start with '+' or '-'"); |
13710f1e | 176 | |
fbfc7580 | 177 | if (offset_start[1] < '0' || offset_start[1] > '9') |
fea400f8 | 178 | return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset."); |
fbfc7580 | 179 | |
c6e65aca | 180 | if (git__strtol32(&dec_offset, offset_start + 1, &offset_end, 10) < GIT_SUCCESS) |
5de24ec7 | 181 | return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. It isn't a number"); |
13710f1e | 182 | |
183 | if (offset_end - offset_start != 5) | |
5de24ec7 | 184 | return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Invalid length"); |
13710f1e | 185 | |
fbfc7580 DG |
186 | if (dec_offset > 1400) |
187 | return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Value too large"); | |
188 | ||
13710f1e | 189 | hours = dec_offset / 100; |
190 | mins = dec_offset % 100; | |
191 | ||
932d1baf | 192 | if (hours > 14) // see http://www.worldtimezone.com/faq.html |
fbfc7580 | 193 | return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Hour value too large"); |
13710f1e | 194 | |
638c2ca4 | 195 | if (mins > 59) |
5de24ec7 | 196 | return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Minute value too large"); |
13710f1e | 197 | |
198 | offset = (hours * 60) + mins; | |
199 | ||
200 | if (offset_start[0] == '-') | |
13710f1e | 201 | offset *= -1; |
932d1baf | 202 | |
13710f1e | 203 | *offset_out = offset; |
204 | ||
205 | return GIT_SUCCESS; | |
206 | } | |
207 | ||
d568d585 | 208 | static int process_next_token(const char **buffer_out, char **storage, |
a01acc47 | 209 | const char *token_end, const char *right_boundary) |
42a1b5e1 | 210 | { |
a01acc47 | 211 | int error = process_trimming(*buffer_out, storage, token_end, 0); |
212 | if (error < GIT_SUCCESS) | |
213 | return error; | |
42a1b5e1 | 214 | |
a01acc47 | 215 | *buffer_out = token_end + 1; |
42a1b5e1 | 216 | |
a01acc47 | 217 | if (*buffer_out > right_boundary) |
42a1b5e1 | 218 | return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Signature too short"); |
219 | ||
42a1b5e1 | 220 | return GIT_SUCCESS; |
221 | } | |
222 | ||
d568d585 | 223 | static const char *scan_for_previous_token(const char *buffer, const char *left_boundary) |
42a1b5e1 | 224 | { |
a01acc47 | 225 | const char *start; |
42a1b5e1 | 226 | |
a01acc47 | 227 | if (buffer <= left_boundary) |
42a1b5e1 | 228 | return NULL; |
229 | ||
a01acc47 | 230 | start = skip_trailing_spaces(left_boundary, buffer); |
42a1b5e1 | 231 | |
232 | /* Search for previous occurence of space */ | |
233 | while (start[-1] != ' ' && start > left_boundary) | |
234 | start--; | |
235 | ||
236 | return start; | |
237 | } | |
238 | ||
d568d585 | 239 | static int parse_time(git_time_t *time_out, const char *buffer) |
42a1b5e1 | 240 | { |
ad196c6a | 241 | int time; |
42a1b5e1 | 242 | int error; |
243 | ||
244 | if (*buffer == '+' || *buffer == '-') | |
245 | return git__throw(GIT_ERROR, "Failed while parsing time. '%s' rather look like a timezone offset.", buffer); | |
246 | ||
247 | error = git__strtol32(&time, buffer, &buffer, 10); | |
248 | ||
249 | if (error < GIT_SUCCESS) | |
250 | return error; | |
251 | ||
252 | *time_out = (git_time_t)time; | |
253 | ||
254 | return GIT_SUCCESS; | |
255 | } | |
13710f1e | 256 | |
720d5472 | 257 | int git_signature__parse(git_signature *sig, const char **buffer_out, |
7757be33 | 258 | const char *buffer_end, const char *header, char ender) |
58519018 | 259 | { |
720d5472 | 260 | const char *buffer = *buffer_out; |
42a1b5e1 | 261 | const char *line_end, *name_end, *email_end, *tz_start, *time_start; |
262 | int error = GIT_SUCCESS; | |
58519018 | 263 | |
638c2ca4 | 264 | memset(sig, 0x0, sizeof(git_signature)); |
58519018 | 265 | |
7757be33 | 266 | if ((line_end = memchr(buffer, ender, buffer_end - buffer)) == NULL) |
8b2c913a | 267 | return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. No newline given"); |
58519018 | 268 | |
8b2c913a MS |
269 | if (header) { |
270 | const size_t header_len = strlen(header); | |
58519018 | 271 | |
8b2c913a MS |
272 | if (memcmp(buffer, header, header_len) != 0) |
273 | return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Expected prefix '%s' doesn't match actual", header); | |
58519018 | 274 | |
8b2c913a MS |
275 | buffer += header_len; |
276 | } | |
58519018 | 277 | |
8b2c913a MS |
278 | if (buffer > line_end) |
279 | return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Signature too short"); | |
58519018 | 280 | |
8b2c913a MS |
281 | if ((name_end = strchr(buffer, '<')) == NULL) |
282 | return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Cannot find '<' in signature"); | |
fbfc7580 | 283 | |
6f2856f3 | 284 | if ((email_end = strchr(name_end, '>')) == NULL) |
8b2c913a | 285 | return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Cannot find '>' in signature"); |
58519018 | 286 | |
42a1b5e1 | 287 | if (email_end < name_end) |
288 | return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Malformed e-mail"); | |
076141a1 | 289 | |
42a1b5e1 | 290 | error = process_next_token(&buffer, &sig->name, name_end, line_end); |
291 | if (error < GIT_SUCCESS) | |
292 | return error; | |
58519018 | 293 | |
42a1b5e1 | 294 | error = process_next_token(&buffer, &sig->email, email_end, line_end); |
295 | if (error < GIT_SUCCESS) | |
296 | return error; | |
58519018 | 297 | |
42a1b5e1 | 298 | tz_start = scan_for_previous_token(line_end - 1, buffer); |
58519018 | 299 | |
42a1b5e1 | 300 | if (tz_start == NULL) |
301 | goto clean_exit; /* No timezone nor date */ | |
c6e65aca | 302 | |
42a1b5e1 | 303 | time_start = scan_for_previous_token(tz_start - 1, buffer); |
304 | if (time_start == NULL || parse_time(&sig->when.time, time_start) < GIT_SUCCESS) { | |
305 | /* The tz_start might point at the time */ | |
306 | parse_time(&sig->when.time, tz_start); | |
307 | goto clean_exit; | |
308 | } | |
932d1baf | 309 | |
42a1b5e1 | 310 | if (parse_timezone_offset(tz_start, &sig->when.offset) < GIT_SUCCESS) { |
311 | sig->when.time = 0; /* Bogus timezone, we reset the time */ | |
312 | } | |
13710f1e | 313 | |
42a1b5e1 | 314 | clean_exit: |
8b2c913a | 315 | *buffer_out = line_end + 1; |
6f02c3ba | 316 | return GIT_SUCCESS; |
58519018 VM |
317 | } |
318 | ||
afeecf4f VM |
319 | void git_signature__writebuf(git_buf *buf, const char *header, const git_signature *sig) |
320 | { | |
321 | int offset, hours, mins; | |
322 | char sign; | |
323 | ||
324 | offset = sig->when.offset; | |
325 | sign = (sig->when.offset < 0) ? '-' : '+'; | |
326 | ||
327 | if (offset < 0) | |
328 | offset = -offset; | |
329 | ||
330 | hours = offset / 60; | |
331 | mins = offset % 60; | |
332 | ||
333 | git_buf_printf(buf, "%s%s <%s> %u %c%02d%02d\n", | |
334 | header ? header : "", sig->name, sig->email, | |
335 | (unsigned)sig->when.time, sign, hours, mins); | |
336 | } | |
58519018 | 337 |