blob: ab38b9dc36f83cebf0585a7584dd88939b8c0921 [file] [log] [blame]
Junio C Hamano0df92712011-12-21 22:30:441<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
2 "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
3<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
4<head>
5<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
6<meta name="generator" content="AsciiDoc 8.5.2" />
7<title>credentials API</title>
8<style type="text/css">
9/* Debug borders */
10p, li, dt, dd, div, pre, h1, h2, h3, h4, h5, h6 {
11/*
12 border: 1px solid red;
13*/
14}
15
16body {
17 margin: 1em 5% 1em 5%;
18}
19
20a {
21 color: blue;
22 text-decoration: underline;
23}
24a:visited {
25 color: fuchsia;
26}
27
28em {
29 font-style: italic;
30 color: navy;
31}
32
33strong {
34 font-weight: bold;
35 color: #083194;
36}
37
38tt {
39 color: navy;
40}
41
42h1, h2, h3, h4, h5, h6 {
43 color: #527bbd;
44 font-family: sans-serif;
45 margin-top: 1.2em;
46 margin-bottom: 0.5em;
47 line-height: 1.3;
48}
49
50h1, h2, h3 {
51 border-bottom: 2px solid silver;
52}
53h2 {
54 padding-top: 0.5em;
55}
56h3 {
57 float: left;
58}
59h3 + * {
60 clear: left;
61}
62
63div.sectionbody {
64 font-family: serif;
65 margin-left: 0;
66}
67
68hr {
69 border: 1px solid silver;
70}
71
72p {
73 margin-top: 0.5em;
74 margin-bottom: 0.5em;
75}
76
77ul, ol, li > p {
78 margin-top: 0;
79}
80
81pre {
82 padding: 0;
83 margin: 0;
84}
85
86span#author {
87 color: #527bbd;
88 font-family: sans-serif;
89 font-weight: bold;
90 font-size: 1.1em;
91}
92span#email {
93}
94span#revnumber, span#revdate, span#revremark {
95 font-family: sans-serif;
96}
97
98div#footer {
99 font-family: sans-serif;
100 font-size: small;
101 border-top: 2px solid silver;
102 padding-top: 0.5em;
103 margin-top: 4.0em;
104}
105div#footer-text {
106 float: left;
107 padding-bottom: 0.5em;
108}
109div#footer-badges {
110 float: right;
111 padding-bottom: 0.5em;
112}
113
114div#preamble {
115 margin-top: 1.5em;
116 margin-bottom: 1.5em;
117}
118div.tableblock, div.imageblock, div.exampleblock, div.verseblock,
119div.quoteblock, div.literalblock, div.listingblock, div.sidebarblock,
120div.admonitionblock {
121 margin-top: 1.0em;
122 margin-bottom: 1.5em;
123}
124div.admonitionblock {
125 margin-top: 2.0em;
126 margin-bottom: 2.0em;
127 margin-right: 10%;
128 color: #606060;
129}
130
131div.content { /* Block element content. */
132 padding: 0;
133}
134
135/* Block element titles. */
136div.title, caption.title {
137 color: #527bbd;
138 font-family: sans-serif;
139 font-weight: bold;
140 text-align: left;
141 margin-top: 1.0em;
142 margin-bottom: 0.5em;
143}
144div.title + * {
145 margin-top: 0;
146}
147
148td div.title:first-child {
149 margin-top: 0.0em;
150}
151div.content div.title:first-child {
152 margin-top: 0.0em;
153}
154div.content + div.title {
155 margin-top: 0.0em;
156}
157
158div.sidebarblock > div.content {
159 background: #ffffee;
160 border: 1px solid silver;
161 padding: 0.5em;
162}
163
164div.listingblock > div.content {
165 border: 1px solid silver;
166 background: #f4f4f4;
167 padding: 0.5em;
168}
169
170div.quoteblock, div.verseblock {
171 padding-left: 1.0em;
172 margin-left: 1.0em;
173 margin-right: 10%;
174 border-left: 5px solid #dddddd;
175 color: #777777;
176}
177
178div.quoteblock > div.attribution {
179 padding-top: 0.5em;
180 text-align: right;
181}
182
183div.verseblock > div.content {
184 white-space: pre;
185}
186div.verseblock > div.attribution {
187 padding-top: 0.75em;
188 text-align: left;
189}
190/* DEPRECATED: Pre version 8.2.7 verse style literal block. */
191div.verseblock + div.attribution {
192 text-align: left;
193}
194
195div.admonitionblock .icon {
196 vertical-align: top;
197 font-size: 1.1em;
198 font-weight: bold;
199 text-decoration: underline;
200 color: #527bbd;
201 padding-right: 0.5em;
202}
203div.admonitionblock td.content {
204 padding-left: 0.5em;
205 border-left: 3px solid #dddddd;
206}
207
208div.exampleblock > div.content {
209 border-left: 3px solid #dddddd;
210 padding-left: 0.5em;
211}
212
213div.imageblock div.content { padding-left: 0; }
214span.image img { border-style: none; }
215a.image:visited { color: white; }
216
217dl {
218 margin-top: 0.8em;
219 margin-bottom: 0.8em;
220}
221dt {
222 margin-top: 0.5em;
223 margin-bottom: 0;
224 font-style: normal;
225 color: navy;
226}
227dd > *:first-child {
228 margin-top: 0.1em;
229}
230
231ul, ol {
232 list-style-position: outside;
233}
234ol.arabic {
235 list-style-type: decimal;
236}
237ol.loweralpha {
238 list-style-type: lower-alpha;
239}
240ol.upperalpha {
241 list-style-type: upper-alpha;
242}
243ol.lowerroman {
244 list-style-type: lower-roman;
245}
246ol.upperroman {
247 list-style-type: upper-roman;
248}
249
250div.compact ul, div.compact ol,
251div.compact p, div.compact p,
252div.compact div, div.compact div {
253 margin-top: 0.1em;
254 margin-bottom: 0.1em;
255}
256
257div.tableblock > table {
258 border: 3px solid #527bbd;
259}
260thead, p.table.header {
261 font-family: sans-serif;
262 font-weight: bold;
263}
264tfoot {
265 font-weight: bold;
266}
267td > div.verse {
268 white-space: pre;
269}
270p.table {
271 margin-top: 0;
272}
273/* Because the table frame attribute is overriden by CSS in most browsers. */
274div.tableblock > table[frame="void"] {
275 border-style: none;
276}
277div.tableblock > table[frame="hsides"] {
278 border-left-style: none;
279 border-right-style: none;
280}
281div.tableblock > table[frame="vsides"] {
282 border-top-style: none;
283 border-bottom-style: none;
284}
285
286
287div.hdlist {
288 margin-top: 0.8em;
289 margin-bottom: 0.8em;
290}
291div.hdlist tr {
292 padding-bottom: 15px;
293}
294dt.hdlist1.strong, td.hdlist1.strong {
295 font-weight: bold;
296}
297td.hdlist1 {
298 vertical-align: top;
299 font-style: normal;
300 padding-right: 0.8em;
301 color: navy;
302}
303td.hdlist2 {
304 vertical-align: top;
305}
306div.hdlist.compact tr {
307 margin: 0;
308 padding-bottom: 0;
309}
310
311.comment {
312 background: yellow;
313}
314
315.footnote, .footnoteref {
316 font-size: 0.8em;
317}
318
319span.footnote, span.footnoteref {
320 vertical-align: super;
321}
322
323#footnotes {
324 margin: 20px 0 20px 0;
325 padding: 7px 0 0 0;
326}
327
328#footnotes div.footnote {
329 margin: 0 0 5px 0;
330}
331
332#footnotes hr {
333 border: none;
334 border-top: 1px solid silver;
335 height: 1px;
336 text-align: left;
337 margin-left: 0;
338 width: 20%;
339 min-width: 100px;
340}
341
342
343@media print {
344 div#footer-badges { display: none; }
345}
346
347div#toc {
348 margin-bottom: 2.5em;
349}
350
351div#toctitle {
352 color: #527bbd;
353 font-family: sans-serif;
354 font-size: 1.1em;
355 font-weight: bold;
356 margin-top: 1.0em;
357 margin-bottom: 0.1em;
358}
359
360div.toclevel1, div.toclevel2, div.toclevel3, div.toclevel4 {
361 margin-top: 0;
362 margin-bottom: 0;
363}
364div.toclevel2 {
365 margin-left: 2em;
366 font-size: 0.9em;
367}
368div.toclevel3 {
369 margin-left: 4em;
370 font-size: 0.9em;
371}
372div.toclevel4 {
373 margin-left: 6em;
374 font-size: 0.9em;
375}
376/* Workarounds for IE6's broken and incomplete CSS2. */
377
378div.sidebar-content {
379 background: #ffffee;
380 border: 1px solid silver;
381 padding: 0.5em;
382}
383div.sidebar-title, div.image-title {
384 color: #527bbd;
385 font-family: sans-serif;
386 font-weight: bold;
387 margin-top: 0.0em;
388 margin-bottom: 0.5em;
389}
390
391div.listingblock div.content {
392 border: 1px solid silver;
393 background: #f4f4f4;
394 padding: 0.5em;
395}
396
397div.quoteblock-attribution {
398 padding-top: 0.5em;
399 text-align: right;
400}
401
402div.verseblock-content {
403 white-space: pre;
404}
405div.verseblock-attribution {
406 padding-top: 0.75em;
407 text-align: left;
408}
409
410div.exampleblock-content {
411 border-left: 3px solid #dddddd;
412 padding-left: 0.5em;
413}
414
415/* IE6 sets dynamically generated links as visited. */
416div#toc a:visited { color: blue; }
417</style>
418<script type="text/javascript">
419/*<![CDATA[*/
420window.onload = function(){asciidoc.footnotes();}
421var asciidoc = { // Namespace.
422
423/////////////////////////////////////////////////////////////////////
424// Table Of Contents generator
425/////////////////////////////////////////////////////////////////////
426
427/* Author: Mihai Bazon, September 2002
428 * http://students.infoiasi.ro/~mishoo
429 *
430 * Table Of Content generator
431 * Version: 0.4
432 *
433 * Feel free to use this script under the terms of the GNU General Public
434 * License, as long as you do not remove or alter this notice.
435 */
436
437 /* modified by Troy D. Hanson, September 2006. License: GPL */
438 /* modified by Stuart Rackham, 2006, 2009. License: GPL */
439
440// toclevels = 1..4.
441toc: function (toclevels) {
442
443 function getText(el) {
444 var text = "";
445 for (var i = el.firstChild; i != null; i = i.nextSibling) {
446 if (i.nodeType == 3 /* Node.TEXT_NODE */) // IE doesn't speak constants.
447 text += i.data;
448 else if (i.firstChild != null)
449 text += getText(i);
450 }
451 return text;
452 }
453
454 function TocEntry(el, text, toclevel) {
455 this.element = el;
456 this.text = text;
457 this.toclevel = toclevel;
458 }
459
460 function tocEntries(el, toclevels) {
461 var result = new Array;
462 var re = new RegExp('[hH]([2-'+(toclevels+1)+'])');
463 // Function that scans the DOM tree for header elements (the DOM2
464 // nodeIterator API would be a better technique but not supported by all
465 // browsers).
466 var iterate = function (el) {
467 for (var i = el.firstChild; i != null; i = i.nextSibling) {
468 if (i.nodeType == 1 /* Node.ELEMENT_NODE */) {
469 var mo = re.exec(i.tagName);
470 if (mo && (i.getAttribute("class") || i.getAttribute("className")) != "float") {
471 result[result.length] = new TocEntry(i, getText(i), mo[1]-1);
472 }
473 iterate(i);
474 }
475 }
476 }
477 iterate(el);
478 return result;
479 }
480
481 var toc = document.getElementById("toc");
482 var entries = tocEntries(document.getElementById("content"), toclevels);
483 for (var i = 0; i < entries.length; ++i) {
484 var entry = entries[i];
485 if (entry.element.id == "")
486 entry.element.id = "_toc_" + i;
487 var a = document.createElement("a");
488 a.href = "#" + entry.element.id;
489 a.appendChild(document.createTextNode(entry.text));
490 var div = document.createElement("div");
491 div.appendChild(a);
492 div.className = "toclevel" + entry.toclevel;
493 toc.appendChild(div);
494 }
495 if (entries.length == 0)
496 toc.parentNode.removeChild(toc);
497},
498
499
500/////////////////////////////////////////////////////////////////////
501// Footnotes generator
502/////////////////////////////////////////////////////////////////////
503
504/* Based on footnote generation code from:
505 * http://www.brandspankingnew.net/archive/2005/07/format_footnote.html
506 */
507
508footnotes: function () {
509 var cont = document.getElementById("content");
510 var noteholder = document.getElementById("footnotes");
511 var spans = cont.getElementsByTagName("span");
512 var refs = {};
513 var n = 0;
514 for (i=0; i<spans.length; i++) {
515 if (spans[i].className == "footnote") {
516 n++;
517 // Use [\s\S] in place of . so multi-line matches work.
518 // Because JavaScript has no s (dotall) regex flag.
519 note = spans[i].innerHTML.match(/\s*\[([\s\S]*)]\s*/)[1];
520 noteholder.innerHTML +=
521 "<div class='footnote' id='_footnote_" + n + "'>" +
522 "<a href='#_footnoteref_" + n + "' title='Return to text'>" +
523 n + "</a>. " + note + "</div>";
524 spans[i].innerHTML =
525 "[<a id='_footnoteref_" + n + "' href='#_footnote_" + n +
526 "' title='View footnote' class='footnote'>" + n + "</a>]";
527 var id =spans[i].getAttribute("id");
528 if (id != null) refs["#"+id] = n;
529 }
530 }
531 if (n == 0)
532 noteholder.parentNode.removeChild(noteholder);
533 else {
534 // Process footnoterefs.
535 for (i=0; i<spans.length; i++) {
536 if (spans[i].className == "footnoteref") {
537 var href = spans[i].getElementsByTagName("a")[0].getAttribute("href");
538 href = href.match(/#.*/)[0]; // Because IE return full URL.
539 n = refs[href];
540 spans[i].innerHTML =
541 "[<a href='#_footnote_" + n +
542 "' title='View footnote' class='footnote'>" + n + "</a>]";
543 }
544 }
545 }
546}
547
548}
549/*]]>*/
550</script>
551</head>
552<body>
553<div id="header">
554<h1>credentials API</h1>
555</div>
556<div id="content">
557<div id="preamble">
558<div class="sectionbody">
559<div class="paragraph"><p>The credentials API provides an abstracted way of gathering username and
560password credentials from the user (even though credentials in the wider
561world can take many forms, in this document the word "credential" always
562refers to a username and password pair).</p></div>
563</div>
564</div>
565<h2 id="_data_structures">Data Structures</h2>
566<div class="sectionbody">
567<div class="dlist"><dl>
568<dt class="hdlist1">
569<tt>struct credential</tt>
570</dt>
571<dd>
572<p>
573 This struct represents a single username/password combination
574 along with any associated context. All string fields should be
575 heap-allocated (or NULL if they are not known or not applicable).
576 The meaning of the individual context fields is the same as
577 their counterparts in the helper protocol; see the section below
578 for a description of each field.
579</p>
580<div class="paragraph"><p>The <tt>helpers</tt> member of the struct is a <tt>string_list</tt> of helpers. Each
581string specifies an external helper which will be run, in order, to
582either acquire or store credentials. See the section on credential
583helpers below.</p></div>
584<div class="paragraph"><p>This struct should always be initialized with <tt>CREDENTIAL_INIT</tt> or
585<tt>credential_init</tt>.</p></div>
586</dd>
587</dl></div>
588</div>
589<h2 id="_functions">Functions</h2>
590<div class="sectionbody">
591<div class="dlist"><dl>
592<dt class="hdlist1">
593<tt>credential_init</tt>
594</dt>
595<dd>
596<p>
597 Initialize a credential structure, setting all fields to empty.
598</p>
599</dd>
600<dt class="hdlist1">
601<tt>credential_clear</tt>
602</dt>
603<dd>
604<p>
605 Free any resources associated with the credential structure,
606 returning it to a pristine initialized state.
607</p>
608</dd>
609<dt class="hdlist1">
610<tt>credential_fill</tt>
611</dt>
612<dd>
613<p>
614 Instruct the credential subsystem to fill the username and
615 password fields of the passed credential struct by first
616 consulting helpers, then asking the user. After this function
617 returns, the username and password fields of the credential are
618 guaranteed to be non-NULL. If an error occurs, the function will
619 die().
620</p>
621</dd>
622<dt class="hdlist1">
623<tt>credential_reject</tt>
624</dt>
625<dd>
626<p>
627 Inform the credential subsystem that the provided credentials
628 have been rejected. This will cause the credential subsystem to
629 notify any helpers of the rejection (which allows them, for
630 example, to purge the invalid credentials from storage). It
631 will also free() the username and password fields of the
632 credential and set them to NULL (readying the credential for
633 another call to <tt>credential_fill</tt>). Any errors from helpers are
634 ignored.
635</p>
636</dd>
637<dt class="hdlist1">
638<tt>credential_approve</tt>
639</dt>
640<dd>
641<p>
642 Inform the credential subsystem that the provided credentials
643 were successfully used for authentication. This will cause the
644 credential subsystem to notify any helpers of the approval, so
645 that they may store the result to be used again. Any errors
646 from helpers are ignored.
647</p>
648</dd>
649<dt class="hdlist1">
650<tt>credential_from_url</tt>
651</dt>
652<dd>
653<p>
654 Parse a URL into broken-down credential fields.
655</p>
656</dd>
657</dl></div>
658</div>
659<h2 id="_example">Example</h2>
660<div class="sectionbody">
661<div class="paragraph"><p>The example below shows how the functions of the credential API could be
662used to login to a fictitious "foo" service on a remote host:</p></div>
663<div class="listingblock">
664<div class="content">
665<pre><tt>int foo_login(struct foo_connection *f)
666{
667 int status;
668 /*
669 * Create a credential with some context; we don't yet know the
670 * username or password.
671 */
672
673 struct credential c = CREDENTIAL_INIT;
674 c.protocol = xstrdup("foo");
675 c.host = xstrdup(f-&gt;hostname);
676
677 /*
678 * Fill in the username and password fields by contacting
679 * helpers and/or asking the user. The function will die if it
680 * fails.
681 */
682 credential_fill(&amp;c);
683
684 /*
685 * Otherwise, we have a username and password. Try to use it.
686 */
687 status = send_foo_login(f, c.username, c.password);
688 switch (status) {
689 case FOO_OK:
690 /* It worked. Store the credential for later use. */
691 credential_accept(&amp;c);
692 break;
693 case FOO_BAD_LOGIN:
694 /* Erase the credential from storage so we don't try it
695 * again. */
696 credential_reject(&amp;c);
697 break;
698 default:
699 /*
700 * Some other error occured. We don't know if the
701 * credential is good or bad, so report nothing to the
702 * credential subsystem.
703 */
704 }
705
706 /* Free any associated resources. */
707 credential_clear(&amp;c);
708
709 return status;
710}</tt></pre>
711</div></div>
712</div>
713<h2 id="_credential_helpers">Credential Helpers</h2>
714<div class="sectionbody">
715<div class="paragraph"><p>Credential helpers are programs executed by git to fetch or save
716credentials from and to long-term storage (where "long-term" is simply
717longer than a single git process; e.g., credentials may be stored
718in-memory for a few minutes, or indefinitely on disk).</p></div>
719<div class="paragraph"><p>Each helper is specified by a single string. The string is transformed
720by git into a command to be executed using these rules:</p></div>
721<div class="olist arabic"><ol class="arabic">
722<li>
723<p>
724If the helper string begins with "!", it is considered a shell
725 snippet, and everything after the "!" becomes the command.
726</p>
727</li>
728<li>
729<p>
730Otherwise, if the helper string begins with an absolute path, the
731 verbatim helper string becomes the command.
732</p>
733</li>
734<li>
735<p>
736Otherwise, the string "git credential-" is prepended to the helper
737 string, and the result becomes the command.
738</p>
739</li>
740</ol></div>
741<div class="paragraph"><p>The resulting command then has an "operation" argument appended to it
742(see below for details), and the result is executed by the shell.</p></div>
743<div class="paragraph"><p>Here are some example specifications:</p></div>
744<div class="listingblock">
745<div class="content">
746<pre><tt># run "git credential-foo"
747foo
748
749# same as above, but pass an argument to the helper
750foo --bar=baz
751
752# the arguments are parsed by the shell, so use shell
753# quoting if necessary
754foo --bar="whitespace arg"
755
756# you can also use an absolute path, which will not use the git wrapper
757/path/to/my/helper --with-arguments
758
759# or you can specify your own shell snippet
760!f() { echo "password=`cat $HOME/.secret`"; }; f</tt></pre>
761</div></div>
762<div class="paragraph"><p>Generally speaking, rule (3) above is the simplest for users to specify.
763Authors of credential helpers should make an effort to assist their
764users by naming their program "git-credential-$NAME", and putting it in
765the $PATH or $GIT_EXEC_PATH during installation, which will allow a user
766to enable it with <tt>git config credential.helper $NAME</tt>.</p></div>
767<div class="paragraph"><p>When a helper is executed, it will have one "operation" argument
768appended to its command line, which is one of:</p></div>
769<div class="dlist"><dl>
770<dt class="hdlist1">
771<tt>get</tt>
772</dt>
773<dd>
774<p>
775 Return a matching credential, if any exists.
776</p>
777</dd>
778<dt class="hdlist1">
779<tt>store</tt>
780</dt>
781<dd>
782<p>
783 Store the credential, if applicable to the helper.
784</p>
785</dd>
786<dt class="hdlist1">
787<tt>erase</tt>
788</dt>
789<dd>
790<p>
791 Remove a matching credential, if any, from the helper&#8217;s storage.
792</p>
793</dd>
794</dl></div>
795<div class="paragraph"><p>The details of the credential will be provided on the helper&#8217;s stdin
796stream. The credential is split into a set of named attributes.
797Attributes are provided to the helper, one per line. Each attribute is
798specified by a key-value pair, separated by an <tt>=</tt> (equals) sign,
799followed by a newline. The key may contain any bytes except <tt>=</tt>,
800newline, or NUL. The value may contain any bytes except newline or NUL.
801In both cases, all bytes are treated as-is (i.e., there is no quoting,
802and one cannot transmit a value with newline or NUL in it). The list of
803attributes is terminated by a blank line or end-of-file.</p></div>
804<div class="paragraph"><p>Git will send the following attributes (but may not send all of
805them for a given credential; for example, a <tt>host</tt> attribute makes no
806sense when dealing with a non-network protocol):</p></div>
807<div class="dlist"><dl>
808<dt class="hdlist1">
809<tt>protocol</tt>
810</dt>
811<dd>
812<p>
813 The protocol over which the credential will be used (e.g.,
814 <tt>https</tt>).
815</p>
816</dd>
817<dt class="hdlist1">
818<tt>host</tt>
819</dt>
820<dd>
821<p>
822 The remote hostname for a network credential.
823</p>
824</dd>
825<dt class="hdlist1">
826<tt>path</tt>
827</dt>
828<dd>
829<p>
830 The path with which the credential will be used. E.g., for
831 accessing a remote https repository, this will be the
832 repository&#8217;s path on the server.
833</p>
834</dd>
835<dt class="hdlist1">
836<tt>username</tt>
837</dt>
838<dd>
839<p>
840 The credential&#8217;s username, if we already have one (e.g., from a
841 URL, from the user, or from a previously run helper).
842</p>
843</dd>
844<dt class="hdlist1">
845<tt>password</tt>
846</dt>
847<dd>
848<p>
849 The credential&#8217;s password, if we are asking it to be stored.
850</p>
851</dd>
852</dl></div>
853<div class="paragraph"><p>For a <tt>get</tt> operation, the helper should produce a list of attributes
854on stdout in the same format. A helper is free to produce a subset, or
855even no values at all if it has nothing useful to provide. Any provided
856attributes will overwrite those already known about by git.</p></div>
857<div class="paragraph"><p>For a <tt>store</tt> or <tt>erase</tt> operation, the helper&#8217;s output is ignored.
858If it fails to perform the requested operation, it may complain to
859stderr to inform the user. If it does not support the requested
860operation (e.g., a read-only store), it should silently ignore the
861request.</p></div>
862<div class="paragraph"><p>If a helper receives any other operation, it should silently ignore the
863request. This leaves room for future operations to be added (older
864helpers will just ignore the new requests).</p></div>
865</div>
866</div>
867<div id="footnotes"><hr /></div>
868<div id="footer">
869<div id="footer-text">
870Last updated 2011-12-21 14:30:17 PDT
871</div>
872</div>
873</body>
874</html>