signature.c 5.86 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
3
 *
Vicent Marti committed
4 5
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
6 7 8
 */

#include "common.h"
9
#include "signature.h"
10
#include "repository.h"
11
#include "git2/common.h"
12
#include "posix.h"
13

14
void git_signature_free(git_signature *sig)
15
{
16
	if (sig == NULL)
17 18
		return;

19
	git__free(sig->name);
20
	sig->name = NULL;
21
	git__free(sig->email);
22
	sig->email = NULL;
23
	git__free(sig);
24 25
}

26 27
static int signature_error(const char *msg)
{
28
	giterr_set(GITERR_INVALID, "Failed to parse signature - %s", msg);
29 30 31
	return -1;
}

32
static bool contains_angle_brackets(const char *input)
33
{
34
	return strchr(input, '<') != NULL || strchr(input, '>') != NULL;
35 36
}

37
static char *extract_trimmed(const char *ptr, size_t len)
38
{
39
	while (len && git__isspace(ptr[0])) {
40 41
		ptr++; len--;
	}
42

43
	while (len && git__isspace(ptr[len - 1])) {
44 45 46 47
		len--;
	}

	return git__substrdup(ptr, len);
48 49
}

50
int git_signature_new(git_signature **sig_out, const char *name, const char *email, git_time_t time, int offset)
51
{
52
	git_signature *p = NULL;
53

54 55
	assert(name && email);

56 57
	*sig_out = NULL;

58 59 60 61 62 63
	if (contains_angle_brackets(name) ||
		contains_angle_brackets(email)) {
		return signature_error(
			"Neither `name` nor `email` should contain angle brackets chars.");
	}

64 65
	p = git__calloc(1, sizeof(git_signature));
	GITERR_CHECK_ALLOC(p);
66

67 68 69
	p->name = extract_trimmed(name, strlen(name));
	p->email = extract_trimmed(email, strlen(email));

70 71 72 73
	if (p->name == NULL || p->email == NULL)
		return -1; /* oom */

	if (p->name[0] == '\0') {
74
		git_signature_free(p);
75
		return signature_error("Signature cannot have an empty name");
76
	}
77

78 79
	p->when.time = time;
	p->when.offset = offset;
80

81
	*sig_out = p;
82
	return 0;
83 84
}

85
int git_signature_dup(git_signature **dest, const git_signature *source)
86
{
87
	git_signature *signature;
88

89 90 91 92 93 94 95 96
	if (source == NULL)
		return 0;

	signature = git__calloc(1, sizeof(git_signature));
	GITERR_CHECK_ALLOC(signature);

	signature->name = git__strdup(source->name);
	GITERR_CHECK_ALLOC(signature->name);
97

98 99
	signature->email = git__strdup(source->email);
	GITERR_CHECK_ALLOC(signature->email);
100

101 102
	signature->when.time = source->when.time;
	signature->when.offset = source->when.offset;
103

104 105 106
	*dest = signature;

	return 0;
107 108
}

109
int git_signature_now(git_signature **sig_out, const char *name, const char *email)
110 111
{
	time_t now;
112
	time_t offset;
113
	struct tm *utc_tm;
114
	git_signature *sig;
115
	struct tm _utc;
116

117 118
	*sig_out = NULL;

119 120 121 122 123 124 125 126
	/*
	 * Get the current time as seconds since the epoch and
	 * transform that into a tm struct containing the time at
	 * UTC. Give that to mktime which considers it a local time
	 * (tm_isdst = -1 asks it to take DST into account) and gives
	 * us that time as seconds since the epoch. The difference
	 * between its return value and 'now' is our offset to UTC.
	 */
127
	time(&now);
128
	utc_tm = p_gmtime_r(&now, &_utc);
129
	utc_tm->tm_isdst = -1;
130
	offset = (time_t)difftime(now, mktime(utc_tm));
131
	offset /= 60;
132

133 134
	if (git_signature_new(&sig, name, email, now, (int)offset) < 0)
		return -1;
135 136 137

	*sig_out = sig;

138 139 140
	return 0;
}

