1 | // Copyright (C) 2002 Graydon Hoare <graydon@pobox.com>␊ |
2 | //␊ |
3 | // This program is made available under the GNU GPL version 2.0 or␊ |
4 | // greater. See the accompanying file COPYING for details.␊ |
5 | //␊ |
6 | // This program is distributed WITHOUT ANY WARRANTY; without even the␊ |
7 | // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR␊ |
8 | // PURPOSE.␊ |
9 | ␊ |
10 | #include "base.hh"␊ |
11 | #include <limits>␊ |
12 | #include <sstream>␊ |
13 | #include <vector>␊ |
14 | ␊ |
15 | #include <boost/shared_ptr.hpp>␊ |
16 | #include <boost/tuple/tuple.hpp>␊ |
17 | #include <boost/tuple/tuple_comparison.hpp>␊ |
18 | ␊ |
19 | #include "lexical_cast.hh"␊ |
20 | #include "app_state.hh"␊ |
21 | #include "cert.hh"␊ |
22 | #include "constants.hh"␊ |
23 | #include "interner.hh"␊ |
24 | #include "keys.hh"␊ |
25 | #include "netio.hh"␊ |
26 | #include "option.hh"␊ |
27 | #include "revision.hh"␊ |
28 | #include "sanity.hh"␊ |
29 | #include "simplestring_xform.hh"␊ |
30 | #include "transforms.hh"␊ |
31 | #include "ui.hh"␊ |
32 | ␊ |
33 | using std::make_pair;␊ |
34 | using std::map;␊ |
35 | using std::pair;␊ |
36 | using std::set;␊ |
37 | using std::string;␊ |
38 | using std::vector;␊ |
39 | using std::remove_if;␊ |
40 | ␊ |
41 | using boost::shared_ptr;␊ |
42 | using boost::get;␊ |
43 | using boost::tuple;␊ |
44 | using boost::lexical_cast;␊ |
45 | ␊ |
46 | // The alternaive is to #include "cert.hh" in vocab.*, which is even␊ |
47 | // uglier.␊ |
48 | ␊ |
49 | #include "vocab_macros.hh"␊ |
50 | cc_DECORATE(revision)␊ |
51 | cc_DECORATE(manifest)␊ |
52 | template <typename T>␊ |
53 | static inline void␊ |
54 | verify(T & val)␊ |
55 | {}␊ |
56 | template class revision<cert>;␊ |
57 | template class manifest<cert>;␊ |
58 | ␊ |
59 | // FIXME: the bogus-cert family of functions is ridiculous␊ |
60 | // and needs to be replaced, or at least factored.␊ |
61 | ␊ |
62 | struct␊ |
63 | bogus_cert_p␊ |
64 | {␊ |
65 | app_state & app;␊ |
66 | bogus_cert_p(app_state & a) : app(a) {};␊ |
67 | ␊ |
68 | bool cert_is_bogus(cert const & c) const␊ |
69 | {␊ |
70 | cert_status status = check_cert(app, c);␊ |
71 | if (status == cert_ok)␊ |
72 | {␊ |
73 | L(FL("cert ok"));␊ |
74 | return false;␊ |
75 | }␊ |
76 | else if (status == cert_bad)␊ |
77 | {␊ |
78 | string txt;␊ |
79 | cert_signable_text(c, txt);␊ |
80 | W(F("ignoring bad signature by '%s' on '%s'") % c.key() % txt);␊ |
81 | return true;␊ |
82 | }␊ |
83 | else␊ |
84 | {␊ |
85 | I(status == cert_unknown);␊ |
86 | string txt;␊ |
87 | cert_signable_text(c, txt);␊ |
88 | W(F("ignoring unknown signature by '%s' on '%s'") % c.key() % txt);␊ |
89 | return true;␊ |
90 | }␊ |
91 | }␊ |
92 | ␊ |
93 | bool operator()(revision<cert> const & c) const␊ |
94 | {␊ |
95 | return cert_is_bogus(c.inner());␊ |
96 | }␊ |
97 | ␊ |
98 | bool operator()(manifest<cert> const & c) const␊ |
99 | {␊ |
100 | return cert_is_bogus(c.inner());␊ |
101 | }␊ |
102 | };␊ |
103 | ␊ |
104 | ␊ |
105 | void␊ |
106 | erase_bogus_certs(vector< manifest<cert> > & certs,␊ |
107 | app_state & app)␊ |
108 | {␊ |
109 | typedef vector< manifest<cert> >::iterator it;␊ |
110 | it e = remove_if(certs.begin(), certs.end(), bogus_cert_p(app));␊ |
111 | certs.erase(e, certs.end());␊ |
112 | ␊ |
113 | vector< manifest<cert> > tmp_certs;␊ |
114 | ␊ |
115 | // Sorry, this is a crazy data structure␊ |
116 | typedef tuple< hexenc<id>, cert_name, base64<cert_value> > trust_key;␊ |
117 | typedef map< trust_key, ␊ |
118 | pair< shared_ptr< set<rsa_keypair_id> >, it > > trust_map;␊ |
119 | trust_map trust;␊ |
120 | ␊ |
121 | for (it i = certs.begin(); i != certs.end(); ++i)␊ |
122 | {␊ |
123 | trust_key key = trust_key(i->inner().ident, ␊ |
124 | i->inner().name, ␊ |
125 | i->inner().value);␊ |
126 | trust_map::iterator j = trust.find(key);␊ |
127 | shared_ptr< set<rsa_keypair_id> > s;␊ |
128 | if (j == trust.end())␊ |
129 | {␊ |
130 | s.reset(new set<rsa_keypair_id>());␊ |
131 | trust.insert(make_pair(key, make_pair(s, i)));␊ |
132 | }␊ |
133 | else␊ |
134 | s = j->second.first;␊ |
135 | s->insert(i->inner().key);␊ |
136 | }␊ |
137 | ␊ |
138 | for (trust_map::const_iterator i = trust.begin();␊ |
139 | i != trust.end(); ++i)␊ |
140 | {␊ |
141 | cert_value decoded_value;␊ |
142 | decode_base64(get<2>(i->first), decoded_value);␊ |
143 | if (app.lua.hook_get_manifest_cert_trust(*(i->second.first),␊ |
144 | get<0>(i->first),␊ |
145 | get<1>(i->first),␊ |
146 | decoded_value))␊ |
147 | {␊ |
148 | L(FL("trust function liked %d signers of %s cert on manifest %s")␊ |
149 | % i->second.first->size() % get<1>(i->first) % get<0>(i->first));␊ |
150 | tmp_certs.push_back(*(i->second.second));␊ |
151 | }␊ |
152 | else␊ |
153 | {␊ |
154 | W(F("trust function disliked %d signers of %s cert on manifest %s")␊ |
155 | % i->second.first->size() % get<1>(i->first) % get<0>(i->first));␊ |
156 | }␊ |
157 | }␊ |
158 | certs = tmp_certs;␊ |
159 | }␊ |
160 | ␊ |
161 | void␊ |
162 | erase_bogus_certs(vector< revision<cert> > & certs,␊ |
163 | app_state & app)␊ |
164 | {␊ |
165 | typedef vector< revision<cert> >::iterator it;␊ |
166 | it e = remove_if(certs.begin(), certs.end(), bogus_cert_p(app));␊ |
167 | certs.erase(e, certs.end());␊ |
168 | ␊ |
169 | vector< revision<cert> > tmp_certs;␊ |
170 | ␊ |
171 | // sorry, this is a crazy data structure␊ |
172 | typedef tuple< hexenc<id>, ␊ |
173 | cert_name, base64<cert_value> > trust_key;␊ |
174 | typedef map< trust_key, ␊ |
175 | pair< shared_ptr< set<rsa_keypair_id> >, it > > trust_map;␊ |
176 | trust_map trust;␊ |
177 | ␊ |
178 | for (it i = certs.begin(); i != certs.end(); ++i)␊ |
179 | {␊ |
180 | trust_key key = trust_key(i->inner().ident, ␊ |
181 | i->inner().name, ␊ |
182 | i->inner().value);␊ |
183 | trust_map::iterator j = trust.find(key);␊ |
184 | shared_ptr< set<rsa_keypair_id> > s;␊ |
185 | if (j == trust.end())␊ |
186 | {␊ |
187 | s.reset(new set<rsa_keypair_id>());␊ |
188 | trust.insert(make_pair(key, make_pair(s, i)));␊ |
189 | }␊ |
190 | else␊ |
191 | s = j->second.first;␊ |
192 | s->insert(i->inner().key);␊ |
193 | }␊ |
194 | ␊ |
195 | for (trust_map::const_iterator i = trust.begin();␊ |
196 | i != trust.end(); ++i)␊ |
197 | {␊ |
198 | cert_value decoded_value;␊ |
199 | decode_base64(get<2>(i->first), decoded_value);␊ |
200 | if (app.lua.hook_get_revision_cert_trust(*(i->second.first),␊ |
201 | get<0>(i->first),␊ |
202 | get<1>(i->first),␊ |
203 | decoded_value))␊ |
204 | {␊ |
205 | L(FL("trust function liked %d signers of %s cert on revision %s")␊ |
206 | % i->second.first->size() % get<1>(i->first) % get<0>(i->first));␊ |
207 | tmp_certs.push_back(*(i->second.second));␊ |
208 | }␊ |
209 | else␊ |
210 | {␊ |
211 | W(F("trust function disliked %d signers of %s cert on revision %s")␊ |
212 | % i->second.first->size() % get<1>(i->first) % get<0>(i->first));␊ |
213 | }␊ |
214 | }␊ |
215 | certs = tmp_certs;␊ |
216 | }␊ |
217 | ␊ |
218 | ␊ |
219 | // cert-managing routines␊ |
220 | ␊ |
221 | cert::cert()␊ |
222 | {}␊ |
223 | ␊ |
224 | cert::cert(std::string const & s)␊ |
225 | {␊ |
226 | read_cert(s, *this);␊ |
227 | }␊ |
228 | ␊ |
229 | cert::cert(hexenc<id> const & ident,␊ |
230 | cert_name const & name,␊ |
231 | base64<cert_value> const & value,␊ |
232 | rsa_keypair_id const & key)␊ |
233 | : ident(ident), name(name), value(value), key(key)␊ |
234 | {}␊ |
235 | ␊ |
236 | cert::cert(hexenc<id> const & ident,␊ |
237 | cert_name const & name,␊ |
238 | base64<cert_value> const & value,␊ |
239 | rsa_keypair_id const & key,␊ |
240 | base64<rsa_sha1_signature> const & sig)␊ |
241 | : ident(ident), name(name), value(value), key(key), sig(sig)␊ |
242 | {}␊ |
243 | ␊ |
244 | bool␊ |
245 | cert::operator<(cert const & other) const␊ |
246 | {␊ |
247 | return (ident < other.ident)␊ |
248 | || ((ident == other.ident) && name < other.name)␊ |
249 | || (((ident == other.ident) && name == other.name)␊ |
250 | && value < other.value)␊ |
251 | || ((((ident == other.ident) && name == other.name)␊ |
252 | && value == other.value) && key < other.key)␊ |
253 | || (((((ident == other.ident) && name == other.name)␊ |
254 | && value == other.value) && key == other.key) && sig < other.sig);␊ |
255 | }␊ |
256 | ␊ |
257 | bool␊ |
258 | cert::operator==(cert const & other) const␊ |
259 | {␊ |
260 | return␊ |
261 | (ident == other.ident)␊ |
262 | && (name == other.name)␊ |
263 | && (value == other.value)␊ |
264 | && (key == other.key)␊ |
265 | && (sig == other.sig);␊ |
266 | }␊ |
267 | ␊ |
268 | // netio support␊ |
269 | ␊ |
270 | void␊ |
271 | read_cert(string const & in, cert & t)␊ |
272 | {␊ |
273 | size_t pos = 0;␊ |
274 | id hash = id(extract_substring(in, pos,␊ |
275 | constants::merkle_hash_length_in_bytes,␊ |
276 | "cert hash"));␊ |
277 | id ident = id(extract_substring(in, pos,␊ |
278 | constants::merkle_hash_length_in_bytes,␊ |
279 | "cert ident"));␊ |
280 | string name, val, key, sig;␊ |
281 | extract_variable_length_string(in, name, pos, "cert name");␊ |
282 | extract_variable_length_string(in, val, pos, "cert val");␊ |
283 | extract_variable_length_string(in, key, pos, "cert key");␊ |
284 | extract_variable_length_string(in, sig, pos, "cert sig");␊ |
285 | assert_end_of_buffer(in, pos, "cert");␊ |
286 | ␊ |
287 | hexenc<id> hid;␊ |
288 | base64<cert_value> bval;␊ |
289 | base64<rsa_sha1_signature> bsig;␊ |
290 | ␊ |
291 | encode_hexenc(ident, hid);␊ |
292 | encode_base64(cert_value(val), bval);␊ |
293 | encode_base64(rsa_sha1_signature(sig), bsig);␊ |
294 | ␊ |
295 | cert tmp(hid, cert_name(name), bval, rsa_keypair_id(key), bsig);␊ |
296 | ␊ |
297 | hexenc<id> hcheck;␊ |
298 | id check;␊ |
299 | cert_hash_code(tmp, hcheck);␊ |
300 | decode_hexenc(hcheck, check);␊ |
301 | if (!(check == hash))␊ |
302 | {␊ |
303 | hexenc<id> hhash;␊ |
304 | encode_hexenc(hash, hhash);␊ |
305 | throw bad_decode(F("calculated cert hash '%s' does not match '%s'")␊ |
306 | % hcheck % hhash);␊ |
307 | }␊ |
308 | t = tmp;␊ |
309 | }␊ |
310 | ␊ |
311 | void␊ |
312 | write_cert(cert const & t, string & out)␊ |
313 | {␊ |
314 | string name, key;␊ |
315 | hexenc<id> hash;␊ |
316 | id ident_decoded, hash_decoded;␊ |
317 | rsa_sha1_signature sig_decoded;␊ |
318 | cert_value value_decoded;␊ |
319 | ␊ |
320 | cert_hash_code(t, hash);␊ |
321 | decode_base64(t.value, value_decoded);␊ |
322 | decode_base64(t.sig, sig_decoded);␊ |
323 | decode_hexenc(t.ident, ident_decoded);␊ |
324 | decode_hexenc(hash, hash_decoded);␊ |
325 | ␊ |
326 | out.append(hash_decoded());␊ |
327 | out.append(ident_decoded());␊ |
328 | insert_variable_length_string(t.name(), out);␊ |
329 | insert_variable_length_string(value_decoded(), out);␊ |
330 | insert_variable_length_string(t.key(), out);␊ |
331 | insert_variable_length_string(sig_decoded(), out);␊ |
332 | }␊ |
333 | ␊ |
334 | void␊ |
335 | cert_signable_text(cert const & t,␊ |
336 | string & out)␊ |
337 | {␊ |
338 | out = (FL("[%s@%s:%s]") % t.name % t.ident % remove_ws(t.value())).str();␊ |
339 | L(FL("cert: signable text %s") % out);␊ |
340 | }␊ |
341 | ␊ |
342 | void␊ |
343 | cert_hash_code(cert const & t, hexenc<id> & out)␊ |
344 | {␊ |
345 | string tmp;␊ |
346 | tmp.reserve(4+t.ident().size() + t.name().size() + t.value().size() +␊ |
347 | t.key().size() + t.sig().size());␊ |
348 | tmp.append(t.ident());␊ |
349 | tmp += ':';␊ |
350 | tmp.append(t.name());␊ |
351 | tmp += ':';␊ |
352 | append_without_ws(tmp,t.value());␊ |
353 | tmp += ':';␊ |
354 | tmp.append(t.key());␊ |
355 | tmp += ':';␊ |
356 | append_without_ws(tmp,t.sig());␊ |
357 | ␊ |
358 | data tdat(tmp);␊ |
359 | calculate_ident(tdat, out);␊ |
360 | }␊ |
361 | ␊ |
362 | bool␊ |
363 | priv_key_exists(app_state & app, rsa_keypair_id const & id)␊ |
364 | {␊ |
365 | ␊ |
366 | return app.keys.key_pair_exists(id);␊ |
367 | }␊ |
368 | ␊ |
369 | // Loads a key pair for a given key id, from either a lua hook␊ |
370 | // or the key store. This will bomb out if the same keyid exists␊ |
371 | // in both with differing contents.␊ |
372 | ␊ |
373 | void␊ |
374 | load_key_pair(app_state & app,␊ |
375 | rsa_keypair_id const & id,␊ |
376 | keypair & kp)␊ |
377 | {␊ |
378 | ␊ |
379 | static map<rsa_keypair_id, keypair> keys;␊ |
380 | bool persist_ok = (!keys.empty()) || app.lua.hook_persist_phrase_ok();␊ |
381 | ␊ |
382 | if (persist_ok && keys.find(id) != keys.end())␊ |
383 | {␊ |
384 | kp = keys[id];␊ |
385 | }␊ |
386 | else␊ |
387 | {␊ |
388 | N(app.keys.key_pair_exists(id),␊ |
389 | F("no key pair '%s' found in key store '%s'")␊ |
390 | % id % app.keys.get_key_dir());␊ |
391 | app.keys.get_key_pair(id, kp);␊ |
392 | if (persist_ok)␊ |
393 | keys.insert(make_pair(id, kp));␊ |
394 | }␊ |
395 | }␊ |
396 | ␊ |
397 | void␊ |
398 | calculate_cert(app_state & app, cert & t)␊ |
399 | {␊ |
400 | string signed_text;␊ |
401 | keypair kp;␊ |
402 | cert_signable_text(t, signed_text);␊ |
403 | ␊ |
404 | load_key_pair(app, t.key, kp);␊ |
405 | app.db.put_key(t.key, kp.pub);␊ |
406 | ␊ |
407 | make_signature(app, t.key, kp.priv, signed_text, t.sig);␊ |
408 | }␊ |
409 | ␊ |
410 | cert_status␊ |
411 | check_cert(app_state & app, cert const & t)␊ |
412 | {␊ |
413 | ␊ |
414 | base64< rsa_pub_key > pub;␊ |
415 | ␊ |
416 | static map<rsa_keypair_id, base64< rsa_pub_key > > pubkeys;␊ |
417 | bool persist_ok = (!pubkeys.empty()) || app.lua.hook_persist_phrase_ok();␊ |
418 | ␊ |
419 | if (persist_ok␊ |
420 | && pubkeys.find(t.key) != pubkeys.end())␊ |
421 | {␊ |
422 | pub = pubkeys[t.key];␊ |
423 | }␊ |
424 | else␊ |
425 | {␊ |
426 | if (!app.db.public_key_exists(t.key))␊ |
427 | return cert_unknown;␊ |
428 | app.db.get_key(t.key, pub);␊ |
429 | if (persist_ok)␊ |
430 | pubkeys.insert(make_pair(t.key, pub));␊ |
431 | }␊ |
432 | ␊ |
433 | string signed_text;␊ |
434 | cert_signable_text(t, signed_text);␊ |
435 | if (check_signature(app, t.key, pub, signed_text, t.sig))␊ |
436 | return cert_ok;␊ |
437 | else␊ |
438 | return cert_bad;␊ |
439 | }␊ |
440 | ␊ |
441 | ␊ |
442 | // "special certs"␊ |
443 | ␊ |
444 | void␊ |
445 | get_user_key(rsa_keypair_id & key, app_state & app)␊ |
446 | {␊ |
447 | ␊ |
448 | if (app.opts.signing_key() != "")␊ |
449 | {␊ |
450 | key = app.opts.signing_key;␊ |
451 | return;␊ |
452 | }␊ |
453 | ␊ |
454 | if (app.lua.hook_get_branch_key(app.opts.branchname, key))␊ |
455 | return;␊ |
456 | ␊ |
457 | vector<rsa_keypair_id> all_privkeys;␊ |
458 | app.keys.get_keys(all_privkeys);␊ |
459 | N(!all_privkeys.empty(), ␊ |
460 | F("you have no private key to make signatures with\n"␊ |
461 | "perhaps you need to 'genkey <your email>'"));␊ |
462 | N(all_privkeys.size() == 1,␊ |
463 | F("you have multiple private keys\n"␊ |
464 | "pick one to use for signatures by adding '-k<keyname>' to your command"));␊ |
465 | key = all_privkeys[0];␊ |
466 | }␊ |
467 | ␊ |
468 | // Guess which branch is appropriate for a commit below IDENT.␊ |
469 | // APP may override. Branch name is returned in BRANCHNAME.␊ |
470 | // Does not modify branch state in APP.␊ |
471 | void␊ |
472 | guess_branch(revision_id const & ident, app_state & app, branch_name & branchname)␊ |
473 | {␊ |
474 | if (app.opts.branch_given && !app.opts.branchname().empty())␊ |
475 | branchname = app.opts.branchname;␊ |
476 | else␊ |
477 | {␊ |
478 | N(!ident.inner()().empty(),␊ |
479 | F("no branch found for empty revision, "␊ |
480 | "please provide a branch name"));␊ |
481 | ␊ |
482 | set<branch_name> branches;␊ |
483 | app.get_project().get_revision_branches(ident, branches);␊ |
484 | ␊ |
485 | N(branches.size() != 0,␊ |
486 | F("no branch certs found for revision %s, "␊ |
487 | "please provide a branch name") % ident);␊ |
488 | ␊ |
489 | N(branches.size() == 1,␊ |
490 | F("multiple branch certs found for revision %s, "␊ |
491 | "please provide a branch name") % ident);␊ |
492 | ␊ |
493 | set<branch_name>::iterator i = branches.begin();␊ |
494 | I(i != branches.end());␊ |
495 | branchname = *i;␊ |
496 | }␊ |
497 | }␊ |
498 | ␊ |
499 | // As above, but set the branch name in the app state.␊ |
500 | void␊ |
501 | guess_branch(revision_id const & ident, app_state & app)␊ |
502 | {␊ |
503 | branch_name branchname;␊ |
504 | guess_branch(ident, app, branchname);␊ |
505 | app.opts.branchname = branchname;␊ |
506 | }␊ |
507 | ␊ |
508 | void␊ |
509 | make_simple_cert(hexenc<id> const & id,␊ |
510 | cert_name const & nm,␊ |
511 | cert_value const & cv,␊ |
512 | app_state & app,␊ |
513 | cert & c)␊ |
514 | {␊ |
515 | rsa_keypair_id key;␊ |
516 | get_user_key(key, app);␊ |
517 | base64<cert_value> encoded_val;␊ |
518 | encode_base64(cv, encoded_val);␊ |
519 | cert t(id, nm, encoded_val, key);␊ |
520 | calculate_cert(app, t);␊ |
521 | c = t;␊ |
522 | }␊ |
523 | ␊ |
524 | void␊ |
525 | put_simple_revision_cert(revision_id const & id,␊ |
526 | cert_name const & nm,␊ |
527 | cert_value const & val,␊ |
528 | app_state & app)␊ |
529 | {␊ |
530 | cert t;␊ |
531 | make_simple_cert(id.inner(), nm, val, app, t);␊ |
532 | revision<cert> cc(t);␊ |
533 | app.db.put_revision_cert(cc);␊ |
534 | }␊ |
535 | ␊ |
536 | void␊ |
537 | cert_revision_in_branch(revision_id const & rev,␊ |
538 | branch_name const & branch,␊ |
539 | app_state & app)␊ |
540 | {␊ |
541 | put_simple_revision_cert (rev, branch_cert_name, cert_value(branch()),␊ |
542 | app);␊ |
543 | }␊ |
544 | ␊ |
545 | ␊ |
546 | // "standard certs"␊ |
547 | ␊ |
548 | void␊ |
549 | cert_revision_date_time(revision_id const & m,␊ |
550 | date_t const & t,␊ |
551 | app_state & app)␊ |
552 | {␊ |
553 | cert_value val = cert_value(t.as_iso_8601_extended());␊ |
554 | put_simple_revision_cert(m, date_cert_name, val, app);␊ |
555 | }␊ |
556 | ␊ |
557 | void␊ |
558 | cert_revision_author(revision_id const & m,␊ |
559 | string const & author,␊ |
560 | app_state & app)␊ |
561 | {␊ |
562 | put_simple_revision_cert(m, author_cert_name, cert_value(author), app);␊ |
563 | }␊ |
564 | ␊ |
565 | void␊ |
566 | cert_revision_author_default(revision_id const & m,␊ |
567 | app_state & app)␊ |
568 | {␊ |
569 | string author;␊ |
570 | rsa_keypair_id key;␊ |
571 | get_user_key(key, app);␊ |
572 | ␊ |
573 | if (!app.lua.hook_get_author(app.opts.branchname, key, author))␊ |
574 | {␊ |
575 | author = key();␊ |
576 | }␊ |
577 | cert_revision_author(m, author, app);␊ |
578 | }␊ |
579 | ␊ |
580 | void␊ |
581 | cert_revision_tag(revision_id const & m,␊ |
582 | string const & tagname,␊ |
583 | app_state & app)␊ |
584 | {␊ |
585 | put_simple_revision_cert(m, tag_cert_name, cert_value(tagname), app);␊ |
586 | }␊ |
587 | ␊ |
588 | ␊ |
589 | void␊ |
590 | cert_revision_changelog(revision_id const & m,␊ |
591 | utf8 const & log,␊ |
592 | app_state & app)␊ |
593 | {␊ |
594 | put_simple_revision_cert(m, changelog_cert_name, cert_value(log()), app);␊ |
595 | }␊ |
596 | ␊ |
597 | void␊ |
598 | cert_revision_comment(revision_id const & m,␊ |
599 | utf8 const & comment,␊ |
600 | app_state & app)␊ |
601 | {␊ |
602 | put_simple_revision_cert(m, comment_cert_name, cert_value(comment()), app);␊ |
603 | }␊ |
604 | ␊ |
605 | void␊ |
606 | cert_revision_testresult(revision_id const & r,␊ |
607 | string const & results,␊ |
608 | app_state & app)␊ |
609 | {␊ |
610 | bool passed = false;␊ |
611 | if (lowercase(results) == "true" ||␊ |
612 | lowercase(results) == "yes" ||␊ |
613 | lowercase(results) == "pass" ||␊ |
614 | results == "1")␊ |
615 | passed = true;␊ |
616 | else if (lowercase(results) == "false" ||␊ |
617 | lowercase(results) == "no" ||␊ |
618 | lowercase(results) == "fail" ||␊ |
619 | results == "0")␊ |
620 | passed = false;␊ |
621 | else␊ |
622 | throw informative_failure("could not interpret test results, "␊ |
623 | "tried '0/1' 'yes/no', 'true/false', "␊ |
624 | "'pass/fail'");␊ |
625 | ␊ |
626 | put_simple_revision_cert(r, testresult_cert_name,␊ |
627 | cert_value(lexical_cast<string>(passed)), app);␊ |
628 | }␊ |
629 | ␊ |
630 | // Local Variables:␊ |
631 | // mode: C++␊ |
632 | // fill-column: 76␊ |
633 | // c-file-style: "gnu"␊ |
634 | // indent-tabs-mode: nil␊ |
635 | // End:␊ |
636 | // vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:␊ |