141 142 143
int git_signature_default(git_signature **out, git_repository *repo)
{
	int error;
144
	git_config *cfg;
145 146
	const char *user_name, *user_email;

147
	if ((error = git_repository_config_snapshot(&cfg, repo)) < 0)
148 149 150 151 152 153 154 155 156 157
		return error;

	if (!(error = git_config_get_string(&user_name, cfg, "user.name")) &&
		!(error = git_config_get_string(&user_email, cfg, "user.email")))
		error = git_signature_now(out, user_name, user_email);

	git_config_free(cfg);
	return error;
}

158
int git_signature__parse(git_signature *sig, const char **buffer_out,
159
		const char *buffer_end, const char *header, char ender)
160
{
161
	const char *buffer = *buffer_out;
162
	const char *email_start, *email_end;
163

164
	memset(sig, 0, sizeof(git_signature));
165

166
	if ((buffer_end = memchr(buffer, ender, buffer_end - buffer)) == NULL)
167
		return signature_error("no newline given");
168

169 170
	if (header) {
		const size_t header_len = strlen(header);
171

172
		if (buffer + header_len >= buffer_end || memcmp(buffer, header, header_len) != 0)
173
			return signature_error("expected prefix doesn't match actual");
174

175 176
		buffer += header_len;
	}
177

178 179
	email_start = git__memrchr(buffer, '<', buffer_end - buffer);
	email_end = git__memrchr(buffer, '>', buffer_end - buffer);
180

181
	if (!email_start || !email_end || email_end <= email_start)
182
		return signature_error("malformed e-mail");
183

184
	email_start += 1;
Vicent Marti committed
185
	sig->name = extract_trimmed(buffer, email_start - buffer - 1);
186
	sig->email = extract_trimmed(email_start, email_end - email_start);
187

188 189 190 191
	/* Do we even have a time at the end of the signature? */
	if (email_end + 2 < buffer_end) {
		const char *time_start = email_end + 2;
		const char *time_end;
192

193 194
		if (git__strtol64(&sig->when.time, time_start, &time_end, 10) < 0)
			return signature_error("invalid Unix timestamp");
195

Vicent Marti committed
196
		/* do we have a timezone? */
197 198 199 200 201 202
		if (time_end + 1 < buffer_end) {
			int offset, hours, mins;
			const char *tz_start, *tz_end;

			tz_start = time_end + 1;

nulltoken committed
203
			if ((tz_start[0] != '-' && tz_start[0] != '+') ||
204 205 206 207
				git__strtol32(&offset, tz_start + 1, &tz_end, 10) < 0) {
				//malformed timezone, just assume it's zero
				offset = 0;
			}
208 209 210

			hours = offset / 100;
			mins = offset % 100;
211

212 213 214 215 216 217 218 219 220 221
			/*
			 * only store timezone if it's not overflowing;
			 * see http://www.worldtimezone.com/faq.html
			 */
			if (hours < 14 && mins < 59) {
				sig->when.offset = (hours * 60) + mins;
				if (tz_start[0] == '-')
					sig->when.offset = -sig->when.offset;
			}
		}
222
	}
223

224
	*buffer_out = buffer_end + 1;
225
	return 0;
226 227
}

228 229 230 231 232
void git_signature__writebuf(git_buf *buf, const char *header, const git_signature *sig)
{
	int offset, hours, mins;
	char sign;

233 234
	assert(buf && sig);

235 236 237 238 239 240 241 242 243 244 245 246 247
	offset = sig->when.offset;
	sign = (sig->when.offset < 0) ? '-' : '+';

	if (offset < 0)
		offset = -offset;

	hours = offset / 60;
	mins = offset % 60;

	git_buf_printf(buf, "%s%s <%s> %u %c%02d%02d\n",
			header ? header : "", sig->name, sig->email,
			(unsigned)sig->when.time, sign, hours, mins);
}
248