Changes On Branch orefkovs-change
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch orefkovs-change Excluding Merge-Ins

This is equivalent to a diff from ab9b449190 to 102bfeae46

2013-02-19
05:15
merge with trunk Leaf check-in: 102bfeae46 user: orefkov tags: orefkovs-change
2013-02-18
22:35
Update version of OpenSSL that is referred to in the makefiles. Leaf check-in: ab9b449190 user: mistachkin tags: trunk
13:46
Fixed ticket [5df2715635b99bd46a] (check-in count mismatch). check-in: b27c0d6d3f user: stephan tags: trunk
2013-01-31
05:10
Очередное слияние с транком check-in: 738c8b3779 user: orefkov tags: orefkovs-change

Changes to Makefile.classic.

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#    for building intermediate code-generator tools.
#
#TCC = gcc -O6
#TCC = gcc -g -O0 -Wall -fprofile-arcs -ftest-coverage
TCC = gcc -g -Os -Wall

# To add support for HTTPS
TCC += -DFOSSIL_ENABLE_SSL

#### Extra arguments for linking the finished binary.  Fossil needs
#    to link against the Z-Lib compression library.  There are no
#    other dependencies.  We sometimes add the -static option here
#    so that we can build a static executable that will run in a
#    chroot jail.
#
LIB = -lz $(LDFLAGS)

# If using HTTPS:
LIB += -lcrypto -lssl

#### Tcl shell for use in running the fossil testsuite.  If you do not
#    care about testing the end result, this can be blank.
#
TCLSH = tclsh

# You should not need to change anything below this line






|










|







34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#    for building intermediate code-generator tools.
#
#TCC = gcc -O6
#TCC = gcc -g -O0 -Wall -fprofile-arcs -ftest-coverage
TCC = gcc -g -Os -Wall

# To add support for HTTPS
#TCC += -DFOSSIL_ENABLE_SSL

#### Extra arguments for linking the finished binary.  Fossil needs
#    to link against the Z-Lib compression library.  There are no
#    other dependencies.  We sometimes add the -static option here
#    so that we can build a static executable that will run in a
#    chroot jail.
#
LIB = -lz $(LDFLAGS)

# If using HTTPS:
#LIB += -lcrypto -lssl

#### Tcl shell for use in running the fossil testsuite.  If you do not
#    care about testing the end result, this can be blank.
#
TCLSH = tclsh

# You should not need to change anything below this line

Changes to src/attach.c.

47
48
49
50
51
52
53




54


55
56
57




58


59
60
61




62


63
64
65
66
67
68
69
..
89
90
91
92
93
94
95
96






97
98
99
100
101
102



103

104



105

106
107





108
109
110





111
112
113
114
115



116

117



118

119
120



121

122
123
124
125
126
127
128
...
171
172
173
174
175
176
177




178
179

180
181
182




183
184

185
186
187
188
189
190
191
...
235
236
237
238
239
240
241

242
243
244
245
246
247
248
249
250
251
252






253
254
255
256
257
258
259
260
261
262






263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
...
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
...
315
316
317
318
319
320
321



322

323



324

325



326

327
328




329

330



331

332
333
334
335
336
337
338




339
340

341
342
343
344
345
346
347
...
431
432
433
434
435
436
437



438

439
440
441
442
443
444




445
446

447
448
449
450
451
452
453
...
458
459
460
461
462
463
464








465
466
467
468
469
470

471
472
473
474
475
476



477

478
479



480

481
482
483



484

485
486



487

488
489



490

491



492

493
494
495
496



497

498
499
500
501



502

503
504
505



506

507
508
509



510

511



512

513
514
515
516
517



518

519
520
521
522
523
524
525
...
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
...
570
571
572
573
574
575
576



577

578



579

580
581
582
583
584
585
586
587
     "       comment, user,"
     "       (SELECT uuid FROM blob WHERE rid=attachid), attachid"
     "  FROM attachment",
     -1
  );
  if( zPage ){
    if( g.perm.RdWiki==0 ) login_needed();




    style_header("Attachments To %h", zPage);


    blob_appendf(&sql, " WHERE target=%Q", zPage);
  }else if( zTkt ){
    if( g.perm.RdTkt==0 ) login_needed();




    style_header("Attachments To Ticket %.10s", zTkt);


    blob_appendf(&sql, " WHERE target GLOB '%q*'", zTkt);
  }else{
    if( g.perm.RdTkt==0 && g.perm.RdWiki==0 ) login_needed();




    style_header("All Attachments");


  }
  blob_appendf(&sql, " ORDER BY mtime DESC");
  db_prepare(&q, "%s", blob_str(&sql));
  @ <ol>
  while( db_step(&q)==SQLITE_ROW ){
    const char *zDate = db_column_text(&q, 0);
    const char *zSrc = db_column_text(&q, 1);
................................................................................
    }
    @ <li><p>
    @ Attachment %z(href("%R/ainfo/%s",zUuid))%S(zUuid)</a>
    if( moderation_pending(attachid) ){
      @ <span class="modpending">*** Awaiting Moderator Approval ***</span>
    }
    @ <br><a href="/attachview?%s(zUrlTail)">%h(zFilename)</a>
    @ [<a href="/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br />






    if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++;
    if( zComment && zComment[0] ){
      @ %w(zComment)<br />
    }
    if( zPage==0 && zTkt==0 ){
      if( zSrc==0 || zSrc[0]==0 ){



        zSrc = "Deleted from";

      }else {



        zSrc = "Added to";

      }
      if( strlen(zTarget)==UUID_SIZE && validate16(zTarget, UUID_SIZE) ){





        @ %s(zSrc) ticket <a href="%s(g.zTop)/tktview?name=%s(zTarget)">
        @ %S(zTarget)</a>
      }else{





        @ %s(zSrc) wiki page <a href="%s(g.zTop)/wiki?name=%t(zTarget)">
        @ %h(zTarget)</a>
      }
    }else{
      if( zSrc==0 || zSrc[0]==0 ){



        @ Deleted

      }else {



        @ Added

      }
    }



    @ by %h(zDispUser) on

    hyperlink_to_date(zDate, ".");
    free(zUrlTail);
  }
  db_finalize(&q);
  @ </ol>
  style_footer();
  return;
................................................................................
       "SELECT coalesce(src,'x') FROM attachment"
       " WHERE target=%Q AND filename=%Q"
       " ORDER BY mtime DESC LIMIT 1",
       zTarget, zFile
    );
  }
  if( zUUID==0 || zUUID[0]==0 ){




    style_header("No Such Attachment");
    @ No such attachment....

    style_footer();
    return;
  }else if( zUUID[0]=='x' ){




    style_header("Missing");
    @ Attachment has been deleted

    style_footer();
    return;
  }
  g.perm.Read = 1;
  cgi_replace_parameter("name",zUUID);
  if( fossil_strcmp(g.zPath,"attachview")==0 ){
    artifact_page();
................................................................................
  const char *zFrom = P("from");
  const char *aContent = P("f");
  const char *zName = PD("f:filename","unknown");
  const char *zTarget;
  const char *zTargetType;
  int szContent = atoi(PD("f:bytes","0"));
  int goodCaptcha = 1;


  if( P("cancel") ) cgi_redirect(zFrom);
  if( zPage && zTkt ) fossil_redirect_home();
  if( zPage==0 && zTkt==0 ) fossil_redirect_home();
  login_check_credentials();
  if( zPage ){
    if( g.perm.ApndWiki==0 || g.perm.Attach==0 ) login_needed();
    if( !db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'", zPage) ){
      fossil_redirect_home();
    }
    zTarget = zPage;






    zTargetType = mprintf("Wiki Page <a href=\"%s/wiki?name=%h\">%h</a>",
                           g.zTop, zPage, zPage);
  }else{
    if( g.perm.ApndTkt==0 || g.perm.Attach==0 ) login_needed();
    if( !db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'", zTkt) ){
      zTkt = db_text(0, "SELECT substr(tagname,5) FROM tag" 
                        " WHERE tagname GLOB 'tkt-%q*'", zTkt);
      if( zTkt==0 ) fossil_redirect_home();
    }
    zTarget = zTkt;






    zTargetType = mprintf("Ticket <a href=\"%s/tktview/%S\">%S</a>",
                          g.zTop, zTkt, zTkt);
  }
  if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop);
  if( P("cancel") ){
    cgi_redirect(zFrom);
  }
  if( P("ok") && szContent>0 && (goodCaptcha = captcha_is_correct()) ){
    Blob content;
    Blob manifest;
    Blob cksum;
    char *zUUID;
    const char *zComment;
    char *zDate;
    int rid;
    int i, n;
    int addCompress = 0;
    Manifest *pManifest;
    int needModerator;

................................................................................
    for(i=n=0; zName[i]; i++){
      if( zName[i]=='/' || zName[i]=='\\' ) n = i;
    }
    zName += n;
    if( zName[0]==0 ) zName = "unknown";
    blob_appendf(&manifest, "A %F%s %F %s\n",
                 zName, addCompress ? ".gz" : "", zTarget, zUUID);
    zComment = PD("comment", "");
    while( fossil_isspace(zComment[0]) ) zComment++;
    n = strlen(zComment);
    while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
    if( n>0 ){
      blob_appendf(&manifest, "C %F\n", zComment);
    }
    zDate = date_in_standard_format("now");
................................................................................
    md5sum_blob(&manifest, &cksum);
    blob_appendf(&manifest, "Z %b\n", &cksum);
    attach_put(&manifest, rid, needModerator);
    assert( blob_is_reset(&manifest) );
    db_end_transaction(0);
    cgi_redirect(zFrom);
  }



  style_header("Add Attachment");

  if( !goodCaptcha ){



    @ <p class="generalError">Error: Incorrect security code.</p>

  }



  @ <h2>Add Attachment To %s(zTargetType)</h2>

  form_begin("enctype='multipart/form-data'", "%R/attachadd");
  @ <div>




  @ File to Attach:

  @ <input type="file" name="f" size="60" /><br />



  @ Description:<br />

  @ <textarea name="comment" cols="80" rows="5" wrap="virtual"></textarea><br />
  if( zTkt ){
    @ <input type="hidden" name="tkt" value="%h(zTkt)" />
  }else{
    @ <input type="hidden" name="page" value="%h(zPage)" />
  }
  @ <input type="hidden" name="from" value="%h(zFrom)" />




  @ <input type="submit" name="ok" value="Add Attachment" />
  @ <input type="submit" name="cancel" value="Cancel" />

  @ </div>
  captcha_generate();
  @ </form>
  style_footer();
}

/*
................................................................................
    blob_appendf(&manifest, "D %s\n", zDate);
    blob_appendf(&manifest, "U %F\n", g.zLogin ? g.zLogin : "nobody");
    md5sum_blob(&manifest, &cksum);
    blob_appendf(&manifest, "Z %b\n", &cksum);
    rid = content_put(&manifest);
    manifest_crosslink(rid, &manifest);
    db_end_transaction(0);



    @ <p>The attachment below has been deleted.</p>

  }

  if( P("del")
   && ((zTktUuid && g.perm.WrTkt) || (zWikiName && g.perm.WrWiki))
  ){
    form_begin(0, "%R/ainfo/%s", zUuid);




    @ <p>Confirm you want to delete the attachment shown below.
    @ <input type="submit" name="confirm" value="Confirm">

    @ </form>
  }

  isModerator = (zTktUuid && g.perm.ModTkt) || (zWikiName && g.perm.ModWiki);
  if( isModerator && (zModAction = P("modaction"))!=0 ){
    if( strcmp(zModAction,"delete")==0 ){
      moderation_disapprove(rid);
................................................................................
      }
      return;
    }
    if( strcmp(zModAction,"approve")==0 ){
      moderation_approve(rid);
    }
  }








  style_header("Attachment Details");
  style_submenu_element("Raw", "Raw", "%R/artifact/%S", zUuid);

  @ <div class="section">Overview</div>
  @ <p><table class="label-value">
  @ <tr><th>Artifact&nbsp;ID:</th>

  @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
  if( g.perm.Setup ){
    @ (%d(rid))
  }
  modPending = moderation_pending(rid);
  if( modPending ){



    @ <span class="modpending">*** Awaiting Moderator Approval ***</span>

  }
  if( zTktUuid ){



    @ <tr><th>Ticket:</th>

    @ <td>%z(href("%R/tktview/%s",zTktUuid))%s(zTktUuid)</a></td></tr>
  }
  if( zWikiName ){



    @ <tr><th>Wiki&nbsp;Page:</th>

    @ <td>%z(href("%R/wiki?name=%t",zWikiName))%h(zWikiName)</a></td></tr>
  }



  @ <tr><th>Date:</th><td>

  hyperlink_to_date(zDate, "</td></tr>");
  free(zDate);



  @ <tr><th>User:</th><td>

  hyperlink_to_user(pAttach->zUser, zDate, "</td></tr>");



  @ <tr><th>Artifact&nbsp;Attached:</th>

  @ <td>%z(href("%R/artifact/%s",zSrc))%s(zSrc)</a>
  if( g.perm.Setup ){
    @ (%d(ridSrc))
  }



  @ <tr><th>Filename:</th><td>%h(zName)</td></tr>

  zMime = mimetype_from_name(zName);
  if( g.perm.Setup ){
    @ <tr><th>MIME-Type:</th><td>%h(zMime)</td></tr>
  }



  @ <tr><th valign="top">Description:</th><td valign="top">%h(zDesc)</td></tr>

  @ </table>
  
  if( isModerator && modPending ){



    @ <div class="section">Moderation</div>

    @ <blockquote>
    form_begin(0, "%R/ainfo/%s", zUuid);
    @ <label><input type="radio" name="modaction" value="delete">



    @ Delete this change</label><br />

    @ <label><input type="radio" name="modaction" value="approve">



    @ Approve this change</label><br />

    @ <input type="submit" value="Submit">
    @ </form>
    @ </blockquote>
  }




  @ <div class="section">Content Appended</div>

  @ <blockquote>
  blob_zero(&attach);
  if( zMime==0 || strncmp(zMime,"text/", 5)==0 ){
    const char *z;
    const char *zLn = P("ln");
    content_get(ridSrc, &attach);
    blob_to_utf8_no_bom(&attach, 0);
................................................................................
    }else{
      @ <pre>
      @ %h(z)
      @ </pre>
    }
  }else if( strncmp(zMime, "image/", 6)==0 ){
    @ <img src="%R/raw/%S(zSrc)?m=%s(zMime)"></img>
    style_submenu_element("Image", "Image", "%R/raw/%S?m=%s", zSrc, zMime);
  }else{
    int sz = db_int(0, "SELECT size FROM blob WHERE rid=%d", ridSrc);
    @ <i>(file is %d(sz) bytes of binary data)</i>
  }
  @ </blockquote>
  manifest_destroy(pAttach);
  blob_reset(&attach);
................................................................................
    const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous";
    if( cnt==0 ){
      @ %s(zHeader)
    }
    cnt++;
    @ <li>
    @ %z(href("%R/artifact/%s",zSrc))%h(zFile)</a>



    @ added by %h(zDispUser) on

    hyperlink_to_date(zDate, ".");



    @ [%z(href("%R/ainfo/%s",zUuid))details</a>]

    @ </li>
  }
  if( cnt ){
    @ </ul>
  }
  db_finalize(&q);
  
}






>
>
>
>
|
>
>



>
>
>
>
|
>
>



>
>
>
>
|
>
>







 







|
>
>
>
>
>
>






>
>
>

>

>
>
>

>


>
>
>
>
>
|


>
>
>
>
>
|




>
>
>

>

>
>
>

>


>
>
>

>







 







>
>
>
>


>



>
>
>
>


>







 







>
|










>
>
>
>
>
>
|
|








>
>
>
>
>
>
|











<







 







<







 







>
>
>

>

>
>
>

>

>
>
>

>


>
>
>
>

>
|
>
>
>

>
|






>
>
>
>


>







 







>
>
>

>






>
>
>
>


>







 







>
>
>
>
>
>
>
>






>






>
>
>

>


>
>
>

>



>
>
>

>


>
>
>

>


>
>
>

>

>
>
>

>




>
>
>

>




>
>
>

>



>
>
>

>



>
>
>

>

>
>
>

>





>
>
>

>







 







|







 







>
>
>

>

>
>
>

>








47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
...
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
...
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
...
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351

352
353
354
355
356
357
358
...
374
375
376
377
378
379
380

381
382
383
384
385
386
387
...
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
...
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
...
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
...
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
...
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
     "       comment, user,"
     "       (SELECT uuid FROM blob WHERE rid=attachid), attachid"
     "  FROM attachment",
     -1
  );
  if( zPage ){
    if( g.perm.RdWiki==0 ) login_needed();
    style_header(
#ifdef LANG_RU
      "Файлы к %h",
#elif LANG_EN
      "Attachments To %h",
#endif
      zPage);
    blob_appendf(&sql, " WHERE target=%Q", zPage);
  }else if( zTkt ){
    if( g.perm.RdTkt==0 ) login_needed();
    style_header(
#ifdef LANG_RU
      "Файлы к задаче %.10s",
#elif LANG_EN
      "Attachments To Ticket %.10s",
#endif
      zTkt);
    blob_appendf(&sql, " WHERE target GLOB '%q*'", zTkt);
  }else{
    if( g.perm.RdTkt==0 && g.perm.RdWiki==0 ) login_needed();
    style_header(
#ifdef LANG_RU
      "Все файлы"
#elif LANG_EN
      "All Attachments"
#endif
      );
  }
  blob_appendf(&sql, " ORDER BY mtime DESC");
  db_prepare(&q, "%s", blob_str(&sql));
  @ <ol>
  while( db_step(&q)==SQLITE_ROW ){
    const char *zDate = db_column_text(&q, 0);
    const char *zSrc = db_column_text(&q, 1);
................................................................................
    }
    @ <li><p>
    @ Attachment %z(href("%R/ainfo/%s",zUuid))%S(zUuid)</a>
    if( moderation_pending(attachid) ){
      @ <span class="modpending">*** Awaiting Moderator Approval ***</span>
    }
    @ <br><a href="/attachview?%s(zUrlTail)">%h(zFilename)</a>
    @ [<a href="/attachdownload/%t(zFilename)?%s(zUrlTail)">
#ifdef LANG_RU
    @ скачать
#elif LANG_EN
    @ download
#endif
    @ </a>]<br />
    if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++;
    if( zComment && zComment[0] ){
      @ %w(zComment)<br />
    }
    if( zPage==0 && zTkt==0 ){
      if( zSrc==0 || zSrc[0]==0 ){
#ifdef LANG_RU
        zSrc = "Удалён из";
#elif LANG_EN
        zSrc = "Deleted from";
#endif
      }else {
#ifdef LANG_RU
        zSrc = "Добавлен к";
#elif LANG_EN
        zSrc = "Added to";
#endif
      }
      if( strlen(zTarget)==UUID_SIZE && validate16(zTarget, UUID_SIZE) ){
#ifdef LANG_RU
          @ %s(zSrc) задач%s(zSrc==0 || zSrc[0]==0 ? "и" : "е")
#elif LANG_EN
        @ %s(zSrc) ticket
#endif
        @ <a href="%s(g.zTop)/tktview?name=%s(zTarget)">
        @ %S(zTarget)</a>
      }else{
#ifdef LANG_RU
        @ %s(zSrc) страниц%s(zSrc==0 || zSrc[0]==0 ? "ы" : "е")
#elif LANG_EN
        @ %s(zSrc) wiki page
#endif
        @ <a href="%s(g.zTop)/wiki?name=%t(zTarget)">
        @ %h(zTarget)</a>
      }
    }else{
      if( zSrc==0 || zSrc[0]==0 ){
#ifdef LANG_RU
        @ Удалён
#elif LANG_EN
        @ Deleted
#endif
      }else {
#ifdef LANG_RU
        @ Добавлен
#elif LANG_EN
        @ Added
#endif
      }
    }
#ifdef LANG_RU
    @ пользователем %h(zDispUser)
#elif LANG_EN
    @ by %h(zDispUser) on
#endif
    hyperlink_to_date(zDate, ".");
    free(zUrlTail);
  }
  db_finalize(&q);
  @ </ol>
  style_footer();
  return;
................................................................................
       "SELECT coalesce(src,'x') FROM attachment"
       " WHERE target=%Q AND filename=%Q"
       " ORDER BY mtime DESC LIMIT 1",
       zTarget, zFile
    );
  }
  if( zUUID==0 || zUUID[0]==0 ){
#ifdef LANG_RU
    style_header("Нет файлов");
    @ Нет файлов....
#elif LANG_EN
    style_header("No Such Attachment");
    @ No such attachment....
#endif
    style_footer();
    return;
  }else if( zUUID[0]=='x' ){
#ifdef LANG_RU
    style_header("Отсутствует");
    @ Файл был удален
#elif LANG_EN
    style_header("Missing");
    @ Attachment has been deleted
#endif
    style_footer();
    return;
  }
  g.perm.Read = 1;
  cgi_replace_parameter("name",zUUID);
  if( fossil_strcmp(g.zPath,"attachview")==0 ){
    artifact_page();
................................................................................
  const char *zFrom = P("from");
  const char *aContent = P("f");
  const char *zName = PD("f:filename","unknown");
  const char *zTarget;
  const char *zTargetType;
  int szContent = atoi(PD("f:bytes","0"));
  int goodCaptcha = 1;
  const char *zComment = PD("comment", "");
 
  if( P("cancel") ) cgi_redirect(zFrom);
  if( zPage && zTkt ) fossil_redirect_home();
  if( zPage==0 && zTkt==0 ) fossil_redirect_home();
  login_check_credentials();
  if( zPage ){
    if( g.perm.ApndWiki==0 || g.perm.Attach==0 ) login_needed();
    if( !db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'", zPage) ){
      fossil_redirect_home();
    }
    zTarget = zPage;
    zTargetType = mprintf(
#ifdef LANG_RU
      "странице"
#elif LANG_EN
      "Wiki Page"
#endif
      " <a href=\"%s/wiki?name=%h\">%h</a>",
        g.zTop, zPage, zPage);
  }else{
    if( g.perm.ApndTkt==0 || g.perm.Attach==0 ) login_needed();
    if( !db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'", zTkt) ){
      zTkt = db_text(0, "SELECT substr(tagname,5) FROM tag" 
                        " WHERE tagname GLOB 'tkt-%q*'", zTkt);
      if( zTkt==0 ) fossil_redirect_home();
    }
    zTarget = zTkt;
    zTargetType = mprintf(
#ifdef LANG_RU
      "задаче"
#elif LANG_EN
      "Ticket"
#endif
      " <a href=\"%s/tktview/%S\">%S</a>",
                          g.zTop, zTkt, zTkt);
  }
  if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop);
  if( P("cancel") ){
    cgi_redirect(zFrom);
  }
  if( P("ok") && szContent>0 && (goodCaptcha = captcha_is_correct()) ){
    Blob content;
    Blob manifest;
    Blob cksum;
    char *zUUID;

    char *zDate;
    int rid;
    int i, n;
    int addCompress = 0;
    Manifest *pManifest;
    int needModerator;

................................................................................
    for(i=n=0; zName[i]; i++){
      if( zName[i]=='/' || zName[i]=='\\' ) n = i;
    }
    zName += n;
    if( zName[0]==0 ) zName = "unknown";
    blob_appendf(&manifest, "A %F%s %F %s\n",
                 zName, addCompress ? ".gz" : "", zTarget, zUUID);

    while( fossil_isspace(zComment[0]) ) zComment++;
    n = strlen(zComment);
    while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
    if( n>0 ){
      blob_appendf(&manifest, "C %F\n", zComment);
    }
    zDate = date_in_standard_format("now");
................................................................................
    md5sum_blob(&manifest, &cksum);
    blob_appendf(&manifest, "Z %b\n", &cksum);
    attach_put(&manifest, rid, needModerator);
    assert( blob_is_reset(&manifest) );
    db_end_transaction(0);
    cgi_redirect(zFrom);
  }
#ifdef LANG_RU
  style_header("Добавить файл");
#elif LANG_EN
  style_header("Add Attachment");
#endif
  if( !goodCaptcha ){
#ifdef LANG_RU
    @ <p class="generalError">Ошибка: неверный код.</p>
#elif LANG_EN
    @ <p class="generalError">Error: Incorrect security code.</p>
#endif
  }
#ifdef LANG_RU
  @ <h2>Добавление файла к %s(zTargetType)</h2>
#elif LANG_EN
  @ <h2>Add Attachment To %s(zTargetType)</h2>
#endif
  form_begin("enctype='multipart/form-data'", "%R/attachadd");
  @ <div>

#ifdef LANG_RU
  @ Файл для добавления:
#elif LANG_EN
  @ File to Attach:
#endif
  @ <input type="file" name="f" size="120" /><br />
#ifdef LANG_RU
  @ Описание:<br />
#elif LANG_EN
  @ Description:<br />
#endif
  @ <textarea name="comment" cols="80" rows="5" wrap="virtual">%h(zComment)</textarea><br />
  if( zTkt ){
    @ <input type="hidden" name="tkt" value="%h(zTkt)" />
  }else{
    @ <input type="hidden" name="page" value="%h(zPage)" />
  }
  @ <input type="hidden" name="from" value="%h(zFrom)" />
#ifdef LANG_RU
  @ <input type="submit" name="ok" value="Добавить файл" />
  @ <input type="submit" name="cancel" value="Отмена" />
#elif LANG_EN
  @ <input type="submit" name="ok" value="Add Attachment" />
  @ <input type="submit" name="cancel" value="Cancel" />
#endif
  @ </div>
  captcha_generate();
  @ </form>
  style_footer();
}

/*
................................................................................
    blob_appendf(&manifest, "D %s\n", zDate);
    blob_appendf(&manifest, "U %F\n", g.zLogin ? g.zLogin : "nobody");
    md5sum_blob(&manifest, &cksum);
    blob_appendf(&manifest, "Z %b\n", &cksum);
    rid = content_put(&manifest);
    manifest_crosslink(rid, &manifest);
    db_end_transaction(0);
#ifdef LANG_RU
    @ <p>Указанный файл будет удален.</p>
#elif LANG_EN
    @ <p>The attachment below has been deleted.</p>
#endif
  }

  if( P("del")
   && ((zTktUuid && g.perm.WrTkt) || (zWikiName && g.perm.WrWiki))
  ){
    form_begin(0, "%R/ainfo/%s", zUuid);
#ifdef LANG_RU
    @ <p>Подтвердите удаление указанного файла.
    @ <input type="submit" name="confirm" value="Подтверждаю">
#elif LANG_EN
    @ <p>Confirm you want to delete the attachment shown below.
    @ <input type="submit" name="confirm" value="Confirm">
#endif
    @ </form>
  }

  isModerator = (zTktUuid && g.perm.ModTkt) || (zWikiName && g.perm.ModWiki);
  if( isModerator && (zModAction = P("modaction"))!=0 ){
    if( strcmp(zModAction,"delete")==0 ){
      moderation_disapprove(rid);
................................................................................
      }
      return;
    }
    if( strcmp(zModAction,"approve")==0 ){
      moderation_approve(rid);
    }
  }
#ifdef LANG_RU
  style_header("Информация о вложении");
  style_submenu_element("Как есть", "Как есть", "%R/artifact/%S", zUuid);

  @ <div class="section">Обзор</div>
  @ <p><table class="label-value">
  @ <tr><th>ID&nbsp;артефакта:</th>
#elif LANG_EN
  style_header("Attachment Details");
  style_submenu_element("Raw", "Raw", "%R/artifact/%S", zUuid);

  @ <div class="section">Overview</div>
  @ <p><table class="label-value">
  @ <tr><th>Artifact&nbsp;ID:</th>
#endif
  @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
  if( g.perm.Setup ){
    @ (%d(rid))
  }
  modPending = moderation_pending(rid);
  if( modPending ){
#ifdef LANG_RU
    @ <span class="modpending">*** Ожидает утверждения модератором ***</span>
#elif LANG_EN
    @ <span class="modpending">*** Awaiting Moderator Approval ***</span>
#endif
  }
  if( zTktUuid ){
#ifdef LANG_RU
    @ <tr><th>Задача:</th>
#elif LANG_EN
    @ <tr><th>Ticket:</th>
#endif
    @ <td>%z(href("%R/tktview/%s",zTktUuid))%s(zTktUuid)</a></td></tr>
  }
  if( zWikiName ){
#ifdef LANG_RU
    @ <tr><th>Вики-страница:</th>
#elif LANG_EN
    @ <tr><th>Wiki&nbsp;Page:</th>
#endif
    @ <td>%z(href("%R/wiki?name=%t",zWikiName))%h(zWikiName)</a></td></tr>
  }
#ifdef LANG_RU
  @ <tr><th>Дата:</th><td>
#elif LANG_EN
  @ <tr><th>Date:</th><td>
#endif
  hyperlink_to_date(zDate, "</td></tr>");
  free(zDate);
#ifdef LANG_RU
  @ <tr><th>Пользователь:</th><td>
#elif LANG_EN
  @ <tr><th>User:</th><td>
#endif
  hyperlink_to_user(pAttach->zUser, zDate, "</td></tr>");
#ifdef LANG_RU
  @ <tr><th>Артефакт&nbsp;:</th>
#elif LANG_EN
  @ <tr><th>Artifact&nbsp;Attached:</th>
#endif
  @ <td>%z(href("%R/artifact/%s",zSrc))%s(zSrc)</a>
  if( g.perm.Setup ){
    @ (%d(ridSrc))
  }
#ifdef LANG_RU
  @ <tr><th>Имя файла:</th><td>%h(zName)</td></tr>
#elif LANG_EN
  @ <tr><th>Filename:</th><td>%h(zName)</td></tr>
#endif
  zMime = mimetype_from_name(zName);
  if( g.perm.Setup ){
    @ <tr><th>MIME-Type:</th><td>%h(zMime)</td></tr>
  }
#ifdef LANG_RU
  @ <tr><th valign="top">Описание:</th><td valign="top">%h(zDesc)</td></tr>
#elif LANG_EN
  @ <tr><th valign="top">Description:</th><td valign="top">%h(zDesc)</td></tr>
#endif
  @ </table>
  
  if( isModerator && modPending ){
#ifdef LANG_RU
    @ <div class="section">Модерация</div>
#elif LANG_EN
    @ <div class="section">Moderation</div>
#endif
    @ <blockquote>
    form_begin(0, "%R/ainfo/%s", zUuid);
    @ <label><input type="radio" name="modaction" value="delete">
#ifdef LANG_RU
    @ Удалить это изменение</label><br />
#elif LANG_EN
    @ Delete this change</label><br />
#endif
    @ <label><input type="radio" name="modaction" value="approve">
#ifdef LANG_RU
    @ Утвердить это изменение</label><br />
#elif LANG_EN
    @ Approve this change</label><br />
#endif
    @ <input type="submit" value="Submit">
    @ </form>
    @ </blockquote>
  }

#ifdef LANG_RU
  @ <div class="section">Содержимое</div>
#elif LANG_EN
  @ <div class="section">Content Appended</div>
#endif
  @ <blockquote>
  blob_zero(&attach);
  if( zMime==0 || strncmp(zMime,"text/", 5)==0 ){
    const char *z;
    const char *zLn = P("ln");
    content_get(ridSrc, &attach);
    blob_to_utf8_no_bom(&attach, 0);
................................................................................
    }else{
      @ <pre>
      @ %h(z)
      @ </pre>
    }
  }else if( strncmp(zMime, "image/", 6)==0 ){
    @ <img src="%R/raw/%S(zSrc)?m=%s(zMime)"></img>
    style_submenu_element("Изображение", "Изображение", "%R/raw/%S?m=%s", zSrc, zMime);
  }else{
    int sz = db_int(0, "SELECT size FROM blob WHERE rid=%d", ridSrc);
    @ <i>(file is %d(sz) bytes of binary data)</i>
  }
  @ </blockquote>
  manifest_destroy(pAttach);
  blob_reset(&attach);
................................................................................
    const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous";
    if( cnt==0 ){
      @ %s(zHeader)
    }
    cnt++;
    @ <li>
    @ %z(href("%R/artifact/%s",zSrc))%h(zFile)</a>
#ifdef LANG_RU
    @ добавлено %h(zDispUser) 
#elif LANG_EN
    @ added by %h(zDispUser) on
#endif
    hyperlink_to_date(zDate, ".");
#ifdef LANG_RU
    @ [%z(href("%R/ainfo/%s",zUuid))детали</a>]
#elif LANG_EN
    @ [%z(href("%R/ainfo/%s",zUuid))details</a>]
#endif
    @ </li>
  }
  if( cnt ){
    @ </ul>
  }
  db_finalize(&q);
  
}

Changes to src/cgi.c.

1240
1241
1242
1243
1244
1245
1246


1247
1248
1249
1250
1251
1252
1253
      cgi_setenv("HTTP_IF_MODIFIED_SINCE", zVal);
#if 0
    }else if( fossil_strcmp(zFieldName,"referer:")==0 ){
      cgi_setenv("HTTP_REFERER", zVal);
#endif
    }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){
      cgi_setenv("HTTP_USER_AGENT", zVal);


    }
  }
  cgi_init();
  cgi_trace(0);
}

#if INTERFACE






>
>







1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
      cgi_setenv("HTTP_IF_MODIFIED_SINCE", zVal);
#if 0
    }else if( fossil_strcmp(zFieldName,"referer:")==0 ){
      cgi_setenv("HTTP_REFERER", zVal);
#endif
    }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){
      cgi_setenv("HTTP_USER_AGENT", zVal);
    }else if( fossil_strcmp(zFieldName,"x-real-ip:")==0 ){
      cgi_replace_parameter("REMOTE_ADDR", mprintf("%s",zVal));
    }
  }
  cgi_init();
  cgi_trace(0);
}

#if INTERFACE

Changes to src/checkin.c.

650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
  const char *zUuid,    /* The artifact ID of the ancestor */
  const char *zDate     /* Date & time of the current check-in */
){
#ifndef FOSSIL_ALLOW_OUT_OF_ORDER_DATES
  int b;
  b = db_exists(
    "SELECT 1 FROM event"
    " WHERE datetime(mtime)>=%Q"
    "   AND type='ci' AND objid=%d",
    zDate, rid
  );
  if( b ){
    fossil_fatal("ancestor check-in [%.10s] (%s) is not older (clock skew?)"
                 " Use --allow-older to override.", zUuid, zDate);
  }






|







650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
  const char *zUuid,    /* The artifact ID of the ancestor */
  const char *zDate     /* Date & time of the current check-in */
){
#ifndef FOSSIL_ALLOW_OUT_OF_ORDER_DATES
  int b;
  b = db_exists(
    "SELECT 1 FROM event"
    " WHERE strftime('%%Y-%%m-%%dT%%H:%%M:%%f', mtime)>=strftime('%%Y-%%m-%%dT%%H:%%M:%%f', %Q)"
    "   AND type='ci' AND objid=%d",
    zDate, rid
  );
  if( b ){
    fossil_fatal("ancestor check-in [%.10s] (%s) is not older (clock skew?)"
                 " Use --allow-older to override.", zUuid, zDate);
  }

Changes to src/diff.c.

43
44
45
46
47
48
49

50
51
52
53
54
55
56
...
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
#define DIFF_CONTEXT_EX   (((u64)0x04)<<32) /* Use context even if zero */
#define DIFF_NOTTOOBIG    (((u64)0x08)<<32) /* Only display if not too big */

/*
** These error messages are shared in multiple locations.  They are defined
** here for consistency.
*/

#define DIFF_CANNOT_COMPUTE_BINARY \
    "cannot compute difference between binary files\n"

#define DIFF_CANNOT_COMPUTE_SYMLINK \
    "cannot compute difference between symlink and regular file\n"

#define DIFF_TOO_MANY_CHANGES_TXT \
................................................................................
          p->iEnd = p->iEnd2;
          p->iEnd2 = 0;
        }
      }
    }
    if( c=='\t' ){
      z[j++] = ' ';
      while( (k&7)!=7 && k<w ){ z[j++] = ' '; k++; }
    }else if( c=='\r' || c=='\f' ){
      z[j++] = ' ';
    }else if( c=='<' && p->escHtml ){
      memcpy(&z[j], "&lt;", 4);
      j += 4;
    }else if( c=='&' && p->escHtml ){
      memcpy(&z[j], "&amp;", 5);






>







 







|







43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
...
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
#define DIFF_CONTEXT_EX   (((u64)0x04)<<32) /* Use context even if zero */
#define DIFF_NOTTOOBIG    (((u64)0x08)<<32) /* Only display if not too big */

/*
** These error messages are shared in multiple locations.  They are defined
** here for consistency.
*/

#define DIFF_CANNOT_COMPUTE_BINARY \
    "cannot compute difference between binary files\n"

#define DIFF_CANNOT_COMPUTE_SYMLINK \
    "cannot compute difference between symlink and regular file\n"

#define DIFF_TOO_MANY_CHANGES_TXT \
................................................................................
          p->iEnd = p->iEnd2;
          p->iEnd2 = 0;
        }
      }
    }
    if( c=='\t' ){
      z[j++] = ' ';
      while( (k&3)!=3 && k<w ){ z[j++] = ' '; k++; }
    }else if( c=='\r' || c=='\f' ){
      z[j++] = ' ';
    }else if( c=='<' && p->escHtml ){
      memcpy(&z[j], "&lt;", 4);
      j += 4;
    }else if( c=='&' && p->escHtml ){
      memcpy(&z[j], "&amp;", 5);

Changes to src/info.c.

83
84
85
86
87
88
89
90












91
92
93
94
95
96
97
98
99
100
101
102
103
104












105
106
107
108
109
110
111
112
113
114
115




116


117
118
119




120


121
122
123
124
125
126
127
...
459
460
461
462
463
464
465




466





467

468
469
470
471
472
473
474
...
484
485
486
487
488
489
490
491






492
493
494
495
496
497
498
...
504
505
506
507
508
509
510



511

512






513
514



515

516
517



518

519
520



521

522
523
524



525

526



527

528
529



530

531
532
533




534
535

536

537



538
539
540
541
542
543
544
545
546
547
548
549
550




551
552

553
554
555
556
557













558
559
560
561
562
563
564
565
566
567
568

569
570
571
572
573
574
575
...
576
577
578
579
580
581
582
583
584
585
586
587



588

589
590
591






592
593
594
595
596

597



598

599
600
601
602
603
604




605


606
607
608
609
610



611

612
613
614
615
616
617



618

619
620



621

622
623



624

625
626
627



628

629



630

631
632
633
634





635
636



637

638
639



640

641
642
643



644

645



646

647
648

649



650



651
652
653
654
655
656
657
...
694
695
696
697
698
699
700




701





702

703
704
705
706
707
708
709
710
711
712
713
714
715



716

717
718







719
720
721
722
723

724



725

726



727

728
729
730
731
732
733



734

735
736






737
738
739
740

741
742
743



744

745
746
747
748
749
750
751
752
753



754

755
756
757



758

759



760




761

762
763
764
765
766



767

768
769
770
771
772
773
774
...
777
778
779
780
781
782
783




784
785

786
787
788
789
790
791
792
793
794
795
796
797
798
799
800




801


802




803


804
805
806
807




808


809
810
811
812
813
814
815
...
838
839
840
841
842
843
844



845

846
847
848
849
850
851
852
...
854
855
856
857
858
859
860



861

862
863

864



865



866

867
868
869
870
871
872
873
...
915
916
917
918
919
920
921




922

923
924
925




926

927
928
929
930
931
932





933
934
935

936






937
938
939
940
941
942
943
....
1271
1272
1273
1274
1275
1276
1277



1278







1279
1280
1281




1282

1283
1284
1285




1286

1287
1288
1289
1290
1291



1292

1293





1294
1295



1296

1297
1298






1299
1300
1301
1302
1303
1304
1305
1306
....
1414
1415
1416
1417
1418
1419
1420






1421
1422
1423






1424
1425
1426
1427




1428


1429
1430





1431
1432
1433

1434
1435
1436



1437

1438
1439
1440
1441
1442
1443
1444
....
1571
1572
1573
1574
1575
1576
1577



1578

1579
1580





1581
1582
1583

1584

1585
1586



1587

1588
1589



1590

1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602





1603
1604
1605
1606
1607





1608
1609
1610
1611





1612
1613
1614
1615
1616
1617
1618
....
1642
1643
1644
1645
1646
1647
1648



1649

1650
1651
1652
1653
1654
1655
1656
  }
  if( showFamily ){
    db_prepare(&q, "SELECT uuid, pid, isprim FROM plink JOIN blob ON pid=rid "
                   " WHERE cid=%d"
                   " ORDER BY isprim DESC, mtime DESC /*sort*/", rid);
    while( db_step(&q)==SQLITE_ROW ){
      const char *zUuid = db_column_text(&q, 0);
      const char *zType = db_column_int(&q, 2) ? "parent:" : "merged-from:";












      zDate = db_text("",
        "SELECT datetime(mtime) || ' UTC' FROM event WHERE objid=%d",
        db_column_int(&q, 1)
      );
      fossil_print("%-13s %s %s\n", zType, zUuid, zDate);
      free(zDate);
    }
    db_finalize(&q);
    db_prepare(&q, "SELECT uuid, cid, isprim FROM plink JOIN blob ON cid=rid "
                   " WHERE pid=%d"
                   " ORDER BY isprim DESC, mtime DESC /*sort*/", rid);
    while( db_step(&q)==SQLITE_ROW ){
      const char *zUuid = db_column_text(&q, 0);
      const char *zType = db_column_int(&q, 2) ? "child:" : "merged-into:";












      zDate = db_text("",
        "SELECT datetime(mtime) || ' UTC' FROM event WHERE objid=%d",
        db_column_int(&q, 1)
      );
      fossil_print("%-13s %s %s\n", zType, zUuid, zDate);
      free(zDate);
    }
    db_finalize(&q);
  }
  zTags = info_tags_of_checkin(rid, 0);
  if( zTags && zTags[0] ){




    fossil_print("tags:         %s\n", zTags);


  }
  free(zTags);
  if( zComment ){




    fossil_print("comment:      ");


    comment_print(zComment, 14, 79);
    free(zComment);
  }
}

/*
** Print information about the URLs used to access a repository and
................................................................................
  ReCompiled *pRe = 0; /* regex */

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  zName = P("name");
  rid = name_to_rid_www("name");
  if( rid==0 ){




    style_header("Check-in Information Error");





    @ No such object: %h(g.argv[2])

    style_footer();
    return;
  }
  zRe = P("regex");
  if( zRe ) re_compile(&pRe, zRe, 0);
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
  zParent = db_text(0,
................................................................................
     " WHERE blob.rid=%d"
     "   AND event.objid=%d",
     rid, rid
  );
  sideBySide = atoi(PD("sbs","1"));
  if( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);
    char *zTitle = mprintf("Check-in [%.10s]", zUuid);






    char *zEUser, *zEComment;
    const char *zUser;
    const char *zComment;
    const char *zDate;
    const char *zOrigDate;

    style_header(zTitle);
................................................................................
    zEComment = db_text(0,
                   "SELECT value FROM tagxref WHERE tagid=%d AND rid=%d",
                   TAG_COMMENT, rid);
    zUser = db_column_text(&q, 2);
    zComment = db_column_text(&q, 3);
    zDate = db_column_text(&q,1);
    zOrigDate = db_column_text(&q, 4);



    @ <div class="section">Overview</div>

    @ <table class="label-value">






    @ <tr><th>SHA1&nbsp;Hash:</th><td>%s(zUuid)
    if( g.perm.Setup ){



      @ (Record ID: %d(rid))

    }
    @ </td></tr>



    @ <tr><th>Date:</th><td>

    hyperlink_to_date(zDate, "</td></tr>");
    if( zOrigDate && fossil_strcmp(zDate, zOrigDate)!=0 ){



      @ <tr><th>Original&nbsp;Date:</th><td>

      hyperlink_to_date(zOrigDate, "</td></tr>");
    }
    if( zEUser ){



      @ <tr><th>Edited&nbsp;User:</th><td>

      hyperlink_to_user(zEUser,zDate,"</td></tr>");



      @ <tr><th>Original&nbsp;User:</th><td>

      hyperlink_to_user(zUser,zDate,"</td></tr>");
    }else{



      @ <tr><th>User:</th><td>

      hyperlink_to_user(zUser,zDate,"</td></tr>");
    }
    if( zEComment ){




      @ <tr><th>Edited&nbsp;Comment:</th><td>%w(zEComment)</td></tr>
      @ <tr><th>Original&nbsp;Comment:</th><td>%w(zComment)</td></tr>

    }else{

      @ <tr><th>Comment:</th><td>%w(zComment)</td></tr>



    }
    if( g.perm.Admin ){
      db_prepare(&q,
         "SELECT rcvfrom.ipaddr, user.login, datetime(rcvfrom.mtime)"
         "  FROM blob JOIN rcvfrom USING(rcvid) LEFT JOIN user USING(uid)"
         " WHERE blob.rid=%d",
         rid
      );
      if( db_step(&q)==SQLITE_ROW ){
        const char *zIpAddr = db_column_text(&q, 0);
        const char *zUser = db_column_text(&q, 1);
        const char *zDate = db_column_text(&q, 2);
        if( zUser==0 || zUser[0]==0 ) zUser = "unknown";




        @ <tr><th>Received&nbsp;From:</th>
        @ <td>%h(zUser) @ %h(zIpAddr) on %s(zDate)</td></tr>

      }
      db_finalize(&q);
    }
    if( g.perm.Hyperlink ){
      const char *zProjName = db_get("project-name", "unnamed");













      @ <tr><th>Timelines:</th><td>
      @   %z(href("%R/timeline?f=%S",zUuid))family</a>
      if( zParent ){
        @ | %z(href("%R/timeline?p=%S",zUuid))ancestors</a>
      }
      if( !isLeaf ){
        @ | %z(href("%R/timeline?d=%S",zUuid))descendants</a>
      }
      if( zParent && !isLeaf ){
        @ | %z(href("%R/timeline?dp=%S",zUuid))both</a>
      }

      db_prepare(&q, "SELECT substr(tag.tagname,5) FROM tagxref, tag "
                     " WHERE rid=%d AND tagtype>0 "
                     "   AND tag.tagid=tagxref.tagid "
                     "   AND +tag.tagname GLOB 'sym-*'", rid);
      while( db_step(&q)==SQLITE_ROW ){
        const char *zTagName = db_column_text(&q, 0);
        @  | %z(href("%R/timeline?r=%T",zTagName))%h(zTagName)</a>
................................................................................
      }
      db_finalize(&q);


      /* The Download: line */
      if( g.perm.Zip ){
        char *zUrl = mprintf("%R/tarball/%t-%S.tar.gz?uuid=%s",
                             zProjName, zUuid, zUuid);
        @ </td></tr>
        @ <tr><th>Downloads:</th><td>
        @ %z(href("%s",zUrl))Tarball</a>
        @ | %z(href("%R/zip/%t-%S.zip?uuid=%s",zProjName,zUuid,zUuid))



        @         ZIP archive</a>

        fossil_free(zUrl);
      }
      @ </td></tr>






      @ <tr><th>Other&nbsp;Links:</th>
      @   <td>
      @     %z(href("%R/dir?ci=%S",zUuid))files</a>
      @   | %z(href("%R/fileage?name=%S",zUuid))file ages</a>
      @   | %z(href("%R/artifact/%S",zUuid))manifest</a>

      if( g.perm.Write ){



        @   | %z(href("%R/ci_edit?r=%S",zUuid))edit</a>

      }
      @   </td>
      @ </tr>
    }
    @ </table>
  }else{




    style_header("Check-in Information");


    login_anonymous_available();
  }
  db_finalize(&q);
  showTags(rid, "");
  if( zParent ){



    @ <div class="section">Changes</div>

    @ <div class="sectionmenu">
    showDiff = g.zPath[0]!='c';
    if( db_get_boolean("show-version-diffs", 0)==0 ){
      showDiff = !showDiff;
      if( showDiff ){
        @ %z(xhref("class='button'","%R/vinfo/%T",zName))



        @ hide&nbsp;diffs</a>

        if( sideBySide ){
          @ %z(xhref("class='button'","%R/ci/%T?sbs=0",zName))



          @ unified&nbsp;diffs</a>

        }else{
          @ %z(xhref("class='button'","%R/ci/%T?sbs=1",zName))



          @ side-by-side&nbsp;diffs</a>

        }
      }else{
        @ %z(xhref("class='button'","%R/ci/%T?sbs=0",zName))



        @ show&nbsp;unified&nbsp;diffs</a>

        @ %z(xhref("class='button'","%R/ci/%T?sbs=1",zName))



        @ show&nbsp;side-by-side&nbsp;diffs</a>

      }
    }else{
      if( showDiff ){
        @ %z(xhref("class='button'","%R/ci/%T",zName))hide&nbsp;diffs</a>





        if( sideBySide ){
          @ %z(xhref("class='button'","%R/info/%T?sbs=0",zName))



          @ unified&nbsp;diffs</a>

        }else{
          @ %z(xhref("class='button'","%R/info/%T?sbs=1",zName))



          @ side-by-side&nbsp;diffs</a>

        }
      }else{
        @ %z(xhref("class='button'","%R/vinfo/%T?sbs=0",zName))



        @ show&nbsp;unified&nbsp;diffs</a>

        @ %z(xhref("class='button'","%R/vinfo/%T?sbs=1",zName))



        @ show&nbsp;side-by-side&nbsp;diffs</a>

      }
    }

    @ %z(xhref("class='button'","%R/vpatch?from=%S&to=%S",zParent,zUuid))



    @ patch</a></div>



    if( pRe ){
      @ <p><b>Only differences that match regular expression "%h(zRe)"
      @ are shown.</b></p>
    }
    db_prepare(&q,
       "SELECT name,"
       "       mperm,"
................................................................................
  int modPending;
  const char *zModAction;

  login_check_credentials();
  if( !g.perm.RdWiki ){ login_needed(); return; }
  rid = name_to_rid_www("name");
  if( rid==0 || (pWiki = manifest_get(rid, CFTYPE_WIKI))==0 ){




    style_header("Wiki Page Information Error");





    @ No such object: %h(P("name"))

    style_footer();
    return;
  }
  if( g.perm.ModWiki && (zModAction = P("modaction"))!=0 ){
    if( strcmp(zModAction,"delete")==0 ){
      moderation_disapprove(rid);
      cgi_redirectf("%R/wiki?name=%T", pWiki->zWikiTitle);
      /*NOTREACHED*/
    }
    if( strcmp(zModAction,"approve")==0 ){
      moderation_approve(rid);
    }
  }



  style_header("Update of \"%h\"", pWiki->zWikiTitle);

  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
  zDate = db_text(0, "SELECT datetime(%.17g)", pWiki->rDate);







  style_submenu_element("Raw", "Raw", "artifact/%S", zUuid);
  style_submenu_element("History", "History", "whistory?name=%t",
                        pWiki->zWikiTitle);
  style_submenu_element("Page", "Page", "wiki?name=%t",
                        pWiki->zWikiTitle);

  login_anonymous_available();



  @ <div class="section">Overview</div>

  @ <p><table class="label-value">



  @ <tr><th>Artifact&nbsp;ID:</th>

  @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
  if( g.perm.Setup ){
    @ (%d(rid))
  }
  modPending = moderation_pending(rid);
  if( modPending ){



    @ <span class="modpending">*** Awaiting Moderator Approval ***</span>

  }
  @ </td></tr>






  @ <tr><th>Page&nbsp;Name:</th><td>%h(pWiki->zWikiTitle)</td></tr>
  @ <tr><th>Date:</th><td>
  hyperlink_to_date(zDate, "</td></tr>");
  @ <tr><th>Original&nbsp;User:</th><td>

  hyperlink_to_user(pWiki->zUser, zDate, "</td></tr>");
  if( pWiki->nParent>0 ){
    int i;



    @ <tr><th>Parent%s(pWiki->nParent==1?"":"s"):</th><td>

    for(i=0; i<pWiki->nParent; i++){
      char *zParent = pWiki->azParent[i];
      @ %z(href("info/%S",zParent))%s(zParent)</a>
    }
    @ </td></tr>
  }
  @ </table>

  if( g.perm.ModWiki && modPending ){



    @ <div class="section">Moderation</div>

    @ <blockquote>
    @ <form method="POST" action="%R/winfo/%s(zUuid)">
    @ <label><input type="radio" name="modaction" value="delete">



    @ Delete this change</label><br />

    @ <label><input type="radio" name="modaction" value="approve">



    @ Approve this change</label><br />




    @ <input type="submit" value="Submit">

    @ </form>
    @ </blockquote>
  }





  @ <div class="section">Content</div>

  blob_init(&wiki, pWiki->zWiki, -1);
  wiki_convert(&wiki, 0, 0);
  blob_reset(&wiki);
  manifest_destroy(pWiki);
  style_footer();
}

................................................................................
*/
void webpage_error(const char *zFormat, ...){
  va_list ap;
  const char *z;
  va_start(ap, zFormat);
  z = vmprintf(zFormat, ap);
  va_end(ap);




  style_header("URL Error");
  @ <h1>Error</h1>

  @ <p>%h(z)</p>
  style_footer();
}

/*
** Find an checkin based on query parameter zParam and parse its
** manifest.  Return the number of errors.
*/
static Manifest *vdiff_parse_manifest(const char *zParam, int *pRid){
  int rid;

  *pRid = rid = name_to_rid_www(zParam);
  if( rid==0 ){
    const char *z = P(zParam);
    if( z==0 || z[0]==0 ){




      webpage_error("Missing \"%s\" query parameter.", zParam);


    }else{




      webpage_error("No such artifact: \"%s\"", z);


    }
    return 0;
  }
  if( !is_a_version(rid) ){




    webpage_error("Artifact %s is not a checkin.", P(zParam));


    return 0;
  }
  return manifest_get(rid, CFTYPE_MANIFEST);
}

/*
** Output a description of a check-in
................................................................................
      wikiFlags |= WIKI_NOBLOCK;
    }
    hyperlink_to_uuid(zUuid);
    blob_zero(&comment);
    db_column_blob(&q, 2, &comment);
    wiki_convert(&comment, 0, wikiFlags);
    blob_reset(&comment);



    @ (user:

    hyperlink_to_user(zUser,zDate,",");
    if( zTagList && zTagList[0] && g.perm.Hyperlink ){
      int i;
      const char *z = zTagList;
      Blob links;
      blob_zero(&links);
      while( z && z[0] ){
................................................................................
        blob_appendf(&links,
              "%z%#h</a>%.2s",
              href("%R/timeline?r=%#t&nd&c=%t",i,z,zDate), i,z, &z[i]
        );
        if( z[i]==0 ) break;
        z += i+2;
      }



      @ tags: %s(blob_str(&links)),

      blob_reset(&links);
    }else{

      @ tags: %h(zTagList),



    }



    @ date:

    hyperlink_to_date(zDate, ")");
  }
  db_finalize(&q);
}


/*
................................................................................
  if( pFrom==0 ) return;
  sideBySide = atoi(PD("sbs","1"));
  showDetail = atoi(PD("detail","0"));
  if( !showDetail && sideBySide ) showDetail = 1;
  zFrom = P("from");
  zTo = P("to");
  if( !sideBySide ){




    style_submenu_element("Side-by-side Diff", "sbsdiff",

                          "%R/vdiff?from=%T&to=%T&detail=%d&sbs=1",
                          zFrom, zTo, showDetail);
  }else{




    style_submenu_element("Unified Diff", "udiff",

                          "%R/vdiff?from=%T&to=%T&detail=%d&sbs=0",
                          zFrom, zTo, showDetail);
  }
  style_submenu_element("Invert", "invert",
                        "%R/vdiff?from=%T&to=%T&detail=%d&sbs=%d",
                        zTo, zFrom, showDetail, sideBySide);





  style_header("Check-in Differences");
  @ <h2>Difference From:</h2><blockquote>
  checkin_description(ridFrom);

  @ </blockquote><h2>To:</h2><blockquote>






  checkin_description(ridTo);
  @ </blockquote>
  if( pRe ){
    @ <p><b>Only differences that match regular expression "%h(zRe)"
    @ are shown.</b></p>
  }
  @<hr /><p>
................................................................................
  if( zRe ) re_compile(&pRe, zRe, 0);
  content_get(v1, &c1);
  content_get(v2, &c2);
  text_diff(&c1, &c2, pOut, pRe, diffFlags);
  blob_reset(&c1);
  blob_reset(&c2);
  if( !isPatch ){



    style_header("Diff");







    style_submenu_element("Patch", "Patch", "%s/fdiff?v1=%T&v2=%T&patch",
                          g.zTop, P("v1"), P("v2"));
    if( !sideBySide ){




      style_submenu_element("Side-by-side Diff", "sbsdiff",

                            "%s/fdiff?v1=%T&v2=%T&sbs=1",
                            g.zTop, P("v1"), P("v2"));
    }else{




      style_submenu_element("Unified Diff", "udiff",

                            "%s/fdiff?v1=%T&v2=%T&sbs=0",
                            g.zTop, P("v1"), P("v2"));
    }

    if( P("smhdr")!=0 ){



      @ <h2>Differences From Artifact

      @ %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a> To





      @ %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>.</h2>
    }else{



      @ <h2>Differences From

      @ Artifact %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a>:</h2>
      object_description(v1, 0, 0);






      @ <h2>To Artifact %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>:</h2>
      object_description(v2, 0, 0);
    }
    if( pRe ){
      @ <b>Only differences that match regular expression "%h(zRe)"
      @ are shown.</b>
    }
    @ <hr />
................................................................................
  rid = name_to_rid_www("name");
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  if( rid==0 ) fossil_redirect_home();
  if( g.perm.Admin ){
    const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
    if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){






      style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1",
            g.zTop, zUuid);
    }else{






      style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun",
            g.zTop, zUuid);
    }
  }




  style_header("Hex Artifact Content");


  zUuid = db_text("?","SELECT uuid FROM blob WHERE rid=%d", rid);
  if( g.perm.Setup ){





    @ <h2>Artifact %s(zUuid) (%d(rid)):</h2>
  }else{
    @ <h2>Artifact %s(zUuid):</h2>

  }
  blob_zero(&downloadName);
  object_description(rid, 0, &downloadName);



  style_submenu_element("Download", "Download",

        "%s/raw/%T?name=%s", g.zTop, blob_str(&downloadName), zUuid);
  @ <hr />
  content_get(rid, &content);
  @ <blockquote><pre>
  hexdump(&content);
  @ </pre></blockquote>
  style_footer();
................................................................................
      style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1",
            g.zTop, zUuid);
    }else{
      style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun",
            g.zTop, zUuid);
    }
  }



  style_header("Artifact Content");

  zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);
  if( g.perm.Setup ){





    @ <h2>Artifact %s(zUuid) (%d(rid)):</h2>
  }else{
    @ <h2>Artifact %s(zUuid):</h2>

  }

  blob_zero(&downloadName);
  objType = object_description(rid, 0, &downloadName);



  style_submenu_element("Download", "Download",

          "%R/raw/%T?name=%s", blob_str(&downloadName), zUuid);
  if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){



    style_submenu_element("Checkins Using", "Checkins Using",

          "%R/timeline?uf=%s&n=200",zUuid);
  }
  asText = P("txt")!=0;
  zMime = mimetype_from_name(blob_str(&downloadName));
  if( zMime ){
    if( fossil_strcmp(zMime, "text/html")==0 ){
      if( asText ){
        style_submenu_element("Html", "Html",
                              "%s/artifact/%s", g.zTop, zUuid);
      }else{
        renderAsHtml = 1;
        style_submenu_element("Text", "Text",





                              "%s/artifact/%s?txt=1", g.zTop, zUuid);
      }
    }else if( fossil_strcmp(zMime, "application/x-fossil-wiki")==0 ){
      if( asText ){
        style_submenu_element("Wiki", "Wiki",





                              "%s/artifact/%s", g.zTop, zUuid);
      }else{
        renderAsWiki = 1;
        style_submenu_element("Text", "Text",





                              "%s/artifact/%s?txt=1", g.zTop, zUuid);
      }
    }
  }
  if( (objType & (OBJTYPE_WIKI|OBJTYPE_TICKET))!=0 ){
    style_submenu_element("Parsed", "Parsed", "%R/info/%s", zUuid);
  }
................................................................................
        @ </pre>
      }
    }else if( strncmp(zMime, "image/", 6)==0 ){
      @ <img src="%R/raw/%S(zUuid)?m=%s(zMime)" />
      style_submenu_element("Image", "Image",
                            "%R/raw/%S?m=%s", zUuid, zMime);
    }else{



      @ <i>(file is %d(blob_size(&content)) bytes of binary data)</i>

    }
    @ </blockquote>
  }
  style_footer();
}

/*






|
>
>
>
>
>
>
>
>
>
>
>
>













|
>
>
>
>
>
>
>
>
>
>
>
>











>
>
>
>
|
>
>



>
>
>
>
|
>
>







 







>
>
>
>
|
>
>
>
>
>

>







 







|
>
>
>
>
>
>







 







>
>
>

>

>
>
>
>
>
>
|

>
>
>

>


>
>
>
|
>


>
>
>

>



>
>
>

>

>
>
>

>


>
>
>

>



>
>
>
>


>

>

>
>
>













>
>
>
>


>





>
>
>
>
>
>
>
>
>
>
>
>
>











>







 







|




>
>
>

>


|
>
>
>
>
>
>





>

>
>
>

>






>
>
>
>
|
>
>





>
>
>

>






>
>
>

>


>
>
>

>


>
>
>

>



>
>
>

>

>
>
>

>



|
>
>
>
>
>


>
>
>

>


>
>
>

>



>
>
>

>

>
>
>

>


>

>
>
>
|
>
>
>







 







>
>
>
>
|
>
>
>
>
>

>













>
>
>

>


>
>
>
>
>
>
>





>

>
>
>

>

>
>
>

>






>
>
>

>


>
>
>
>
>
>




>



>
>
>

>









>
>
>

>



>
>
>

>

>
>
>

>
>
>
>

>





>
>
>

>







 







>
>
>
>


>













|
|
>
>
>
>
|
>
>
|
>
>
>
>
|
>
>
|
|


>
>
>
>
|
>
>







 







>
>
>

>







 







>
>
>

>


>
|
>
>
>

>
>
>

>







 







>
>
>
>
|
>



>
>
>
>
|
>






>
>
>
>
>



>
|
>
>
>
>
>
>







 







>
>
>

>
>
>
>
>
>
>
|


>
>
>
>
|
>



>
>
>
>
|
>





>
>
>

>
|
>
>
>
>
>


>
>
>
|
>
|

>
>
>
>
>
>
|







 







>
>
>
>
>
>
|


>
>
>
>
>
>
|



>
>
>
>
|
>
>


>
>
>
>
>



>



>
>
>

>







 







>
>
>

>


>
>
>
>
>



>

>


>
>
>

>


>
>
>

>











|
>
>
>
>
>




|
>
>
>
>
>



|
>
>
>
>
>







 







>
>
>

>







83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
...
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
...
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
...
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
...
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
...
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
....
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
....
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
....
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
....
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
....
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
....
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
....
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
....
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
  }
  if( showFamily ){
    db_prepare(&q, "SELECT uuid, pid, isprim FROM plink JOIN blob ON pid=rid "
                   " WHERE cid=%d"
                   " ORDER BY isprim DESC, mtime DESC /*sort*/", rid);
    while( db_step(&q)==SQLITE_ROW ){
      const char *zUuid = db_column_text(&q, 0);
      const char *zType = db_column_int(&q, 2) ?
#ifdef LANG_RU
        "родитель:"
#elif LANG_EN
        "parent:"
#endif
        :
#ifdef LANG_RU
      "слито с:"
#elif LANG_EN
        "merged-from:"
#endif
          ;
      zDate = db_text("",
        "SELECT datetime(mtime) || ' UTC' FROM event WHERE objid=%d",
        db_column_int(&q, 1)
      );
      fossil_print("%-13s %s %s\n", zType, zUuid, zDate);
      free(zDate);
    }
    db_finalize(&q);
    db_prepare(&q, "SELECT uuid, cid, isprim FROM plink JOIN blob ON cid=rid "
                   " WHERE pid=%d"
                   " ORDER BY isprim DESC, mtime DESC /*sort*/", rid);
    while( db_step(&q)==SQLITE_ROW ){
      const char *zUuid = db_column_text(&q, 0);
      const char *zType = db_column_int(&q, 2) ?
#ifdef LANG_RU
        "потомок:"
#elif LANG_EN
        "child:"
#endif
        :
#ifdef LANG_RU
      "слито в:"
#elif LANG_EN
        "merged-into:"
#endif
        ;
      zDate = db_text("",
        "SELECT datetime(mtime) || ' UTC' FROM event WHERE objid=%d",
        db_column_int(&q, 1)
      );
      fossil_print("%-13s %s %s\n", zType, zUuid, zDate);
      free(zDate);
    }
    db_finalize(&q);
  }
  zTags = info_tags_of_checkin(rid, 0);
  if( zTags && zTags[0] ){
    fossil_print(
#ifdef LANG_RU
      "теги:         %s\n",
#elif LANG_EN
      "tags:         %s\n",
#endif
      zTags);
  }
  free(zTags);
  if( zComment ){
    fossil_print(
#ifdef LANG_RU
      "комментарий:      "
#elif LANG_EN
      "comment:      "
#endif
      );
    comment_print(zComment, 14, 79);
    free(zComment);
  }
}

/*
** Print information about the URLs used to access a repository and
................................................................................
  ReCompiled *pRe = 0; /* regex */

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  zName = P("name");
  rid = name_to_rid_www("name");
  if( rid==0 ){
    style_header(
#ifdef LANG_RU
      "Ошибка информации о фиксации"
#elif LANG_EN
      "Check-in Information Error"
#endif
      );
#ifdef LANG_RU
    @ Объект не найден: %h(g.argv[2])
#elif LANG_EN
    @ No such object: %h(g.argv[2])
#endif
    style_footer();
    return;
  }
  zRe = P("regex");
  if( zRe ) re_compile(&pRe, zRe, 0);
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
  zParent = db_text(0,
................................................................................
     " WHERE blob.rid=%d"
     "   AND event.objid=%d",
     rid, rid
  );
  sideBySide = atoi(PD("sbs","1"));
  if( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);
    char *zTitle = mprintf(
#ifdef LANG_RU
      "Фиксация [%.10s]",
#elif LANG_EN
      "Check-in [%.10s]",
#endif
      zUuid);
    char *zEUser, *zEComment;
    const char *zUser;
    const char *zComment;
    const char *zDate;
    const char *zOrigDate;

    style_header(zTitle);
................................................................................
    zEComment = db_text(0,
                   "SELECT value FROM tagxref WHERE tagid=%d AND rid=%d",
                   TAG_COMMENT, rid);
    zUser = db_column_text(&q, 2);
    zComment = db_column_text(&q, 3);
    zDate = db_column_text(&q,1);
    zOrigDate = db_column_text(&q, 4);
#ifdef LANG_RU
    @ <div class="section">Обзор</div>
#elif LANG_EN
    @ <div class="section">Overview</div>
#endif
    @ <table class="label-value">
    @ <tr><th>
#ifdef LANG_RU
    @ SHA1&nbsp;хэш:
#elif LANG_EN
    @ SHA1&nbsp;Hash:
#endif
    @ </th><td>%s(zUuid)
    if( g.perm.Setup ){
#ifdef LANG_RU
      @ (ID записи: %d(rid))
#elif LANG_EN
      @ (Record ID: %d(rid))
#endif
    }
    @ </td></tr>
#ifdef LANG_RU
      @ <tr><th>Дата:</th><td>
#elif LANG_EN
      @ <tr><th>Date:</th><td>
#endif
    hyperlink_to_date(zDate, "</td></tr>");
    if( zOrigDate && fossil_strcmp(zDate, zOrigDate)!=0 ){
#ifdef LANG_RU
      @ <tr><th>Оригинальная&nbsp;дата:</th><td>
#elif LANG_EN
      @ <tr><th>Original&nbsp;Date:</th><td>
#endif
      hyperlink_to_date(zOrigDate, "</td></tr>");
    }
    if( zEUser ){
#ifdef LANG_RU
      @ <tr><th>Исправлявший&nbsp;пользователь:</th><td>
#elif LANG_EN
      @ <tr><th>Edited&nbsp;User:</th><td>
#endif
      hyperlink_to_user(zEUser,zDate,"</td></tr>");
#ifdef LANG_RU
      @ <tr><th>Первый&nbsp;пользователь:</th><td>
#elif LANG_EN
      @ <tr><th>Original&nbsp;User:</th><td>
#endif
      hyperlink_to_user(zUser,zDate,"</td></tr>");
    }else{
#ifdef LANG_RU
      @ <tr><th>Пользователь:</th><td>
#elif LANG_EN
      @ <tr><th>User:</th><td>
#endif
      hyperlink_to_user(zUser,zDate,"</td></tr>");
    }
    if( zEComment ){
#ifdef LANG_RU
      @ <tr><th>Исправленный&nbsp;комментарий:</th><td>%w(zEComment)</td></tr>
      @ <tr><th>Первоначальный&nbsp;комментарий:</th><td>%w(zComment)</td></tr>
#elif LANG_EN
      @ <tr><th>Edited&nbsp;Comment:</th><td>%w(zEComment)</td></tr>
      @ <tr><th>Original&nbsp;Comment:</th><td>%w(zComment)</td></tr>
#endif
    }else{
#ifdef LANG_RU
      @ <tr><th>Comment:</th><td>%w(zComment)</td></tr>
#elif LANG_EN
      @ <tr><th>Comment:</th><td>%w(zComment)</td></tr>
#endif
    }
    if( g.perm.Admin ){
      db_prepare(&q,
         "SELECT rcvfrom.ipaddr, user.login, datetime(rcvfrom.mtime)"
         "  FROM blob JOIN rcvfrom USING(rcvid) LEFT JOIN user USING(uid)"
         " WHERE blob.rid=%d",
         rid
      );
      if( db_step(&q)==SQLITE_ROW ){
        const char *zIpAddr = db_column_text(&q, 0);
        const char *zUser = db_column_text(&q, 1);
        const char *zDate = db_column_text(&q, 2);
        if( zUser==0 || zUser[0]==0 ) zUser = "unknown";
#ifdef LANG_RU
        @ <tr><th>Получено&nbsp;от:</th>
        @ <td>%h(zUser) @ %h(zIpAddr) %s(zDate)</td></tr>
#elif LANG_EN
        @ <tr><th>Received&nbsp;From:</th>
        @ <td>%h(zUser) @ %h(zIpAddr) on %s(zDate)</td></tr>
#endif
      }
      db_finalize(&q);
    }
    if( g.perm.Hyperlink ){
      const char *zProjName = db_get("project-name", "unnamed");
#ifdef LANG_RU
      @ <tr><th>События:</th><td>
      @   %z(href("%R/timeline?f=%S",zUuid))ближние</a>
      if( zParent ){
        @ | %z(href("%R/timeline?p=%S",zUuid))родители</a>
      }
      if( !isLeaf ){
        @ | %z(href("%R/timeline?d=%S",zUuid))потомки</a>
      }
      if( zParent && !isLeaf ){
        @ | %z(href("%R/timeline?dp=%S",zUuid))обои</a>
      }
#elif LANG_EN
      @ <tr><th>Timelines:</th><td>
      @   %z(href("%R/timeline?f=%S",zUuid))family</a>
      if( zParent ){
        @ | %z(href("%R/timeline?p=%S",zUuid))ancestors</a>
      }
      if( !isLeaf ){
        @ | %z(href("%R/timeline?d=%S",zUuid))descendants</a>
      }
      if( zParent && !isLeaf ){
        @ | %z(href("%R/timeline?dp=%S",zUuid))both</a>
      }
#endif
      db_prepare(&q, "SELECT substr(tag.tagname,5) FROM tagxref, tag "
                     " WHERE rid=%d AND tagtype>0 "
                     "   AND tag.tagid=tagxref.tagid "
                     "   AND +tag.tagname GLOB 'sym-*'", rid);
      while( db_step(&q)==SQLITE_ROW ){
        const char *zTagName = db_column_text(&q, 0);
        @  | %z(href("%R/timeline?r=%T",zTagName))%h(zTagName)</a>
................................................................................
      }
      db_finalize(&q);


      /* The Download: line */
      if( g.perm.Zip ){
        char *zUrl = mprintf("%R/tarball/%t-%S.tar.gz?uuid=%s",
          zProjName, zUuid, zUuid);
        @ </td></tr>
        @ <tr><th>Downloads:</th><td>
        @ %z(href("%s",zUrl))Tarball</a>
        @ | %z(href("%R/zip/%t-%S.zip?uuid=%s",zProjName,zUuid,zUuid))
#ifdef LANG_RU
        @         ZIP архив</a>
#elif LANG_EN
        @         ZIP archive</a>
#endif
        fossil_free(zUrl);
      }
	  @ </td></tr>
#ifdef LANG_RU
		  @ <tr><th>Другие&nbsp;ссылки:</th>
		  @   <td>
		  @     %z(href("%R/dir?ci=%S",zUuid))файлы</a>
		  @   | %z(href("%R/artifact/%S",zUuid))манифест</a>
#elif LANG_EN
      @ <tr><th>Other&nbsp;Links:</th>
      @   <td>
      @     %z(href("%R/dir?ci=%S",zUuid))files</a>
      @   | %z(href("%R/fileage?name=%S",zUuid))file ages</a>
      @   | %z(href("%R/artifact/%S",zUuid))manifest</a>
#endif
      if( g.perm.Write ){
#ifdef LANG_RU
        @   | %z(href("%R/ci_edit?r=%S",zUuid))изменить</a>
#elif LANG_EN
        @   | %z(href("%R/ci_edit?r=%S",zUuid))edit</a>
#endif
      }
      @   </td>
      @ </tr>
    }
    @ </table>
  }else{
    style_header(
#ifdef LANG_RU
      "Информация о фиксации"
#elif LANG_EN
      "Check-in Information"
#endif
      );
    login_anonymous_available();
  }
  db_finalize(&q);
  showTags(rid, "");
  if( zParent ){
#ifdef LANG_RU
    @ <div class="section">Изменения</div>
#elif LANG_EN
    @ <div class="section">Changes</div>
#endif
    @ <div class="sectionmenu">
    showDiff = g.zPath[0]!='c';
    if( db_get_boolean("show-version-diffs", 0)==0 ){
      showDiff = !showDiff;
      if( showDiff ){
        @ %z(xhref("class='button'","%R/vinfo/%T",zName))
#ifdef LANG_RU
        @ спрятать&nbsp;различия</a>
#elif LANG_EN
        @ hide&nbsp;diffs</a>
#endif
        if( sideBySide ){
          @ %z(xhref("class='button'","%R/ci/%T?sbs=0",zName))
#ifdef LANG_RU
          @ различия&nbsp;разом</a>
#elif LANG_EN
          @ unified&nbsp;diffs</a>
#endif
        }else{
          @ %z(xhref("class='button'","%R/ci/%T?sbs=1",zName))
#ifdef LANG_RU
          @ различия&nbsp;бок-о-бок</a>
#elif LANG_EN
          @ side-by-side&nbsp;diffs</a>
#endif
        }
      }else{
        @ %z(xhref("class='button'","%R/ci/%T?sbs=0",zName))
#ifdef LANG_RU
        @ показать&nbsp;различия&nbsp;разом</a>
#elif LANG_EN
        @ show&nbsp;unified&nbsp;diffs</a>
#endif
        @ %z(xhref("class='button'","%R/ci/%T?sbs=1",zName))
#ifdef LANG_RU
        @ показать&nbsp;различия&nbsp;бок-о-бок</a>
#elif LANG_EN
        @ show&nbsp;side-by-side&nbsp;diffs</a>
#endif
      }
    }else{
      if( showDiff ){
        @ %z(xhref("class='button'","%R/ci/%T",zName))
#ifdef LANG_RU
        @ спрятать&nbsp;различия</a>
#elif LANG_EN
        @ hide&nbsp;diffs</a>
#endif
        if( sideBySide ){
          @ %z(xhref("class='button'","%R/info/%T?sbs=0",zName))
#ifdef LANG_RU
          @ различия&nbsp;разом</a>
#elif LANG_EN
          @ unified&nbsp;diffs</a>
#endif
        }else{
          @ %z(xhref("class='button'","%R/info/%T?sbs=1",zName))
#ifdef LANG_RU
          @ различия&nbsp;бок-о-бок</a>
#elif LANG_EN
          @ side-by-side&nbsp;diffs</a>
#endif
        }
      }else{
        @ %z(xhref("class='button'","%R/vinfo/%T?sbs=0",zName))
#ifdef LANG_RU
          @ показать&nbsp;различия&nbsp;разом</a>
#elif LANG_EN
        @ show&nbsp;unified&nbsp;diffs</a>
#endif
        @ %z(xhref("class='button'","%R/vinfo/%T?sbs=1",zName))
#ifdef LANG_RU
        @ показать&nbsp;различия&nbsp;бок-о-бок</a>
#elif LANG_EN
        @ show&nbsp;side-by-side&nbsp;diffs</a>
#endif
      }
    }
    @ &nbsp;&nbsp;
    @ %z(xhref("class='button'","%R/vpatch?from=%S&to=%S",zParent,zUuid))
#ifdef LANG_RU
    @ патч</a>
#elif LANG_EN
    @ patch</a>
#endif
    @</div>
    
    if( pRe ){
      @ <p><b>Only differences that match regular expression "%h(zRe)"
      @ are shown.</b></p>
    }
    db_prepare(&q,
       "SELECT name,"
       "       mperm,"
................................................................................
  int modPending;
  const char *zModAction;

  login_check_credentials();
  if( !g.perm.RdWiki ){ login_needed(); return; }
  rid = name_to_rid_www("name");
  if( rid==0 || (pWiki = manifest_get(rid, CFTYPE_WIKI))==0 ){
    style_header(
#ifdef LANG_RU
      "Ошибка информации о странице"
#elif LANG_EN
      "Wiki Page Information Error"
#endif
      );
#ifdef LANG_RU
    @ Объект не найден: %h(g.argv[2])
#elif LANG_EN
    @ No such object: %h(P("name"))
#endif
    style_footer();
    return;
  }
  if( g.perm.ModWiki && (zModAction = P("modaction"))!=0 ){
    if( strcmp(zModAction,"delete")==0 ){
      moderation_disapprove(rid);
      cgi_redirectf("%R/wiki?name=%T", pWiki->zWikiTitle);
      /*NOTREACHED*/
    }
    if( strcmp(zModAction,"approve")==0 ){
      moderation_approve(rid);
    }
  }
#ifdef LANG_RU
  style_header("Правка \"%h\"", pWiki->zWikiTitle);
#elif LANG_EN
  style_header("Update of \"%h\"", pWiki->zWikiTitle);
#endif
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
  zDate = db_text(0, "SELECT datetime(%.17g)", pWiki->rDate);
#ifdef LANG_RU
  style_submenu_element("Как есть", "Как есть", "artifact/%S", zUuid);
  style_submenu_element("История", "История", "whistory?name=%t",
                        pWiki->zWikiTitle);
  style_submenu_element("Страница", "Страница", "wiki?name=%t",
                        pWiki->zWikiTitle);
#elif LANG_EN
  style_submenu_element("Raw", "Raw", "artifact/%S", zUuid);
  style_submenu_element("History", "History", "whistory?name=%t",
                        pWiki->zWikiTitle);
  style_submenu_element("Page", "Page", "wiki?name=%t",
                        pWiki->zWikiTitle);
#endif
  login_anonymous_available();
#ifdef LANG_RU
  @ <div class="section">Обзор</div>
#elif LANG_EN
  @ <div class="section">Overview</div>
#endif
  @ <p><table class="label-value">
#ifdef LANG_RU
  @ <tr><th>ID&nbsp;артефакта:</th>
#elif LANG_EN
  @ <tr><th>Artifact&nbsp;ID:</th>
#endif
  @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
  if( g.perm.Setup ){
    @ (%d(rid))
  }
  modPending = moderation_pending(rid);
  if( modPending ){
#ifdef LANG_RU
    @ <span class="modpending">*** Ожидает утверждения модератором ***</span>
#elif LANG_EN
    @ <span class="modpending">*** Awaiting Moderator Approval ***</span>
#endif
  }
  @ </td></tr>
#ifdef LANG_RU
  @ <tr><th>Имя&nbsp;страницы:</th><td>%h(pWiki->zWikiTitle)</td></tr>
  @ <tr><th>Дата:</th><td>
  hyperlink_to_date(zDate, "</td></tr>");
  @ <tr><th>Создавший&nbsp;пользователь:</th><td>
#elif LANG_EN
  @ <tr><th>Page&nbsp;Name:</th><td>%h(pWiki->zWikiTitle)</td></tr>
  @ <tr><th>Date:</th><td>
  hyperlink_to_date(zDate, "</td></tr>");
  @ <tr><th>Original&nbsp;User:</th><td>
#endif
  hyperlink_to_user(pWiki->zUser, zDate, "</td></tr>");
  if( pWiki->nParent>0 ){
    int i;
#ifdef LANG_RU
    @ <tr><th>Родител%s(pWiki->nParent==1?"ь":"и"):</th><td>
#elif LANG_EN
    @ <tr><th>Parent%s(pWiki->nParent==1?"":"s"):</th><td>
#endif
    for(i=0; i<pWiki->nParent; i++){
      char *zParent = pWiki->azParent[i];
      @ %z(href("info/%S",zParent))%s(zParent)</a>
    }
    @ </td></tr>
  }
  @ </table>

  if( g.perm.ModWiki && modPending ){
#ifdef LANG_RU
    @ <div class="section">Модерация</div>
#elif LANG_EN
    @ <div class="section">Moderation</div>
#endif
    @ <blockquote>
    @ <form method="POST" action="%R/winfo/%s(zUuid)">
    @ <label><input type="radio" name="modaction" value="delete">
#ifdef LANG_RU
    @ Удалить это изменение</label><br />
#elif LANG_EN
    @ Delete this change</label><br />
#endif
    @ <label><input type="radio" name="modaction" value="approve">
#ifdef LANG_RU
    @ Утвердить это изменение</label><br />
#elif LANG_EN
    @ Approve this change</label><br />
#endif
#ifdef LANG_RU
    @ <input type="submit" value="Ок">
#elif LANG_EN
    @ <input type="submit" value="Submit">
#endif
    @ </form>
    @ </blockquote>
  }


#ifdef LANG_RU
  @ <div class="section">Содержимое</div>
#elif LANG_EN
  @ <div class="section">Content</div>
#endif
  blob_init(&wiki, pWiki->zWiki, -1);
  wiki_convert(&wiki, 0, 0);
  blob_reset(&wiki);
  manifest_destroy(pWiki);
  style_footer();
}

................................................................................
*/
void webpage_error(const char *zFormat, ...){
  va_list ap;
  const char *z;
  va_start(ap, zFormat);
  z = vmprintf(zFormat, ap);
  va_end(ap);
#ifdef LANG_RU
  style_header("Ошибка URL");
  @ <h1>Ошибка</h1>
#elif LANG_EN
  style_header("URL Error");
  @ <h1>Error</h1>
#endif
  @ <p>%h(z)</p>
  style_footer();
}

/*
** Find an checkin based on query parameter zParam and parse its
** manifest.  Return the number of errors.
*/
static Manifest *vdiff_parse_manifest(const char *zParam, int *pRid){
  int rid;

  *pRid = rid = name_to_rid_www(zParam);
  if( rid==0 ){
	  const char *z = P(zParam);
	  if( z==0 || z[0]==0 ){
		  webpage_error(
#ifdef LANG_RU
			  "Отсутствует параметр \"%s\" в запросе."
#elif LANG_EN
			  "Missing \"%s\" query parameter."
#endif
			  , zParam);
	  }else{
		  webpage_error(
#ifdef LANG_RU
			  "Артефакт не найден: \"%s\""
#elif LANG_EN
			  "No such artifact: \"%s\""
#endif
			  , z);
	  }
      return 0;
  }
  if( !is_a_version(rid) ){
    webpage_error(
#ifdef LANG_RU
      "Артефакт не Фиксация.",
#elif LANG_EN
      "Artifact %s is not a checkin.",
#endif
      P(zParam));
    return 0;
  }
  return manifest_get(rid, CFTYPE_MANIFEST);
}

/*
** Output a description of a check-in
................................................................................
      wikiFlags |= WIKI_NOBLOCK;
    }
    hyperlink_to_uuid(zUuid);
    blob_zero(&comment);
    db_column_blob(&q, 2, &comment);
    wiki_convert(&comment, 0, wikiFlags);
    blob_reset(&comment);
#ifdef LANG_RU
    @ (пользователь:
#elif LANG_EN
    @ (user:
#endif
    hyperlink_to_user(zUser,zDate,",");
    if( zTagList && zTagList[0] && g.perm.Hyperlink ){
      int i;
      const char *z = zTagList;
      Blob links;
      blob_zero(&links);
      while( z && z[0] ){
................................................................................
        blob_appendf(&links,
              "%z%#h</a>%.2s",
              href("%R/timeline?r=%#t&nd&c=%t",i,z,zDate), i,z, &z[i]
        );
        if( z[i]==0 ) break;
        z += i+2;
      }
#ifdef LANG_RU
      @ метка: %s(blob_str(&links)),
#elif LANG_EN
      @ tags: %s(blob_str(&links)),
#endif
      blob_reset(&links);
    }else{
#ifdef LANG_RU
      @ метка: %h(zTagList),
#elif LANG_EN
      @ метка: %h(zTagList),
#endif
    }
#ifdef LANG_RU
    @ дата:
#elif LANG_EN
    @ date:
#endif
    hyperlink_to_date(zDate, ")");
  }
  db_finalize(&q);
}


/*
................................................................................
  if( pFrom==0 ) return;
  sideBySide = atoi(PD("sbs","1"));
  showDetail = atoi(PD("detail","0"));
  if( !showDetail && sideBySide ) showDetail = 1;
  zFrom = P("from");
  zTo = P("to");
  if( !sideBySide ){
    style_submenu_element(
#ifdef LANG_RU
      "Различия бок-о-бок", "sbsdiff",
#elif LANG_EN
      "Side-by-side Diff", "sbsdiff",
#endif
                          "%R/vdiff?from=%T&to=%T&detail=%d&sbs=1",
                          zFrom, zTo, showDetail);
  }else{
    style_submenu_element(
#ifdef LANG_RU
      "Различия разом", "udiff",
#elif LANG_EN
      "Unified Diff", "udiff",
#endif
                          "%R/vdiff?from=%T&to=%T&detail=%d&sbs=0",
                          zFrom, zTo, showDetail);
  }
  style_submenu_element("Invert", "invert",
                        "%R/vdiff?from=%T&to=%T&detail=%d&sbs=%d",
                        zTo, zFrom, showDetail, sideBySide);
#ifdef LANG_RU
  style_header("Различия в фиксации");
  @ <h2>Различия между:</h2><blockquote>
  checkin_description(ridFrom);
#elif LANG_EN
  style_header("Check-in Differences");
  @ <h2>Difference From:</h2><blockquote>
  checkin_description(ridFrom);
#endif
  @ </blockquote><h2>
#ifdef LANG_RU
    @ и:
#elif LANG_EN
    @ To:
#endif
  @ </h2><blockquote>
  checkin_description(ridTo);
  @ </blockquote>
  if( pRe ){
    @ <p><b>Only differences that match regular expression "%h(zRe)"
    @ are shown.</b></p>
  }
  @<hr /><p>
................................................................................
  if( zRe ) re_compile(&pRe, zRe, 0);
  content_get(v1, &c1);
  content_get(v2, &c2);
  text_diff(&c1, &c2, pOut, pRe, diffFlags);
  blob_reset(&c1);
  blob_reset(&c2);
  if( !isPatch ){
#ifdef LANG_RU
    style_header("Различия");
#elif LANG_EN
    style_header("Diff");
#endif
    style_submenu_element(
#ifdef LANG_RU
      "Патч", "Патч",
#elif LANG_EN
      "Patch", "Patch",
#endif
      "%s/fdiff?v1=%T&v2=%T&patch",
                          g.zTop, P("v1"), P("v2"));
    if( !sideBySide ){
      style_submenu_element(
#ifdef LANG_RU
        "Различия бок-о-бок", "sbsdiff",
#elif LANG_EN
        "Side-by-side Diff", "sbsdiff",
#endif
                            "%s/fdiff?v1=%T&v2=%T&sbs=1",
                            g.zTop, P("v1"), P("v2"));
    }else{
      style_submenu_element(
#ifdef LANG_RU
        "Различия разом", "udiff",
#elif LANG_EN
        "Unified Diff", "udiff",
#endif
                            "%s/fdiff?v1=%T&v2=%T&sbs=0",
                            g.zTop, P("v1"), P("v2"));
    }

    if( P("smhdr")!=0 ){
#ifdef LANG_RU
      @ <h2>Различия между артефактом
#elif LANG_EN
      @ <h2>Differences From Artifact
#endif
      @ %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a>
#ifdef LANG_RU
      @ и
#elif LANG_EN
      @ To
#endif
      @ %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>.</h2>
    }else{
#ifdef LANG_RU
      @ <h2>Различия между артефактом
#elif LANG_EN
      @ <h2>Differences From Artifact
#endif
      @ %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a>:</h2>
      object_description(v1, 0, 0);
      @ <h2>
#ifdef LANG_RU
        @ и артефактом
#elif LANG_EN
        @ To Artifact
#endif
      @ %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>:</h2>
      object_description(v2, 0, 0);
    }
    if( pRe ){
      @ <b>Only differences that match regular expression "%h(zRe)"
      @ are shown.</b>
    }
    @ <hr />
................................................................................
  rid = name_to_rid_www("name");
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  if( rid==0 ) fossil_redirect_home();
  if( g.perm.Admin ){
    const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
    if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){
      style_submenu_element(
#ifdef LANG_RU
        "Восстановить","Восстановить",
#elif LANG_EN
        "Unshun","Unshun",
#endif
        "%s/shun?uuid=%s&sub=1",
            g.zTop, zUuid);
    }else{
      style_submenu_element(
#ifdef LANG_RU
        "Стереть","Стереть",
#elif LANG_EN
        "Shun","Shun",
#endif
        "%s/shun?shun=%s#addshun",
            g.zTop, zUuid);
    }
  }
  style_header(
#ifdef LANG_RU
    "Содержимое бинарного артефакта"
#elif LANG_EN
    "Hex Artifact Content"
#endif
    );
  zUuid = db_text("?","SELECT uuid FROM blob WHERE rid=%d", rid);
  if( g.perm.Setup ){
#ifdef LANG_RU
    @ <h2>Артефакт %s(zUuid) (%d(rid)):</h2>
  }else{
    @ <h2>Artifact %s(zUuid):</h2>
#elif LANG_EN
    @ <h2>Artifact %s(zUuid) (%d(rid)):</h2>
  }else{
    @ <h2>Artifact %s(zUuid):</h2>
#endif
  }
  blob_zero(&downloadName);
  object_description(rid, 0, &downloadName);
#ifdef LANG_RU
  style_submenu_element("Скачать", "Скачать",
#elif LANG_EN
  style_submenu_element("Download", "Download",
#endif
        "%s/raw/%T?name=%s", g.zTop, blob_str(&downloadName), zUuid);
  @ <hr />
  content_get(rid, &content);
  @ <blockquote><pre>
  hexdump(&content);
  @ </pre></blockquote>
  style_footer();
................................................................................
      style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1",
            g.zTop, zUuid);
    }else{
      style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun",
            g.zTop, zUuid);
    }
  }
#ifdef LANG_RU
  style_header("Содержимое артефакта");
#elif LANG_EN
  style_header("Artifact Content");
#endif
  zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);
  if( g.perm.Setup ){
#ifdef LANG_RU
    @ <h2>Артефакт %s(zUuid) (%d(rid)):</h2>
  }else{
    @ <h2>Артефакт %s(zUuid):</h2>
#elif LANG_EN
    @ <h2>Artifact %s(zUuid) (%d(rid)):</h2>
  }else{
    @ <h2>Artifact %s(zUuid):</h2>
#endif
  }
  @ <blockquote><p>
  blob_zero(&downloadName);
  objType = object_description(rid, 0, &downloadName);
#ifdef LANG_RU
  style_submenu_element("Скачать", "Скачать",
#elif LANG_EN
  style_submenu_element("Download", "Download",
#endif
          "%R/raw/%T?name=%s", blob_str(&downloadName), zUuid);
  if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){
#ifdef LANG_RU
    style_submenu_element("Используется в фиксациях", "Используется в фиксациях",
#elif LANG_EN
    style_submenu_element("Checkins Using", "Checkins Using",
#endif
          "%R/timeline?uf=%s&n=200",zUuid);
  }
  asText = P("txt")!=0;
  zMime = mimetype_from_name(blob_str(&downloadName));
  if( zMime ){
    if( fossil_strcmp(zMime, "text/html")==0 ){
      if( asText ){
        style_submenu_element("Html", "Html",
                              "%s/artifact/%s", g.zTop, zUuid);
      }else{
        renderAsHtml = 1;
        style_submenu_element(
#ifdef LANG_RU
          "Текст", "Показать как текст",
#elif LANG_EN
          "Text", "Text",
#endif
                              "%s/artifact/%s?txt=1", g.zTop, zUuid);
      }
    }else if( fossil_strcmp(zMime, "application/x-fossil-wiki")==0 ){
      if( asText ){
        style_submenu_element(
#ifdef LANG_RU
          "Вики", "Показать как вики",
#elif LANG_EN
          "Wiki", "Wiki",
#endif
                              "%s/artifact/%s", g.zTop, zUuid);
      }else{
        renderAsWiki = 1;
        style_submenu_element(
#ifdef LANG_RU
          "Текст", "Показать как текст",
#elif LANG_EN
          "Text", "Text",
#endif
                              "%s/artifact/%s?txt=1", g.zTop, zUuid);
      }
    }
  }
  if( (objType & (OBJTYPE_WIKI|OBJTYPE_TICKET))!=0 ){
    style_submenu_element("Parsed", "Parsed", "%R/info/%s", zUuid);
  }
................................................................................
        @ </pre>
      }
    }else if( strncmp(zMime, "image/", 6)==0 ){
      @ <img src="%R/raw/%S(zUuid)?m=%s(zMime)" />
      style_submenu_element("Image", "Image",
                            "%R/raw/%S?m=%s", zUuid, zMime);
    }else{
#ifdef LANG_RU
      @ <i>(бинарный файл, байт: %d(blob_size(&content)))</i>
#elif LANG_EN
      @ <i>(file is %d(blob_size(&content)) bytes of binary data)</i>
#endif
    }
    @ </blockquote>
  }
  style_footer();
}

/*

Changes to src/login.c.

491
492
493
494
495
496
497

498
499






500
501
502

503
504

505
506






507
508
509

510
511
512
513
514
515
516
...
541
542
543
544
545
546
547

548
549





550
551

552
553
554
555
556
557
558
...
559
560
561
562
563
564
565




566


567
568
569
570
571
572
573
574
575
576
577






578
579
580
581
582
583
584
585






586
587
588
589
590
591
592
593
594



595

596
597
598
599
600
601
602
...
606
607
608
609
610
611
612

613











614
615
616
617
618
619
620
621
622


623




624
625

626

627
628
629
630
631
632
633




634
635

636
637
638
639



640

641
642
643
644
645
646
647





648
649
650

651
652
653
654














655
656
657
658
659
660
661
662
663
664
665
666
667

668
669
670
671
672
673
674
....
1143
1144
1145
1146
1147
1148
1149





1150
1151
1152

1153
1154
1155
1156
1157
1158
1159
    zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0);
    if( db_int(1, "SELECT 0 FROM user"
                  " WHERE uid=%d"
                  " AND (constant_time_cmp(pw,%Q)=0"
                  "      OR constant_time_cmp(pw,%Q)=0)", 
                  g.userUid, zSha1Pw, zPasswd) ){
      sleep(1);

      zErrMsg = 
         @ <p><span class="loginError">






         @ You entered an incorrect old password while attempting to change
         @ your password.  Your password is unchanged.
         @ </span></p>

      ;
    }else if( fossil_strcmp(zNew1,zNew2)!=0 ){

      zErrMsg = 
         @ <p><span class="loginError">






         @ The two copies of your new passwords do not match.
         @ Your password is unchanged.
         @ </span></p>

      ;
    }else{
      char *zNewPw = sha1_shared_secret(zNew1, g.zLogin, 0);
      char *zChngPw;
      char *zErr;
      db_multi_exec(
         "UPDATE user SET pw=%Q WHERE uid=%d", zNewPw, g.userUid
................................................................................
  }
  if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
    /* Attempting to log in as a user other than anonymous.
    */
    uid = login_search_uid(zUsername, zPasswd);
    if( uid<=0 ){
      sleep(1);

      zErrMsg = 
         @ <p><span class="loginError">





         @ You entered an unknown user or an incorrect password.
         @ </span></p>

      ;
      record_login_attempt(zUsername, zIpAddr, 0);
    }else{
      /* Non-anonymous login is successful.  Set a cookie of the form:
      **
      **    HASH/PROJECT/LOGIN
      **
................................................................................
      ** where HASH is a random hex number, PROJECT is either project
      ** code prefix, and LOGIN is the user name.
      */
      login_set_user_cookie(zUsername, uid, NULL);
      redirect_to_g();
    }
  }




  style_header("Login/Logout");


  @ %s(zErrMsg)
  if( zGoto && P("anon")==0 ){
    @ <p>A login is required for <a href="%h(zGoto)">%h(zGoto)</a>.</p>
  }
  form_begin(0, "%R/login");
  if( zGoto ){
    @ <input type="hidden" name="g" value="%h(zGoto)" />
  }
  @ <table class="login_out">
  @ <tr>
  @   <td class="login_out_label">User ID:</td>






  if( anonFlag ){
    @ <td><input type="text" id="u" name="u" value="anonymous" size="30" /></td>
  }else{
    @ <td><input type="text" id="u" name="u" value="" size="30" /></td>
  }
  @ </tr>
  @ <tr>
  @  <td class="login_out_label">Password:</td>






  @   <td><input type="password" id="p" name="p" value="" size="30" /></td>
  @ </tr>
  if( g.zLogin==0 ){
    zAnonPw = db_text(0, "SELECT pw FROM user"
                         " WHERE login='anonymous'"
                         "   AND cap!=''");
  }
  @ <tr>
  @   <td></td>



  @   <td><input type="submit" name="in" value="Login"

  @        onClick="chngAction(this.form)" /></td>
  @ </tr>
  @ </table>
  @ <script type="text/JavaScript">
  @   gebi('u').focus()
  @   function chngAction(form){
  if( g.sslNotAvailable==0
................................................................................
     char *zSSL = mprintf("https:%s", &g.zBaseURL[5]);
     @  if( form.u.value!="anonymous" ){
     @     form.action = "%h(zSSL)/login";
     @  }
  }
  @ }
  @ </script>

  if( g.zLogin==0 ){











    @ <p>Enter
  }else{
    @ <p>You are currently logged in as <b>%h(g.zLogin)</b></p>
    @ <p>To change your login to a different user, enter
  }
  @ your user-id and password at the left and press the
  @ "Login" button.  Your user name will be stored in a browser cookie.
  @ You must configure your web browser to accept cookies in order for
  @ the login to take.</p>


  if( db_get_boolean("self-register", 0) ){




    @ <p>If you do not have an account, you can 
    @ <a href="%s(g.zTop)/register?g=%T(P("G"))">create one</a>.

  }

  if( zAnonPw ){
    unsigned int uSeed = captcha_seed();
    char const *zDecoded = captcha_decode(uSeed);
    int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
    char *zCaptcha = captcha_render(zDecoded);

    @ <p><input type="hidden" name="cs" value="%u(uSeed)" />




    @ Visitors may enter <b>anonymous</b> as the user-ID with
    @ the 8-character hexadecimal password shown below:</p>

    @ <div class="captcha"><table class="captcha"><tr><td><pre>
    @ %h(zCaptcha)
    @ </pre></td></tr></table>
    if( bAutoCaptcha ) {



        @ <input type="button" value="Fill out captcha"

        @  onclick="gebi('u').value='anonymous'; gebi('p').value='%s(zDecoded)';" />
    }
    @ </div>
    free(zCaptcha);
  }
  if( g.zLogin ){
    @ <hr />





    @ <p>To log off the system (and delete your login cookie)
    @  press the following button:<br />
    @ <input type="submit" name="out" value="Logout" /></p>

  }
  @ </form>
  if( g.perm.Password ){
    @ <hr />














    @ <p>To change your password, enter your old password and your
    @ new password twice below then press the "Change Password"
    @ button.</p>
    form_begin(0, "%R/login");
    @ <table>
    @ <tr><td class="login_out_label">Old Password:</td>
    @ <td><input type="password" name="p" size="30" /></td></tr>
    @ <tr><td class="login_out_label">New Password:</td>
    @ <td><input type="password" name="n1" size="30" /></td></tr>
    @ <tr><td class="login_out_label">Repeat New Password:</td>
    @ <td><input type="password" name="n2" size="30" /></td></tr>
    @ <tr><td></td>
    @ <td><input type="submit" value="Change Password" /></td></tr>

    @ </table>
    @ </form>
  }
  style_footer();
}

/*
................................................................................
*/
void login_anonymous_available(void){
  if( !g.perm.Hyperlink &&
      db_exists("SELECT 1 FROM user"
                " WHERE login='anonymous'"
                "   AND cap LIKE '%%h%%'") ){
    const char *zUrl = PD("REQUEST_URI", "index");





    @ <p>Many <span class="disabled">hyperlinks are disabled.</span><br />
    @ Use <a href="%s(g.zTop)/login?anon=1&amp;g=%T(zUrl)">anonymous login</a>
    @ to enable hyperlinks.</p>

  }
}

/*
** While rendering a form, call this routine to add the Anti-CSRF token
** as a hidden element of the form.
*/






>

|
>
>
>
>
>
>
|
|
|
>


>

|
>
>
>
>
>
>
|
|
|
>







 







>


>
>
>
>
>


>







 







>
>
>
>
|
>
>










|
>
>
>
>
>
>







|
>
>
>
>
>
>









>
>
>

>







 







>
|
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
|
|
|
|
|
>
>

>
>
>
>


>

>







>
>
>
>


>




>
>
>
|
>







>
>
>
>
>



>




>
>
>
>
>
>
>
>
>
>
>
>
>
>













>







 







>
>
>
>
>



>







491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
...
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
...
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
...
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
....
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
    zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0);
    if( db_int(1, "SELECT 0 FROM user"
                  " WHERE uid=%d"
                  " AND (constant_time_cmp(pw,%Q)=0"
                  "      OR constant_time_cmp(pw,%Q)=0)", 
                  g.userUid, zSha1Pw, zPasswd) ){
      sleep(1);
#ifdef LANG_RU
      zErrMsg = 
      @ <p><span class="loginError">
      @ Вы ввели неверный старый пароль.
      @ Ваш пароль не изменен.
      @ </span></p>
#elif LANG_EN
      zErrMsg = 
      @ <p><span class="loginError">
      @ You entered an incorrect old password while attempting to change
      @ your password.  Your password is unchanged.
      @ </span></p>
#endif
      ;
    }else if( fossil_strcmp(zNew1,zNew2)!=0 ){
#ifdef LANG_RU
      zErrMsg = 
       @ <p><span class="loginError">
       @ Два введенных новых пароля не совпадают.
       @ Ваш пароль не изменен.
       @ </span></p>
#elif LANG_EN
      zErrMsg = 
       @ <p><span class="loginError">
       @ The two copies of your new passwords do not match.
       @ Your password is unchanged.
       @ </span></p>
#endif
      ;
    }else{
      char *zNewPw = sha1_shared_secret(zNew1, g.zLogin, 0);
      char *zChngPw;
      char *zErr;
      db_multi_exec(
         "UPDATE user SET pw=%Q WHERE uid=%d", zNewPw, g.userUid
................................................................................
  }
  if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
    /* Attempting to log in as a user other than anonymous.
    */
    uid = login_search_uid(zUsername, zPasswd);
    if( uid<=0 ){
      sleep(1);
#ifdef LANG_RU
      zErrMsg = 
         @ <p><span class="loginError">
         @ Вы ввели неверный пароль или имя несущестующего пользователя.
         @ </span></p>
#elif LANG_EN
      zErrMsg = 
        @ <p><span class="loginError">
         @ You entered an unknown user or an incorrect password.
         @ </span></p>
#endif
      ;
      record_login_attempt(zUsername, zIpAddr, 0);
    }else{
      /* Non-anonymous login is successful.  Set a cookie of the form:
      **
      **    HASH/PROJECT/LOGIN
      **
................................................................................
      ** where HASH is a random hex number, PROJECT is either project
      ** code prefix, and LOGIN is the user name.
      */
      login_set_user_cookie(zUsername, uid, NULL);
      redirect_to_g();
    }
  }
  style_header(
#ifdef LANG_RU
    "Вход/Выход"
#elif LANG_EN
    "Login/Logout"
#endif
    );
  @ %s(zErrMsg)
  if( zGoto && P("anon")==0 ){
    @ <p>A login is required for <a href="%h(zGoto)">%h(zGoto)</a>.</p>
  }
  form_begin(0, "%R/login");
  if( zGoto ){
    @ <input type="hidden" name="g" value="%h(zGoto)" />
  }
  @ <table class="login_out">
  @ <tr>
  @   <td class="login_out_label">
#ifdef LANG_RU
  @ Пользователь:
#elif LANG_EN
  @ User ID:
#endif
  @ </td>
  if( anonFlag ){
    @ <td><input type="text" id="u" name="u" value="anonymous" size="30" /></td>
  }else{
    @ <td><input type="text" id="u" name="u" value="" size="30" /></td>
  }
  @ </tr>
  @ <tr>
  @  <td class="login_out_label">
#ifdef LANG_RU
  @ Пароль:
#elif LANG_EN
  @ Password:
#endif
  @ </td>
  @   <td><input type="password" id="p" name="p" value="" size="30" /></td>
  @ </tr>
  if( g.zLogin==0 ){
    zAnonPw = db_text(0, "SELECT pw FROM user"
                         " WHERE login='anonymous'"
                         "   AND cap!=''");
  }
  @ <tr>
  @   <td></td>
#ifdef LANG_RU
  @   <td><input type="submit" name="in" value="Вход"
#elif LANG_EN
  @   <td><input type="submit" name="in" value="Login"
#endif
  @        onClick="chngAction(this.form)" /></td>
  @ </tr>
  @ </table>
  @ <script type="text/JavaScript">
  @   gebi('u').focus()
  @   function chngAction(form){
  if( g.sslNotAvailable==0
................................................................................
     char *zSSL = mprintf("https:%s", &g.zBaseURL[5]);
     @  if( form.u.value!="anonymous" ){
     @     form.action = "%h(zSSL)/login";
     @  }
  }
  @ }
  @ </script>
#ifdef LANG_RU
    if( g.zLogin==0 ){
      @ <p>Укажите
    }else{
      @ <p>Вы вошли как <b>%h(g.zLogin)</b></p>
      @ <p>Чтобы войти под другим именем, укажите
    }
    @ имя пользователя и пароль, и нажмите кнопку "Вход".
    @ Введенное имя будет сохранено в куках браузера.
    @ Вы должны разрешить браузеру сохранять куки для возможности входа
    @ на сайт.</p>
#elif LANG_EN
    if( g.zLogin==0 ){
      @ <p>Enter
    }else{
      @ <p>You are currently logged in as <b>%h(g.zLogin)</b></p>
      @ <p>To change your login to a different user, enter
    }
    @ your user-id and password at the left and press the
    @ "Login" button.  Your user name will be stored in a browser cookie.
    @ You must configure your web browser to accept cookies in order for
    @ the login to take.</p>
#endif
  
  if( db_get_boolean("self-register", 0) ){
#ifdef LANG_RU
    @ <p>Если у вас нет аккаунта, вы можете
    @ <a href="%s(g.zTop)/register?g=%T(P("G"))">зарегистрироваться</a>.
#elif LANG_EN
    @ <p>If you do not have an account, you can 
    @ <a href="%s(g.zTop)/register?g=%T(P("G"))">create one</a>.
#endif
  }
  
  if( zAnonPw ){
    unsigned int uSeed = captcha_seed();
    char const *zDecoded = captcha_decode(uSeed);
    int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
    char *zCaptcha = captcha_render(zDecoded);

    @ <p><input type="hidden" name="cs" value="%u(uSeed)" />
#ifdef LANG_RU
    @ Гости могут указать <b>anonymous</b> как имя пользователя
    @ и ввести 8-символьный пароль, указанный ниже:</p>
#elif LANG_EN
    @ Visitors may enter <b>anonymous</b> as the user-ID with
    @ the 8-character hexadecimal password shown below:</p>
#endif
    @ <div class="captcha"><table class="captcha"><tr><td><pre>
    @ %h(zCaptcha)
    @ </pre></td></tr></table>
    if( bAutoCaptcha ) {
#ifdef LANG_RU
      @ <input type="button" value="Заполнить капчу"
#elif LANG_EN
      @ <input type="button" value="Fill out captcha"
#endif
        @  onclick="gebi('u').value='anonymous'; gebi('p').value='%s(zDecoded)';" />
    }
    @ </div>
    free(zCaptcha);
  }
  if( g.zLogin ){
    @ <hr />
#ifdef LANG_RU
    @ <p>Для выхода
    @ нажмите эту кнопку:<br />
    @ <input type="submit" name="out" value="Выход" /></p>
#elif LANG_EN
    @ <p>To log off the system (and delete your login cookie)
    @  press the following button:<br />
    @ <input type="submit" name="out" value="Logout" /></p>
#endif
  }
  @ </form>
  if( g.perm.Password ){
    @ <hr />
#ifdef LANG_RU
    @ <p>Для смены своего пароля введите текущий пароль и два раза
    @ новый, после чего нажмите кнопку "Изменить пароль".</p>
    @ <form action="login" method="post">
    @ <table>
    @ <tr><td class="login_out_label">Старый пароль:</td>
    @ <td><input type="password" name="p" size="30" /></td></tr>
    @ <tr><td class="login_out_label">Новый пароль:</td>
    @ <td><input type="password" name="n1" size="30" /></td></tr>
    @ <tr><td class="login_out_label">Повторите новый пароль:</td>
    @ <td><input type="password" name="n2" size="30" /></td></tr>
    @ <tr><td></td>
    @ <td><input type="submit" value="Изменить пароль" /></td></tr>
#elif LANG_EN
    @ <p>To change your password, enter your old password and your
    @ new password twice below then press the "Change Password"
    @ button.</p>
    form_begin(0, "%R/login");
    @ <table>
    @ <tr><td class="login_out_label">Old Password:</td>
    @ <td><input type="password" name="p" size="30" /></td></tr>
    @ <tr><td class="login_out_label">New Password:</td>
    @ <td><input type="password" name="n1" size="30" /></td></tr>
    @ <tr><td class="login_out_label">Repeat New Password:</td>
    @ <td><input type="password" name="n2" size="30" /></td></tr>
    @ <tr><td></td>
    @ <td><input type="submit" value="Change Password" /></td></tr>
#endif
    @ </table>
    @ </form>
  }
  style_footer();
}

/*
................................................................................
*/
void login_anonymous_available(void){
  if( !g.perm.Hyperlink &&
      db_exists("SELECT 1 FROM user"
                " WHERE login='anonymous'"
                "   AND cap LIKE '%%h%%'") ){
    const char *zUrl = PD("REQUEST_URI", "index");
#ifdef LANG_RU
    @ <p>Многие <span class="disabled">ссылки отключены.</span><br />
    @ Используйте <a href="%s(g.zTop)/login?anon=1&amp;g=%T(zUrl)">анонимный вход</a>
    @ для включения ссылок.</p>
#elif LANG_EN
    @ <p>Many <span class="disabled">hyperlinks are disabled.</span><br />
    @ Use <a href="%s(g.zTop)/login?anon=1&amp;g=%T(zUrl)">anonymous login</a>
    @ to enable hyperlinks.</p>
#endif
  }
}

/*
** While rendering a form, call this routine to add the Anti-CSRF token
** as a hidden element of the form.
*/

Changes to src/main.mk.

6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# This file is automatically generated.  Instead of editing this
# file, edit "makemake.tcl" then run "tclsh makemake.tcl"
# to regenerate this file.
#
# This file is included by primary Makefile.
#

XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR) -I$(OBJDIR)


SRC = \
  $(SRCDIR)/add.c \
  $(SRCDIR)/allrepo.c \
  $(SRCDIR)/attach.c \
  $(SRCDIR)/bag.c \






|







6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# This file is automatically generated.  Instead of editing this
# file, edit "makemake.tcl" then run "tclsh makemake.tcl"
# to regenerate this file.
#
# This file is included by primary Makefile.
#

XTCC = $(TCC) $(CFLAGS) -DFOSSIL_ENABLE_JSON -DLANG_RU -I. -I$(SRCDIR) -I$(OBJDIR)


SRC = \
  $(SRCDIR)/add.c \
  $(SRCDIR)/allrepo.c \
  $(SRCDIR)/attach.c \
  $(SRCDIR)/bag.c \

Changes to src/manifest.c.

1550
1551
1552
1553
1554
1555
1556



1557

1558
1559
1560



1561

1562
1563



1564

1565
1566
1567
1568
1569
1570





1571
1572
1573
1574

1575
1576




1577
1578
1579

1580
1581
1582



1583

1584
1585



1586

1587
1588
1589
1590
1591
1592
1593
....
1769
1770
1771
1772
1773
1774
1775



1776

1777



1778

1779
1780
1781
1782
1783
1784
1785
....
1867
1868
1869
1870
1871
1872
1873



1874

1875
1876




1877

1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889



1890

1891
1892




1893

1894
1895
1896
1897
1898
1899
1900
  if( !isNew ){
    for(i=0; i<pManifest->nField; i++){
      if( fossil_strcmp(pManifest->aField[i].zName, zStatusColumn)==0 ){
        zNewStatus = pManifest->aField[i].zValue;
      }
    }
    if( zNewStatus ){



      blob_appendf(&comment, "%h ticket [%.10s]: <i>%h</i>",

         zNewStatus, pManifest->zTicketUuid, zTitle
      );
      if( pManifest->nField>1 ){



        blob_appendf(&comment, " plus %d other change%s",

          pManifest->nField-1, pManifest->nField==2 ? "" : "s");
      }



      blob_appendf(&brief, "%h ticket [%.10s].",

                   zNewStatus, pManifest->zTicketUuid);
    }else{
      zNewStatus = db_text("unknown", 
         "SELECT %s FROM ticket WHERE tkt_uuid='%s'",
         zStatusColumn, pManifest->zTicketUuid
      );





      blob_appendf(&comment, "Ticket [%.10s] <i>%h</i> status still %h with "
           "%d other change%s",
           pManifest->zTicketUuid, zTitle, zNewStatus, pManifest->nField,
           pManifest->nField==1 ? "" : "s"

      );
      free(zNewStatus);




      blob_appendf(&brief, "Ticket [%.10s]: %d change%s",
           pManifest->zTicketUuid, pManifest->nField,
           pManifest->nField==1 ? "" : "s"

      );
    }
  }else{



    blob_appendf(&comment, "New ticket [%.10s] <i>%h</i>.",

      pManifest->zTicketUuid, zTitle
    );



    blob_appendf(&brief, "New ticket [%.10s].", pManifest->zTicketUuid);

  }
  free(zTitle);
  db_multi_exec(
    "REPLACE INTO event(type,tagid,mtime,objid,user,comment,brief)"
    "VALUES('t',%d,%.17g,%d,%Q,%Q,%Q)",
    tktTagId, pManifest->rDate, rid, pManifest->zUser,
    blob_str(&comment), blob_str(&brief)
................................................................................
      " ORDER BY mtime DESC",
      tagid, p->rDate
    );
    if( prior ){
      content_deltify(prior, rid, 0);
    }
    if( nWiki>0 ){



      zComment = mprintf("Changes to wiki page [%h]", p->zWikiTitle);

    }else{



      zComment = mprintf("Deleted wiki page [%h]", p->zWikiTitle);

    }
    db_multi_exec(
      "REPLACE INTO event(type,mtime,objid,user,comment,"
      "                  bgcolor,euser,ecomment)"
      "VALUES('w',%.17g,%d,%Q,%Q,"
      "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1),"
      "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),"
................................................................................
    );
    if( strlen(p->zAttachTarget)!=UUID_SIZE
     || !validate16(p->zAttachTarget, UUID_SIZE) 
    ){
      char *zComment;
      if( p->zAttachSrc && p->zAttachSrc[0] ){
        zComment = mprintf(



             "Add attachment [%R/artifact/%S|%h] to wiki page [%h]",

             p->zAttachSrc, p->zAttachName, p->zAttachTarget);
      }else{




        zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]",

             p->zAttachName, p->zAttachTarget);
      }
      db_multi_exec(
        "REPLACE INTO event(type,mtime,objid,user,comment)"
        "VALUES('w',%.17g,%d,%Q,%Q)",
        p->rDate, rid, p->zUser, zComment
      );
      free(zComment);
    }else{
      char *zComment;
      if( p->zAttachSrc && p->zAttachSrc[0] ){
        zComment = mprintf(



             "Add attachment [%R/artifact/%S|%h] to ticket [%S]",

             p->zAttachSrc, p->zAttachName, p->zAttachTarget);
      }else{




        zComment = mprintf("Delete attachment \"%h\" from ticket [%.10s]",

             p->zAttachName, p->zAttachTarget);
      }
      db_multi_exec(
        "REPLACE INTO event(type,mtime,objid,user,comment)"
        "VALUES('t',%.17g,%d,%Q,%Q)",
        p->rDate, rid, p->zUser, zComment
      );






>
>
>

>



>
>
>

>


>
>
>

>






>
>
>
>
>


|
|
>


>
>
>
>



>



>
>
>

>


>
>
>

>







 







>
>
>

>

>
>
>

>







 







>
>
>

>


>
>
>
>
|
>












>
>
>

>


>
>
>
>
|
>







1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
....
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
....
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
  if( !isNew ){
    for(i=0; i<pManifest->nField; i++){
      if( fossil_strcmp(pManifest->aField[i].zName, zStatusColumn)==0 ){
        zNewStatus = pManifest->aField[i].zValue;
      }
    }
    if( zNewStatus ){
#ifdef LANG_RU
      blob_appendf(&comment, "%h задача [%.10s]: <i>%h</i>",
#elif LANG_EN
      blob_appendf(&comment, "%h ticket [%.10s]: <i>%h</i>",
#endif
         zNewStatus, pManifest->zTicketUuid, zTitle
      );
      if( pManifest->nField>1 ){
#ifdef LANG_RU
        blob_appendf(&comment, " плюс еще изменений %d",
#elif LANG_EN
        blob_appendf(&comment, " plus %d other change%s",
#endif
          pManifest->nField-1, pManifest->nField==2 ? "" : "s");
      }
#ifdef LANG_RU
      blob_appendf(&brief, "%h задача [%.10s].",
#elif LANG_EN
      blob_appendf(&brief, "%h ticket [%.10s].",
#endif
                   zNewStatus, pManifest->zTicketUuid);
    }else{
      zNewStatus = db_text("unknown", 
         "SELECT %s FROM ticket WHERE tkt_uuid='%s'",
         zStatusColumn, pManifest->zTicketUuid
      );
#ifdef LANG_RU
      blob_appendf(&comment, "Задача [%.10s] <i>%h</i> статус остался %h с "
           "%d изменением",
		   pManifest->zTicketUuid, zTitle, zNewStatus, pManifest->nField
#elif LANG_EN
      blob_appendf(&comment, "Ticket [%.10s] <i>%h</i> status still %h with "
           "%d other change%s",
		   pManifest->zTicketUuid, zTitle, zNewStatus, pManifest->nField,
		   pManifest->nField==1 ? "" : "s"
#endif
      );
      free(zNewStatus);
#ifdef LANG_RU
      blob_appendf(&brief, "Задача [%.10s]: %d изменение",
           pManifest->zTicketUuid, pManifest->nField
#elif LANG_EN
      blob_appendf(&brief, "Ticket [%.10s]: %d change%s",
           pManifest->zTicketUuid, pManifest->nField,
           pManifest->nField==1 ? "" : "s"
#endif
      );
    }
  }else{
#ifdef LANG_RU
    blob_appendf(&comment, "Новая задача [%.10s] <i>%h</i>.",
#elif LANG_EN
    blob_appendf(&comment, "New ticket [%.10s] <i>%h</i>.",
#endif
      pManifest->zTicketUuid, zTitle
    );
#ifdef LANG_RU
    blob_appendf(&brief, "Новая задача [%.10s].", pManifest->zTicketUuid);
#elif LANG_EN
    blob_appendf(&brief, "New ticket [%.10s].", pManifest->zTicketUuid);
#endif
  }
  free(zTitle);
  db_multi_exec(
    "REPLACE INTO event(type,tagid,mtime,objid,user,comment,brief)"
    "VALUES('t',%d,%.17g,%d,%Q,%Q,%Q)",
    tktTagId, pManifest->rDate, rid, pManifest->zUser,
    blob_str(&comment), blob_str(&brief)
................................................................................
      " ORDER BY mtime DESC",
      tagid, p->rDate
    );
    if( prior ){
      content_deltify(prior, rid, 0);
    }
    if( nWiki>0 ){
#ifdef LANG_RU
      zComment = mprintf("Правка страницы [%h]", p->zWikiTitle);
#elif LANG_EN
      zComment = mprintf("Changes to wiki page [%h]", p->zWikiTitle);
#endif
    }else{
#ifdef LANG_RU
      zComment = mprintf("Удаление страницы [%h]", p->zWikiTitle);
#elif LANG_EN
      zComment = mprintf("Deleted wiki page [%h]", p->zWikiTitle);
#endif
    }
    db_multi_exec(
      "REPLACE INTO event(type,mtime,objid,user,comment,"
      "                  bgcolor,euser,ecomment)"
      "VALUES('w',%.17g,%d,%Q,%Q,"
      "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1),"
      "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),"
................................................................................
    );
    if( strlen(p->zAttachTarget)!=UUID_SIZE
     || !validate16(p->zAttachTarget, UUID_SIZE) 
    ){
      char *zComment;
      if( p->zAttachSrc && p->zAttachSrc[0] ){
        zComment = mprintf(
#ifdef LANG_RU
             "Добавить вложение [%R/artifact/%S|%h] к вики-странице [%h]",
#elif LANG_EN
             "Add attachment [%R/artifact/%S|%h] to wiki page [%h]",
#endif
             p->zAttachSrc, p->zAttachName, p->zAttachTarget);
      }else{
        zComment = mprintf(
#ifdef LANG_RU
          "Удален файл \"%h\" из страницы[%h]",
#elif LANG_EN
          "Delete attachment \"%h\" from wiki page [%h]",
#endif
             p->zAttachName, p->zAttachTarget);
      }
      db_multi_exec(
        "REPLACE INTO event(type,mtime,objid,user,comment)"
        "VALUES('w',%.17g,%d,%Q,%Q)",
        p->rDate, rid, p->zUser, zComment
      );
      free(zComment);
    }else{
      char *zComment;
      if( p->zAttachSrc && p->zAttachSrc[0] ){
        zComment = mprintf(
#ifdef LANG_RU
             "Добавить вложение [%R/artifact/%S|%h] к задаче [%S]",
#elif LANG_EN
             "Add attachment [%R/artifact/%S|%h] to ticket [%S]",
#endif
             p->zAttachSrc, p->zAttachName, p->zAttachTarget);
      }else{
        zComment = mprintf(
#ifdef LANG_RU
          "Удален файл \"%h\" из задачи [%.10s]",
#elif LANG_EN
          "Delete attachment \"%h\" from ticket [%.10s]",
#endif
             p->zAttachName, p->zAttachTarget);
      }
      db_multi_exec(
        "REPLACE INTO event(type,mtime,objid,user,comment)"
        "VALUES('t',%.17g,%d,%Q,%Q)",
        p->rDate, rid, p->zUser, zComment
      );

Changes to src/report.c.

33
34
35
36
37
38
39




40


41
42
43
44
45
46
47
...
994
995
996
997
998
999
1000

1001
1002
1003
1004
1005
1006
1007
....
1015
1016
1017
1018
1019
1020
1021


1022
1023
1024
1025
1026
1027
1028
....
1030
1031
1032
1033
1034
1035
1036





1037
1038
1039
1040
1041
1042
1043





1044
1045
1046
1047






1048
1049
1050
1051
1052




1053

1054
1055
1056
1057
1058
1059
1060
  Blob ril;   /* Report Item List */
  Stmt q;
  int rn = 0;
  int cnt = 0;

  login_check_credentials();
  if( !g.perm.RdTkt && !g.perm.NewTkt ){ login_needed(); return; }




  style_header("Ticket Main Menu");


  if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST<br />\n", -1);
  zScript = ticket_reportlist_code();
  if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST_SCRIPT<br />\n", -1);
  
  blob_zero(&ril);
  ticket_init();

................................................................................
  char *zTitle;
  char *zOwner;
  char *zClrKey;
  int tabs;
  Stmt q;
  char *zErr1 = 0;
  char *zErr2 = 0;


  login_check_credentials();
  if( !g.perm.RdTkt ){ login_needed(); return; }
  rn = atoi(PD("rn","0"));
  if( rn==0 ){
    cgi_redirect("reportlist");
    return;
................................................................................
    return;
  }
  zTitle = db_column_malloc(&q, 0);
  zSql = db_column_malloc(&q, 1);
  zOwner = db_column_malloc(&q, 2);
  zClrKey = db_column_malloc(&q, 3);
  db_finalize(&q);



  if( P("order_by") ){
    /*
    ** If the user wants to do a column sort, wrap the query into a sub
    ** query and then sort the results. This is a whole lot easier than
    ** trying to insert an ORDER BY into the query itself, especially
    ** if the query is already ordered.
................................................................................
    int nField = atoi(P("order_by"));
    if( nField > 0 ){
      const char* zDir = PD("order_dir","");
      zDir = !strcmp("ASC",zDir) ? "ASC" : "DESC";
      zSql = mprintf("SELECT * FROM (%s) ORDER BY %d %s", zSql, nField, zDir);
    }
  }






  count = 0;
  if( !tabs ){
    struct GenerateHTML sState;

    db_multi_exec("PRAGMA empty_result_callbacks=ON");
    style_submenu_element("Raw", "Raw", 





      "rptview?tablist=1&%h", PD("QUERY_STRING",""));
    if( g.perm.Admin 
       || (g.perm.TktFmt && g.zLogin && fossil_strcmp(g.zLogin,zOwner)==0) ){
      style_submenu_element("Edit", "Edit", "rptedit?rn=%d", rn);






    }
    if( g.perm.TktFmt ){
      style_submenu_element("SQL", "SQL", "rptsql?rn=%d",rn);
    }
    if( g.perm.NewTkt ){




      style_submenu_element("New Ticket", "Create a new ticket",

        "%s/tktnew", g.zTop);
    }
    style_header(zTitle);
    output_color_key(zClrKey, 1, 
        "border=\"0\" cellpadding=\"3\" cellspacing=\"0\" class=\"report\"");
    @ <table border="1" cellpadding="2" cellspacing="0" class="report"
    @  id="reportTable">






>
>
>
>
|
>
>







 







>







 







>
>







 







>
>
>
>
>






|
>
>
>
>
>



|
>
>
>
>
>
>





>
>
>
>
|
>







33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
....
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
....
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
....
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
  Blob ril;   /* Report Item List */
  Stmt q;
  int rn = 0;
  int cnt = 0;

  login_check_credentials();
  if( !g.perm.RdTkt && !g.perm.NewTkt ){ login_needed(); return; }
  style_header(
#ifdef LANG_RU
    "Задачи: основное меню"
#elif LANG_EN
    "Ticket Main Menu"
#endif
    );
  if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST<br />\n", -1);
  zScript = ticket_reportlist_code();
  if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST_SCRIPT<br />\n", -1);
  
  blob_zero(&ril);
  ticket_init();

................................................................................
  char *zTitle;
  char *zOwner;
  char *zClrKey;
  int tabs;
  Stmt q;
  char *zErr1 = 0;
  char *zErr2 = 0;
  Blob bSql = empty_blob;

  login_check_credentials();
  if( !g.perm.RdTkt ){ login_needed(); return; }
  rn = atoi(PD("rn","0"));
  if( rn==0 ){
    cgi_redirect("reportlist");
    return;
................................................................................
    return;
  }
  zTitle = db_column_malloc(&q, 0);
  zSql = db_column_malloc(&q, 1);
  zOwner = db_column_malloc(&q, 2);
  zClrKey = db_column_malloc(&q, 3);
  db_finalize(&q);



  if( P("order_by") ){
    /*
    ** If the user wants to do a column sort, wrap the query into a sub
    ** query and then sort the results. This is a whole lot easier than
    ** trying to insert an ORDER BY into the query itself, especially
    ** if the query is already ordered.
................................................................................
    int nField = atoi(P("order_by"));
    if( nField > 0 ){
      const char* zDir = PD("order_dir","");
      zDir = !strcmp("ASC",zDir) ? "ASC" : "DESC";
      zSql = mprintf("SELECT * FROM (%s) ORDER BY %d %s", zSql, nField, zDir);
    }
  }
  Th_FossilInit(0, 0);
  Th_Store("login", g.zLogin);
  Th_Store("date", db_text(0, "SELECT datetime('now')"));
  Th_RenderToBlob(zSql, &bSql);
  zSql = bSql.aData;

  count = 0;
  if( !tabs ){
    struct GenerateHTML sState;

    db_multi_exec("PRAGMA empty_result_callbacks=ON");
    style_submenu_element(
#ifdef LANG_RU
      "Текстом", "Текстом", 
#elif LANG_EN
      "Raw", "Raw", 
#endif
      "rptview?tablist=1&%h", PD("QUERY_STRING",""));
    if( g.perm.Admin 
       || (g.perm.TktFmt && g.zLogin && fossil_strcmp(g.zLogin,zOwner)==0) ){
      style_submenu_element(
#ifdef LANG_RU
        "Изменить", "Изменить",
#elif LANG_EN
        "Edit", "Edit",
#endif
         "rptedit?rn=%d", rn);
    }
    if( g.perm.TktFmt ){
      style_submenu_element("SQL", "SQL", "rptsql?rn=%d",rn);
    }
    if( g.perm.NewTkt ){
      style_submenu_element(
#ifdef LANG_RU
        "Новая задача", "Создать новую задачу",
#elif LANG_EN
        "New Ticket", "Create a new ticket",
#endif
        "%s/tktnew", g.zTop);
    }
    style_header(zTitle);
    output_color_key(zClrKey, 1, 
        "border=\"0\" cellpadding=\"3\" cellspacing=\"0\" class=\"report\"");
    @ <table border="1" cellpadding="2" cellspacing="0" class="report"
    @  id="reportTable">

Changes to src/sqlite3.c.

14081
14082
14083
14084
14085
14086
14087
14088

14089
14090
14091
14092
14093
14094
14095
14096
.....
14629
14630
14631
14632
14633
14634
14635
14636
14637
14638
14639
14640
14641
14642
14643
14644
  if( zDate[0]=='-' ){
    zDate++;
    neg = 1;
  }else{
    neg = 0;
  }
  if( getDigits(zDate,4,0,9999,'-',&Y,2,1,12,'-',&M,2,1,31,0,&D)!=3 ){

    return 1;
  }
  zDate += 10;
  while( sqlite3Isspace(*zDate) || 'T'==*(u8*)zDate ){ zDate++; }
  if( parseHhMmSs(zDate, p)==0 ){
    /* We got the time */
  }else if( *zDate==0 ){
    p->validHMS = 0;
................................................................................
  int argc,
  sqlite3_value **argv
){
  DateTime x;
  if( isDate(context, argc, argv, &x)==0 ){
    char zBuf[100];
    computeYMD_HMS(&x);
    sqlite3_snprintf(sizeof(zBuf), zBuf, "%04d-%02d-%02d %02d:%02d:%02d",
                     x.Y, x.M, x.D, x.h, x.m, (int)(x.s));
    sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
  }
}

/*
**    time( TIMESTRING, MOD, MOD, ...)
**






|
>
|







 







|
|







14081
14082
14083
14084
14085
14086
14087
14088
14089
14090
14091
14092
14093
14094
14095
14096
14097
.....
14630
14631
14632
14633
14634
14635
14636
14637
14638
14639
14640
14641
14642
14643
14644
14645
  if( zDate[0]=='-' ){
    zDate++;
    neg = 1;
  }else{
    neg = 0;
  }
  if( getDigits(zDate,4,0,9999,'-',&Y,2,1,12,'-',&M,2,1,31,0,&D)!=3 &&
    getDigits(zDate,2,1,31,'.',&D,2,1,12,'.',&M,4,0,9999,0,&Y)!=3 ){
      return 1;
  }
  zDate += 10;
  while( sqlite3Isspace(*zDate) || 'T'==*(u8*)zDate ){ zDate++; }
  if( parseHhMmSs(zDate, p)==0 ){
    /* We got the time */
  }else if( *zDate==0 ){
    p->validHMS = 0;
................................................................................
  int argc,
  sqlite3_value **argv
){
  DateTime x;
  if( isDate(context, argc, argv, &x)==0 ){
    char zBuf[100];
    computeYMD_HMS(&x);
    sqlite3_snprintf(sizeof(zBuf), zBuf, "%02d.%02d.%04d %02d:%02d:%02d",
                     x.D, x.M, x.Y, x.h, x.m, (int)(x.s));
    sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
  }
}

/*
**    time( TIMESTRING, MOD, MOD, ...)
**

Changes to src/th_main.c.

100
101
102
103
104
105
106


107
108
109
110
111
112
113
114
115
116
117
118


119
120
121
122
123
124
125
126
...
916
917
918
919
920
921
922








923
924
925
926
927
928
929
    default: {
      sqlite3_snprintf(sizeof(zRc),zRc,"return code %d",rc);
    }
  }
  return zRc;
}



/*
** Send text to the appropriate output:  Either to the console
** or to the CGI reply buffer.  Escape all characters with special
** meaning to HTML if the encode parameter is true.
*/
static void sendText(const char *z, int n, int encode){
  if( enableOutput && n ){
    if( n<0 ) n = strlen(z);
    if( encode ){
      z = htmlize(z, n);
      n = strlen(z);
    }


    if( g.cgiOutput ){
      cgi_append_content(z, n);
    }else{
      fwrite(z, 1, n, stdout);
      fflush(stdout);
    }
    if( encode ) free((char*)z);
  }
................................................................................
    zResult = (char*)Th_GetResult(g.interp, &n);
    sendError(zResult, n, 1);
  }else{
    sendText(z, i, 0);
  }
  return rc;
}









/*
** COMMAND: test-th-render
*/
void test_th_render(void){
  Blob in;
  if( g.argc<3 ){






>
>












>
>
|







 







>
>
>
>
>
>
>
>







100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
...
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
    default: {
      sqlite3_snprintf(sizeof(zRc),zRc,"return code %d",rc);
    }
  }
  return zRc;
}

static Blob* pOutBlob = NULL;

/*
** Send text to the appropriate output:  Either to the console
** or to the CGI reply buffer.  Escape all characters with special
** meaning to HTML if the encode parameter is true.
*/
static void sendText(const char *z, int n, int encode){
  if( enableOutput && n ){
    if( n<0 ) n = strlen(z);
    if( encode ){
      z = htmlize(z, n);
      n = strlen(z);
    }
    if(pOutBlob)
      blob_append(pOutBlob, z, n);
    else if( g.cgiOutput ){
      cgi_append_content(z, n);
    }else{
      fwrite(z, 1, n, stdout);
      fflush(stdout);
    }
    if( encode ) free((char*)z);
  }
................................................................................
    zResult = (char*)Th_GetResult(g.interp, &n);
    sendError(zResult, n, 1);
  }else{
    sendText(z, i, 0);
  }
  return rc;
}

int Th_RenderToBlob(const char *z, Blob *pBlob){
  int result;
  pOutBlob = pBlob;
  result = Th_Render(z);
  pOutBlob = NULL;
  return result;
}

/*
** COMMAND: test-th-render
*/
void test_th_render(void){
  Blob in;
  if( g.argc<3 ){

Changes to src/timeline.c.

818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
...
919
920
921
922
923
924
925



926

927
928
929
930
931












932
933
934
935
936
937
938
....
1043
1044
1045
1046
1047
1048
1049
1050






1051
1052
1053
1054
1055
1056
1057
....
1080
1081
1082
1083
1084
1085
1086




1087


1088
1089






1090
1091
1092
1093
1094
1095
1096
....
1106
1107
1108
1109
1110
1111
1112

1113





1114
1115
1116
1117
1118
1119
1120
1121






1122





1123
1124
1125
1126




1127

1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141




1142


1143
1144
1145
1146
1147
1148
1149





1150
1151
1152
1153
1154
1155
1156
....
1279
1280
1281
1282
1283
1284
1285




























1286



1287

1288



1289


1290
1291
1292
1293
1294
1295
1296
1297



1298

1299
1300
1301



1302

1303
1304



1305

1306
1307
1308
1309




1310

1311
1312




1313


1314
1315



1316

1317



1318

1319
1320



1321

1322
1323
1324
1325



1326

1327
1328
1329
1330



1331

1332
1333
1334



1335

1336
1337



1338

1339
1340



1341

1342
1343



1344

1345
1346



1347

1348
1349
1350
1351
1352
1353



1354

1355
1356



1357

1358
1359
1360



1361

1362



1363

1364
1365
1366
1367
1368
1369
1370
*/
const char *timeline_query_for_www(void){
  static char *zBase = 0;
  static const char zBaseSql[] =
    @ SELECT
    @   blob.rid AS blobRid,
    @   uuid AS uuid,
    @   datetime(event.mtime,'localtime') AS timestamp,
    @   coalesce(ecomment, comment) AS comment,
    @   coalesce(euser, user) AS user,
    @   blob.rid IN leaf AS leaf,
    @   bgcolor AS bgColor,
    @   event.type AS eventType,
    @   (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref
    @     WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
................................................................................
    zUuid
  );
  blob_zero(&out);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zFN = db_column_text(&q, 0);
    blob_appendf(&out, "%s%z%h</a>", zSep,
          href("%R/finfo?name=%t", zFN), zFN);



    zSep = " or ";

  }
  db_finalize(&q);
  return blob_str(&out);
}














/*
** WEBPAGE: timeline
**
** Query parameters:
**
**    a=TIMEORTAG    after this event
................................................................................
      compute_uses_file("usesfile", ufid, 0);
      zType = "ci";
    }else{
      zUses = 0;
    }
  }

  style_header("Timeline");






  login_anonymous_available();
  timeline_temp_table();
  blob_zero(&sql);
  blob_zero(&desc);
  blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1);
  blob_append(&sql, timeline_query_for_www(), -1);
  if( P("fc")!=0 || P("detail")!=0 ){
................................................................................
    blob_append(&sql, " AND event.objid IN (0", -1);
    while( p ){
      blob_appendf(&sql, ",%d", p->rid);
      p = p->u.pTo;
    }
    blob_append(&sql, ")", -1);
    path_reset();




    blob_append(&desc, "All nodes on the path from ", -1);


    blob_appendf(&desc, "%z%h</a>", href("%R/info/%h", zFrom), zFrom);
    blob_append(&desc, " and ", -1);






    blob_appendf(&desc, "%z[%h]</a>", href("%R/info/%h",zTo), zTo);
    tmFlags |= TIMELINE_DISJOINT;
    db_multi_exec("%s", blob_str(&sql));
  }else if( (p_rid || d_rid) && g.perm.Read ){
    /* If p= or d= is present, ignore all other parameters other than n= */
    char *zUuid;
    int np, nd;
................................................................................
                         p_rid ? p_rid : d_rid);
    blob_appendf(&sql, " AND event.objid IN ok");
    nd = 0;
    if( d_rid ){
      compute_descendants(d_rid, nEntry+1);
      nd = db_int(0, "SELECT count(*)-1 FROM ok");
      if( nd>=0 ) db_multi_exec("%s", blob_str(&sql));

      if( nd>0 ) blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s");





      if( useDividers ) timeline_add_dividers(0, d_rid);
      db_multi_exec("DELETE FROM ok");
    }
    if( p_rid ){
      compute_ancestors(p_rid, nEntry+1, 0);
      np = db_int(0, "SELECT count(*)-1 FROM ok");
      if( np>0 ){
        if( nd>0 ) blob_appendf(&desc, " and ");






        blob_appendf(&desc, "%d ancestors", np);





        db_multi_exec("%s", blob_str(&sql));
      }
      if( d_rid==0 && useDividers ) timeline_add_dividers(0, p_rid);
    }




    blob_appendf(&desc, " of %z[%.10s]</a>",

                   href("%R/info/%s", zUuid), zUuid);
  }else if( f_rid && g.perm.Read ){
    /* If f= is present, ignore all other parameters other than n= */
    char *zUuid;
    db_multi_exec(
       "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
       "INSERT INTO ok VALUES(%d);"
       "INSERT OR IGNORE INTO ok SELECT pid FROM plink WHERE cid=%d;"
       "INSERT OR IGNORE INTO ok SELECT cid FROM plink WHERE pid=%d;",
       f_rid, f_rid, f_rid
    );
    blob_appendf(&sql, " AND event.objid IN ok");
    db_multi_exec("%s", blob_str(&sql));
    if( useDividers ) timeline_add_dividers(0, f_rid);




    blob_appendf(&desc, "Parents and children of check-in ");


    zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid);
    blob_appendf(&desc, "%z[%.10s]</a>", href("%R/info/%s", zUuid), zUuid);
    tmFlags |= TIMELINE_DISJOINT;
  }else{
    /* Otherwise, a timeline based on a span of time */
    int n;
    const char *zEType = "timeline item";





    char *zDate;
    char *zNEntry = mprintf("%d", nEntry);
    url_add_parameter(&url, "n", zNEntry);
    if( zUses ){
      blob_appendf(&sql, " AND event.objid IN usesfile ");
    }
    if( tagid>0 ){
................................................................................
    }else{
      blob_appendf(&sql, " ORDER BY event.mtime DESC");
    }
    blob_appendf(&sql, " LIMIT %d", nEntry);
    db_multi_exec("%s", blob_str(&sql));

    n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/");




























    if( zAfter==0 && zBefore==0 && zCirca==0 ){



      blob_appendf(&desc, "%d most recent %ss", n, zEType);

    }else{



      blob_appendf(&desc, "%d %ss", n, zEType);


    }
    if( zUses ){
      char *zFilenames = names_of_file(zUses);
      blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames,
                   href("%R/artifact/%S",zUses), zUses);
      tmFlags |= TIMELINE_DISJOINT;
    }
    if( zUser ){



      blob_appendf(&desc, " by user %h", zUser);

      tmFlags |= TIMELINE_DISJOINT;
    }
    if( zTagName ){



      blob_appendf(&desc, " tagged with \"%h\"", zTagName);

      tmFlags |= TIMELINE_DISJOINT;
    }else if( zBrName ){



      blob_appendf(&desc, " related to \"%h\"", zBrName);

      tmFlags |= TIMELINE_DISJOINT;
    }
    if( rAfter>0.0 ){
      if( rBefore>0.0 ){




        blob_appendf(&desc, " occurring between %h and %h.<br>",

                     zAfter, zBefore);
      }else{




        blob_appendf(&desc, " occurring on or after %h.<br />", zAfter);


      }
    }else if( rBefore>0.0 ){



      blob_appendf(&desc, " occurring on or before %h.<br />", zBefore);

    }else if( rCirca>0.0 ){



      blob_appendf(&desc, " occurring around %h.<br />", zCirca);

    }
    if( zSearch ){



      blob_appendf(&desc, " matching \"%h\"", zSearch);

    }
    if( g.perm.Hyperlink ){
      if( zAfter || n==nEntry ){
        zDate = db_text(0, "SELECT min(timestamp) FROM timeline /*scan*/");



        timeline_submenu(&url, "Older", "b", zDate, "a");

        free(zDate);
      }
      if( zBefore || (zAfter && n==nEntry) ){
        zDate = db_text(0, "SELECT max(timestamp) FROM timeline /*scan*/");



        timeline_submenu(&url, "Newer", "a", zDate, "b");

        free(zDate);
      }else if( tagid==0 ){
        if( zType[0]!='a' ){



          timeline_submenu(&url, "All Types", "y", "all", 0);

        }
        if( zType[0]!='w' && g.perm.RdWiki ){



          timeline_submenu(&url, "Wiki Only", "y", "w", 0);

        }
        if( zType[0]!='c' && g.perm.Read ){



          timeline_submenu(&url, "Checkins Only", "y", "ci", 0);

        }
        if( zType[0]!='t' && g.perm.RdTkt ){



          timeline_submenu(&url, "Tickets Only", "y", "t", 0);

        }
        if( zType[0]!='e' && g.perm.RdWiki ){



          timeline_submenu(&url, "Events Only", "y", "e", 0);

        }
        if( zType[0]!='g' && g.perm.Read ){
          timeline_submenu(&url, "Tags Only", "y", "g", 0);
        }
      }
      if( nEntry>20 ){



        timeline_submenu(&url, "20 Entries", "n", "20", 0);

      }
      if( nEntry<200 ){



        timeline_submenu(&url, "200 Entries", "n", "200", 0);

      }
      if( zType[0]=='a' || zType[0]=='c' ){
        if( tmFlags & TIMELINE_FCHANGES ){



          timeline_submenu(&url, "Hide Files", "fc", 0, 0);

        }else{



          timeline_submenu(&url, "Show Files", "fc", "", 0);

        }
      }
    }
  }
  if( P("showsql") ){
    @ <blockquote>%h(blob_str(&sql))</blockquote>
  }






|







 







>
>
>

>





>
>
>
>
>
>
>
>
>
>
>
>







 







|
>
>
>
>
>
>







 







>
>
>
>
|
>
>

|
>
>
>
>
>
>







 







>
|
>
>
>
>
>







|
>
>
>
>
>
>
|
>
>
>
>
>




>
>
>
>
|
>
|













>
>
>
>
|
>
>






|
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
|
>
|
>
>
>
|
>
>








>
>
>

>



>
>
>

>


>
>
>

>




>
>
>
>
|
>
|

>
>
>
>
|
>
>


>
>
>

>

>
>
>

>


>
>
>

>




>
>
>

>




>
>
>

>



>
>
>

>


>
>
>

>


>
>
>

>


>
>
>

>


>
>
>

>






>
>
>

>


>
>
>

>



>
>
>

>

>
>
>

>







818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
...
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
....
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
....
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
....
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
....
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
*/
const char *timeline_query_for_www(void){
  static char *zBase = 0;
  static const char zBaseSql[] =
    @ SELECT
    @   blob.rid AS blobRid,
    @   uuid AS uuid,
    @   datetime(event.mtime, 'localtime') AS timestamp,
    @   coalesce(ecomment, comment) AS comment,
    @   coalesce(euser, user) AS user,
    @   blob.rid IN leaf AS leaf,
    @   bgcolor AS bgColor,
    @   event.type AS eventType,
    @   (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref
    @     WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
................................................................................
    zUuid
  );
  blob_zero(&out);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zFN = db_column_text(&q, 0);
    blob_appendf(&out, "%s%z%h</a>", zSep,
          href("%R/finfo?name=%t", zFN), zFN);
#ifdef LANG_RU
    zSep = " или ";
#elif LANG_EN
    zSep = " or ";
#endif
  }
  db_finalize(&q);
  return blob_str(&out);
}

#ifdef LANG_RU
const char* numberSuffix(int number, const char* s1, const char* s24, const char* s590){
	int lastDigit = number % 10;
	if(lastDigit == 1 && number != 11)
		return s1;
	if(lastDigit >= 2 && lastDigit <= 4 && (number < 12 || number > 14))
		return s24;
	return s590;
}
#elif LANG_EN
#define numberSuffix(n) (n==1?"":"s")
#endif

/*
** WEBPAGE: timeline
**
** Query parameters:
**
**    a=TIMEORTAG    after this event
................................................................................
      compute_uses_file("usesfile", ufid, 0);
      zType = "ci";
    }else{
      zUses = 0;
    }
  }

  style_header(
#ifdef LANG_RU
    "События"
#elif LANG_EN
    "Timeline"
#endif
    );
  login_anonymous_available();
  timeline_temp_table();
  blob_zero(&sql);
  blob_zero(&desc);
  blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1);
  blob_append(&sql, timeline_query_for_www(), -1);
  if( P("fc")!=0 || P("detail")!=0 ){
................................................................................
    blob_append(&sql, " AND event.objid IN (0", -1);
    while( p ){
      blob_appendf(&sql, ",%d", p->rid);
      p = p->u.pTo;
    }
    blob_append(&sql, ")", -1);
    path_reset();
    blob_append(&desc,
#ifdef LANG_RU
      "Все узлы с путём от ",
#elif LANG_EN
      "All nodes on the path from ",
#endif
      -1);
    blob_appendf(&desc, "%z%h</a>", href("%R/info/%h", zFrom), zFrom);
    blob_append(&desc,
#ifdef LANG_RU
      " до "
#elif LANG_EN
      " and "
#endif
      , -1);
    blob_appendf(&desc, "%z[%h]</a>", href("%R/info/%h",zTo), zTo);
    tmFlags |= TIMELINE_DISJOINT;
    db_multi_exec("%s", blob_str(&sql));
  }else if( (p_rid || d_rid) && g.perm.Read ){
    /* If p= or d= is present, ignore all other parameters other than n= */
    char *zUuid;
    int np, nd;
................................................................................
                         p_rid ? p_rid : d_rid);
    blob_appendf(&sql, " AND event.objid IN ok");
    nd = 0;
    if( d_rid ){
      compute_descendants(d_rid, nEntry+1);
      nd = db_int(0, "SELECT count(*)-1 FROM ok");
      if( nd>=0 ) db_multi_exec("%s", blob_str(&sql));
      if( nd>0 )
        blob_appendf(&desc,
#ifdef LANG_RU
          "%d потом%s", nd, numberSuffix(nd, "ок", "ка", "ков"));
#elif LANG_EN
          "%d descendant%s", nd, numberSuffix(nd));
#endif
      if( useDividers ) timeline_add_dividers(0, d_rid);
      db_multi_exec("DELETE FROM ok");
    }
    if( p_rid ){
      compute_ancestors(p_rid, nEntry+1, 0);
      np = db_int(0, "SELECT count(*)-1 FROM ok");
      if( np>0 ){
        if( nd>0 ) blob_appendf(&desc,
#ifdef LANG_RU
          " и "
#elif LANG_EN
          " and "
#endif
          );
        blob_appendf(&desc,
#ifdef LANG_RU
          "%d пред%s", np, numberSuffix(np, "ок", "ка", "ков"));
#elif LANG_EN
          "%d ancestor%s", numberSuffix(np));
#endif
        db_multi_exec("%s", blob_str(&sql));
      }
      if( d_rid==0 && useDividers ) timeline_add_dividers(0, p_rid);
    }
    blob_appendf(&desc,
#ifdef LANG_RU
      " %z[%.10s]</a>",
#elif LANG_EN
      " of %z[%.10s]</a>",
#endif
         href("%R/info/%s", zUuid), zUuid);
  }else if( f_rid && g.perm.Read ){
    /* If f= is present, ignore all other parameters other than n= */
    char *zUuid;
    db_multi_exec(
       "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
       "INSERT INTO ok VALUES(%d);"
       "INSERT OR IGNORE INTO ok SELECT pid FROM plink WHERE cid=%d;"
       "INSERT OR IGNORE INTO ok SELECT cid FROM plink WHERE pid=%d;",
       f_rid, f_rid, f_rid
    );
    blob_appendf(&sql, " AND event.objid IN ok");
    db_multi_exec("%s", blob_str(&sql));
    if( useDividers ) timeline_add_dividers(0, f_rid);
    blob_appendf(&desc,
#ifdef LANG_RU
      "Предки и потомки фиксации "
#elif LANG_EN
      "Parents and children of check-in "
#endif
      );
    zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid);
    blob_appendf(&desc, "%z[%.10s]</a>", href("%R/info/%s", zUuid), zUuid);
    tmFlags |= TIMELINE_DISJOINT;
  }else{
    /* Otherwise, a timeline based on a span of time */
    int n;
    const char *zEType =
#ifdef LANG_RU
      "событие";
#elif LANG_EN
      "timeline item";
#endif
    char *zDate;
    char *zNEntry = mprintf("%d", nEntry);
    url_add_parameter(&url, "n", zNEntry);
    if( zUses ){
      blob_appendf(&sql, " AND event.objid IN usesfile ");
    }
    if( tagid>0 ){
................................................................................
    }else{
      blob_appendf(&sql, " ORDER BY event.mtime DESC");
    }
    blob_appendf(&sql, " LIMIT %d", nEntry);
    db_multi_exec("%s", blob_str(&sql));

    n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/");
    {
#ifdef LANG_RU
      const char* rZEtype;
      const char* rSuffix;
      const char* rLastTitleSuffix = numberSuffix(n, "яя", "ие", "их");
      switch(zEType[0]){
          case 'c': /*check-in*/
            rZEtype = "фиксаци";
            rSuffix = numberSuffix(n, "я", "и", "й");
            break;
          case 'w': /*wiki-edit*/
            rZEtype = "вики-прав";
            rSuffix = numberSuffix(n, "ка", "ки", "ок");
            break;
          case 't': /*tiket-change*/
            rZEtype = "задач";
            rSuffix = numberSuffix(n, "а", "и", "");
            break;
          case 'e': /*events*/
            rZEtype = "новост";
            rSuffix = numberSuffix(n, "ь", "и", "ей");
            break;
          default:
            rZEtype = "событи";
            rSuffix = numberSuffix(n, "е", "я", "й");
            rLastTitleSuffix = numberSuffix(n, "ее", "их", "их");
      }
#endif
      if( zAfter==0 && zBefore==0 && zCirca==0 ){
 #ifdef LANG_RU
        blob_appendf(&desc, "%d последн%s %s%s", n, rLastTitleSuffix, rZEtype, rSuffix);
#elif LANG_EN
        blob_appendf(&desc, "%d most recent %ss", n, zEType);
#endif
      }else{
#ifdef LANG_RU
        blob_appendf(&desc, "%d %s%s", n, rZEtype, rSuffix);
#elif LANG_EN
        blob_appendf(&desc, "%d %ss", n, zEType);
#endif
      }
    }
    if( zUses ){
      char *zFilenames = names_of_file(zUses);
      blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames,
                   href("%R/artifact/%S",zUses), zUses);
      tmFlags |= TIMELINE_DISJOINT;
    }
    if( zUser ){
#ifdef LANG_RU
      blob_appendf(&desc, " пользователя %h", zUser);
#elif LANG_EN
      blob_appendf(&desc, " by user %h", zUser);
#endif
      tmFlags |= TIMELINE_DISJOINT;
    }
    if( zTagName ){
#ifdef LANG_RU
      blob_appendf(&desc, " с тэгом \"%h\"", zTagName);
#elif LANG_EN
      blob_appendf(&desc, " tagged with \"%h\"", zTagName);
#endif
      tmFlags |= TIMELINE_DISJOINT;
    }else if( zBrName ){
#ifdef LANG_RU
      blob_appendf(&desc, " ветки \"%h\"", zBrName);
#elif LANG_EN
      blob_appendf(&desc, " related to \"%h\"", zBrName);
#endif
      tmFlags |= TIMELINE_DISJOINT;
    }
    if( rAfter>0.0 ){
      if( rBefore>0.0 ){
        blob_appendf(&desc,
#ifdef LANG_RU
			" между %h and %h.<br>"
#elif LANG_EN
			" occurring between %h and %h.<br>"
#endif
			, zAfter, zBefore);
      }else{
        blob_appendf(&desc,
#ifdef LANG_RU
			" после %h.<br />"
#elif LANG_EN
			" occurring on or after %h.<br />"
#endif
			, zAfter);
      }
    }else if( rBefore>0.0 ){
#ifdef LANG_RU
      blob_appendf(&desc, " до %h.<br />", zBefore);
#elif LANG_EN
      blob_appendf(&desc, " occurring on or before %h.<br />", zBefore);
#endif
    }else if( rCirca>0.0 ){
#ifdef LANG_RU
      blob_appendf(&desc, " около %h.<br />", zCirca);
#elif LANG_EN
      blob_appendf(&desc, " occurring around %h.<br />", zCirca);
#endif
    }
    if( zSearch ){
#ifdef LANG_RU
      blob_appendf(&desc, " похожие на \"%h\"", zSearch);
#elif LANG_EN
      blob_appendf(&desc, " matching \"%h\"", zSearch);
#endif
    }
    if( g.perm.Hyperlink ){
      if( zAfter || n==nEntry ){
        zDate = db_text(0, "SELECT min(timestamp) FROM timeline /*scan*/");
#ifdef LANG_RU
        timeline_submenu(&url, "Ранее", "b", zDate, "a");
#elif LANG_EN
        timeline_submenu(&url, "Older", "b", zDate, "a");
#endif
        free(zDate);
      }
      if( zBefore || (zAfter && n==nEntry) ){
        zDate = db_text(0, "SELECT max(timestamp) FROM timeline /*scan*/");
#ifdef LANG_RU
        timeline_submenu(&url, "Новее", "a", zDate, "b");
#elif LANG_EN
        timeline_submenu(&url, "Newer", "a", zDate, "b");
#endif
        free(zDate);
      }else if( tagid==0 ){
        if( zType[0]!='a' ){
#ifdef LANG_RU
          timeline_submenu(&url, "Все типы", "y", "all", 0);
#elif LANG_EN
          timeline_submenu(&url, "All Types", "y", "all", 0);
#endif
        }
        if( zType[0]!='w' && g.perm.RdWiki ){
#ifdef LANG_RU
          timeline_submenu(&url, "Только вики-правки", "y", "w", 0);
#elif LANG_EN
          timeline_submenu(&url, "Wiki Only", "y", "w", 0);
#endif
        }
        if( zType[0]!='c' && g.perm.Read ){
#ifdef LANG_RU
          timeline_submenu(&url, "Только фиксации", "y", "ci", 0);
#elif LANG_EN
          timeline_submenu(&url, "Checkins Only", "y", "ci", 0);
#endif
        }
        if( zType[0]!='t' && g.perm.RdTkt ){
#ifdef LANG_RU
          timeline_submenu(&url, "Только задачи", "y", "t", 0);
#elif LANG_EN
          timeline_submenu(&url, "Tickets Only", "y", "t", 0);
#endif
        }
        if( zType[0]!='e' && g.perm.RdWiki ){
#ifdef LANG_RU
          timeline_submenu(&url, "Только новости", "y", "e", 0);
#elif LANG_EN
          timeline_submenu(&url, "Events Only", "y", "e", 0);
#endif
        }
        if( zType[0]!='g' && g.perm.Read ){
          timeline_submenu(&url, "Tags Only", "y", "g", 0);
        }
      }
      if( nEntry>20 ){
#ifdef LANG_RU
        timeline_submenu(&url, "20 событий", "n", "20", 0);
#elif LANG_EN
        timeline_submenu(&url, "20 Entries", "n", "20", 0);
#endif
      }
      if( nEntry<200 ){
#ifdef LANG_RU
        timeline_submenu(&url, "200 событий", "n", "200", 0);
#elif LANG_EN
        timeline_submenu(&url, "200 Entries", "n", "200", 0);
#endif
      }
      if( zType[0]=='a' || zType[0]=='c' ){
        if( tmFlags & TIMELINE_FCHANGES ){
#ifdef LANG_RU
          timeline_submenu(&url, "Не показывать файлы", "fc", 0, 0);
#elif LANG_EN
          timeline_submenu(&url, "Hide Files", "fc", 0, 0);
#endif
        }else{
#ifdef LANG_RU
          timeline_submenu(&url, "Показывать файлы", "fc", "", 0);
#elif LANG_EN
          timeline_submenu(&url, "Show Files", "fc", "", 0);
#endif
        }
      }
    }
  }
  if( P("showsql") ){
    @ <blockquote>%h(blob_str(&sql))</blockquote>
  }

Changes to src/tkt.c.

420
421
422
423
424
425
426




427


428
429
430




431

432




433

434




435

436
437
438




439

440
441
442




443

444
445
446
447



448

449



450

451
452
453






454
455
456
457
458
459
460
...
462
463
464
465
466
467
468



469

470
471
472
473
474
475
476
...
558
559
560
561
562
563
564



565

566
567
568
569
570
571
572
...
579
580
581
582
583
584
585






586
587
588
589
590
591
592
...
650
651
652
653
654
655
656
657






658
659
660
661
662
663
664
...
701
702
703
704
705
706
707
708






709
710



711

712
713
714
715
716
717



718

719
720
721
722



723

724
725
726
727
728
729
730
...
801
802
803
804
805
806
807




808

809
810




811

812
813
814





815
816





817
818




819


820




821


822
823
824
825
826
827
828
829



830

831
832
833
834
835
836
837
...
874
875
876
877
878
879
880




881


882





883



884

885



886

887
888
889
890
891
892
893
894
895
896
897
898
899



900

901
902
903
904
905
906
907
...
928
929
930
931
932
933
934



935

936
937



938

939
940
941




942
943

944
945
946
947
948






949
950
951
952

953
954
955
956
957
958
959
  const char *zScript;
  char *zFullName;
  const char *zUuid = PD("name","");

  login_check_credentials();
  if( !g.perm.RdTkt ){ login_needed(); return; }
  if( g.perm.WrTkt || g.perm.ApndTkt ){




    style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T",


        g.zTop, PD("name",""));
  }
  if( g.perm.Hyperlink ){




    style_submenu_element("History", "History Of This Ticket", 

        "%s/tkthistory/%T", g.zTop, zUuid);




    style_submenu_element("Timeline", "Timeline Of This Ticket", 

        "%s/tkttimeline/%T", g.zTop, zUuid);




    style_submenu_element("Check-ins", "Check-ins Of This Ticket", 

        "%s/tkttimeline/%T?y=ci", g.zTop, zUuid);
  }
  if( g.perm.NewTkt ){




    style_submenu_element("New Ticket", "Create a new ticket",

        "%s/tktnew", g.zTop);
  }
  if( g.perm.ApndTkt && g.perm.Attach ){




    style_submenu_element("Attach", "Add An Attachment",

        "%s/attachadd?tkt=%T&from=%s/tktview/%t",
        g.zTop, zUuid, g.zTop, zUuid);
  }
  if( P("plaintext") ){



    style_submenu_element("Formatted", "Formatted", "%R/tktview/%S", zUuid);

  }else{



    style_submenu_element("Plaintext", "Plaintext",

                          "%R/tktview/%S?plaintext", zUuid);
  }
  style_header("View Ticket");






  if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br />\n", -1);
  ticket_init();
  initializeVariablesFromCGI();
  getAllTicketFields();
  initializeVariablesFromDb();
  zScript = ticket_viewpage_code();
  if( P("showfields")!=0 ) showAllFields();
................................................................................
  Th_Render(zScript);
  if( g.thTrace ) Th_Trace("END_TKTVIEW<br />\n", -1);

  zFullName = db_text(0, 
       "SELECT tkt_uuid FROM ticket"
       " WHERE tkt_uuid GLOB '%q*'", zUuid);
  if( zFullName ){



    attachment_list(zFullName, "<hr /><h2>Attachments:</h2><ul>");

  }
 
  style_footer();
}

/*
** TH command:   append_field FIELD STRING
................................................................................
  const char *zUuid;
  int i;
  int nJ = 0;
  Blob tktchng, cksum;

  login_verify_csrf_secret();
  if( !captcha_is_correct() ){



    @ <p class="generalError">Error: Incorrect security code.</p>

    return TH_OK;
  }
  zUuid = (const char *)pUuid;
  blob_zero(&tktchng);
  zDate = date_in_standard_format("now");
  blob_appendf(&tktchng, "D %s\n", zDate);
  free(zDate);
................................................................................
  }
  for(i=0; i<nField; i++){
    const char *zValue;
    int nValue;
    if( aField[i].zAppend ) continue;
    zValue = Th_Fetch(aField[i].zName, &nValue);
    if( zValue ){






      while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; }
      if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0)
       || memcmp(zValue, aField[i].zValue, nValue)!=0
       || strlen(aField[i].zValue)!=nValue
      ){
        if( memcmp(aField[i].zName, "private_", 8)==0 ){
          zValue = db_conceal(zValue, nValue);
................................................................................
  char *zNewUuid = 0;

  login_check_credentials();
  if( !g.perm.NewTkt ){ login_needed(); return; }
  if( P("cancel") ){
    cgi_redirect("home");
  }
  style_header("New Ticket");






  if( g.thTrace ) Th_Trace("BEGIN_TKTNEW<br />\n", -1);
  ticket_init();
  initializeVariablesFromCGI();
  getAllTicketFields();
  initializeVariablesFromDb();
  if( g.zPath[0]=='d' ) showAllFields();
  form_begin(0, "%R/%s", g.zPath);
................................................................................

  login_check_credentials();
  if( !g.perm.ApndTkt && !g.perm.WrTkt ){ login_needed(); return; }
  zName = P("name");
  if( P("cancel") ){
    cgi_redirectf("tktview?name=%T", zName);
  }
  style_header("Edit Ticket");






  if( zName==0 || (nName = strlen(zName))<4 || nName>UUID_SIZE
          || !validate16(zName,nName) ){



    @ <span class="tktError">Not a valid ticket id: \"%h(zName)\"</span>

    style_footer();
    return;
  }
  nRec = db_int(0, "SELECT count(*) FROM ticket WHERE tkt_uuid GLOB '%q*'",
                zName);
  if( nRec==0 ){



    @ <span class="tktError">No such ticket: \"%h(zName)\"</span>

    style_footer();
    return;
  }
  if( nRec>1 ){



    @ <span class="tktError">%d(nRec) tickets begin with:

    @ \"%h(zName)\"</span>
    style_footer();
    return;
  }
  if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT<br />\n", -1);
  ticket_init();
  getAllTicketFields();
................................................................................
  const char *zType;

  login_check_credentials();
  if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; }
  zUuid = PD("name","");
  zType = PD("y","a");
  if( zType[0]!='c' ){




    style_submenu_element("Check-ins", "Check-ins",

       "%s/tkttimeline?name=%T&y=ci", g.zTop, zUuid);
  }else{




    style_submenu_element("Timeline", "Timeline",

       "%s/tkttimeline?name=%T", g.zTop, zUuid);
  }
  style_submenu_element("History", "History",





    "%s/tkthistory/%s", g.zTop, zUuid);
  style_submenu_element("Status", "Status",





    "%s/info/%s", g.zTop, zUuid);
  if( zType[0]=='c' ){




    zTitle = mprintf("Check-Ins Associated With Ticket %h", zUuid);


  }else{




    zTitle = mprintf("Timeline Of Ticket %h", zUuid);


  }
  style_header(zTitle);
  free(zTitle);

  sqlite3_snprintf(6, zGlobPattern, "%s", zUuid);
  canonical16(zGlobPattern, strlen(zGlobPattern));
  tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid);
  if( tagid==0 ){



    @ No such ticket: %h(zUuid)

    style_footer();
    return;
  }
  zFullUuid = db_text(0, "SELECT substr(tagname, 5) FROM tag WHERE tagid=%d",
                         tagid);
  if( zType[0]=='c' ){
    zSQL = mprintf(
................................................................................
  const char *zUuid;
  int tagid;
  int nChng = 0;

  login_check_credentials();
  if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; }
  zUuid = PD("name","");




  zTitle = mprintf("History Of Ticket %h", zUuid);


  style_submenu_element("Status", "Status",





    "%s/info/%s", g.zTop, zUuid);



  style_submenu_element("Check-ins", "Check-ins",

    "%s/tkttimeline?name=%s&y=ci", g.zTop, zUuid);



  style_submenu_element("Timeline", "Timeline",

    "%s/tkttimeline?name=%s", g.zTop, zUuid);
  if( P("plaintext")!=0 ){
    style_submenu_element("Formatted", "Formatted",
                          "%R/tkthistory/%S", zUuid);
  }else{
    style_submenu_element("Plaintext", "Plaintext",
                          "%R/tkthistory/%S?plaintext", zUuid);
  }
  style_header(zTitle);
  free(zTitle);

  tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid);
  if( tagid==0 ){



    @ No such ticket: %h(zUuid)

    style_footer();
    return;
  }
  db_prepare(&q,
    "SELECT datetime(mtime,'localtime'), objid, uuid, NULL, NULL, NULL"
    "  FROM event, blob"
    " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)"
................................................................................
    }
    nChng++;
    if( zFile!=0 ){
      const char *zSrc = db_column_text(&q, 3);
      const char *zUser = db_column_text(&q, 5);
      if( zSrc==0 || zSrc[0]==0 ){
        @ 



        @ <li><p>Delete attachment "%h(zFile)"

      }else{
        @ 



        @ <li><p>Add attachment

        @ "%z(href("%R/artifact/%S",zSrc))%h(zFile)</a>"
      }
      @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>]




      @ (rid %d(rid)) by
      hyperlink_to_user(zUser,zDate," on");

      hyperlink_to_date(zDate, ".</p>");
    }else{
      pTicket = manifest_get(rid, CFTYPE_TICKET);
      if( pTicket ){
        @






        @ <li><p>Ticket change
        @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>]
        @ (rid %d(rid)) by
        hyperlink_to_user(pTicket->zUser,zDate," on");

        hyperlink_to_date(zDate, ":");
        @ </p>
        ticket_output_change_artifact(pTicket, "a");
      }
      manifest_destroy(pTicket);
    }
  }






>
>
>
>
|
>
>



>
>
>
>
|
>

>
>
>
>
|
>

>
>
>
>
|
>



>
>
>
>
|
>



>
>
>
>
|
>




>
>
>

>

>
>
>

>


|
>
>
>
>
>
>







 







>
>
>

>







 







>
>
>

>







 







>
>
>
>
>
>







 







|
>
>
>
>
>
>







 







|
>
>
>
>
>
>


>
>
>
|
>






>
>
>

>




>
>
>

>







 







>
>
>
>
|
>


>
>
>
>
|
>


|
>
>
>
>
>

|
>
>
>
>
>


>
>
>
>
|
>
>

>
>
>
>
|
>
>








>
>
>

>







 







>
>
>
>
|
>
>
|
>
>
>
>
>

>
>
>
|
>

>
>
>
|
>













>
>
>

>







 







>
>
>

>


>
>
>

>
|


>
>
>
>


>





>
>
>
>
>
>




>







420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
...
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
...
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
...
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
...
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
...
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
...
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
...
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
....
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
  const char *zScript;
  char *zFullName;
  const char *zUuid = PD("name","");

  login_check_credentials();
  if( !g.perm.RdTkt ){ login_needed(); return; }
  if( g.perm.WrTkt || g.perm.ApndTkt ){
    style_submenu_element(
#ifdef LANG_RU
      "Правка", "Изменить задачу",
#elif LANG_EN
      "Edit", "Edit The Ticket",
#endif
      "%s/tktedit?name=%T",
        g.zTop, PD("name",""));
  }
  if( g.perm.Hyperlink ){
    style_submenu_element(
#ifdef LANG_RU
      "История", "Исория задачи", 
#elif LANG_EN
      "History", "History Of This Ticket", 
#endif
        "%s/tkthistory/%T", g.zTop, zUuid);
    style_submenu_element(
#ifdef LANG_RU
      "События", "События по этой задаче", 
#elif LANG_EN
      "Timeline", "Timeline Of This Ticket", 
#endif
        "%s/tkttimeline/%T", g.zTop, zUuid);
    style_submenu_element(
#ifdef LANG_RU
      "Фиксации", "Фиксации по этой задаче", 
#elif LANG_EN
      "Check-ins", "Check-ins Of This Ticket", 
#endif
        "%s/tkttimeline/%T?y=ci", g.zTop, zUuid);
  }
  if( g.perm.NewTkt ){
    style_submenu_element(
#ifdef LANG_RU
      "Новая задача", "Создать новую задачу",
#elif LANG_EN
      "New Ticket", "Create a new ticket",
#endif
        "%s/tktnew", g.zTop);
  }
  if( g.perm.ApndTkt && g.perm.Attach ){
    style_submenu_element(
#ifdef LANG_RU
      "Добавить файл", "Добавить файл к задаче",
#elif LANG_EN
      "Attach", "Add An Attachment",
#endif
        "%s/attachadd?tkt=%T&from=%s/tktview/%t",
        g.zTop, zUuid, g.zTop, zUuid);
  }
  if( P("plaintext") ){
#ifdef LANG_RU
    style_submenu_element("Форматировано", "Форматировано", "%R/tktview/%S", zUuid);
#elif LANG_EN
    style_submenu_element("Formatted", "Formatted", "%R/tktview/%S", zUuid);
#endif
  }else{
#ifdef LANG_RU
    style_submenu_element("Текстом", "Текстом",
#elif LANG_EN
    style_submenu_element("Plaintext", "Plaintext",
#endif
                          "%R/tktview/%S?plaintext", zUuid);
  }
  style_header(
#ifdef LANG_RU
	  "Просмотр задачи"
#elif LANG_EN
	  "View Ticket"
#endif
	  );
  if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br />\n", -1);
  ticket_init();
  initializeVariablesFromCGI();
  getAllTicketFields();
  initializeVariablesFromDb();
  zScript = ticket_viewpage_code();
  if( P("showfields")!=0 ) showAllFields();
................................................................................
  Th_Render(zScript);
  if( g.thTrace ) Th_Trace("END_TKTVIEW<br />\n", -1);

  zFullName = db_text(0, 
       "SELECT tkt_uuid FROM ticket"
       " WHERE tkt_uuid GLOB '%q*'", zUuid);
  if( zFullName ){
#ifdef LANG_RU
    attachment_list(zFullName, "<hr /><h2>Вложения:</h2><ul>");
#elif LANG_EN
    attachment_list(zFullName, "<hr /><h2>Attachments:</h2><ul>");
#endif
  }
 
  style_footer();
}

/*
** TH command:   append_field FIELD STRING
................................................................................
  const char *zUuid;
  int i;
  int nJ = 0;
  Blob tktchng, cksum;

  login_verify_csrf_secret();
  if( !captcha_is_correct() ){
#ifdef LANG_RU
    @ <p class="generalError">Ошибка: неверный код.</p>
#elif LANG_EN
    @ <p class="generalError">Error: Incorrect security code.</p>
#endif
    return TH_OK;
  }
  zUuid = (const char *)pUuid;
  blob_zero(&tktchng);
  zDate = date_in_standard_format("now");
  blob_appendf(&tktchng, "D %s\n", zDate);
  free(zDate);
................................................................................
  }
  for(i=0; i<nField; i++){
    const char *zValue;
    int nValue;
    if( aField[i].zAppend ) continue;
    zValue = Th_Fetch(aField[i].zName, &nValue);
    if( zValue ){
      const char* zTemp = wiki_sanify(zValue);
      if(zTemp != zValue)
      {
        zValue = zTemp;
        nValue = strlen(zValue);
      }
      while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; }
      if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0)
       || memcmp(zValue, aField[i].zValue, nValue)!=0
       || strlen(aField[i].zValue)!=nValue
      ){
        if( memcmp(aField[i].zName, "private_", 8)==0 ){
          zValue = db_conceal(zValue, nValue);
................................................................................
  char *zNewUuid = 0;

  login_check_credentials();
  if( !g.perm.NewTkt ){ login_needed(); return; }
  if( P("cancel") ){
    cgi_redirect("home");
  }
  style_header(
#ifdef LANG_RU
    "Новая задача"
#elif LANG_EN
    "New Ticket"
#endif
    );
  if( g.thTrace ) Th_Trace("BEGIN_TKTNEW<br />\n", -1);
  ticket_init();
  initializeVariablesFromCGI();
  getAllTicketFields();
  initializeVariablesFromDb();
  if( g.zPath[0]=='d' ) showAllFields();
  form_begin(0, "%R/%s", g.zPath);
................................................................................

  login_check_credentials();
  if( !g.perm.ApndTkt && !g.perm.WrTkt ){ login_needed(); return; }
  zName = P("name");
  if( P("cancel") ){
    cgi_redirectf("tktview?name=%T", zName);
  }
  style_header(
#ifdef LANG_RU
    "Изменение задачи"
#elif LANG_EN
    "Edit Ticket"
#endif
    );
  if( zName==0 || (nName = strlen(zName))<4 || nName>UUID_SIZE
          || !validate16(zName,nName) ){
#ifdef LANG_RU
          @ <span class="tktError">Неверный ID задачи: \"%h(zName)\"</span>
#elif LANG_EN
          @ <span class="tktError">Not a valid ticket id: \"%h(zName)\"</span>
#endif
    style_footer();
    return;
  }
  nRec = db_int(0, "SELECT count(*) FROM ticket WHERE tkt_uuid GLOB '%q*'",
                zName);
  if( nRec==0 ){
#ifdef LANG_RU
    @ <span class="tktError">Не найдена задача: \"%h(zName)\"</span>
#elif LANG_EN
    @ <span class="tktError">No such ticket: \"%h(zName)\"</span>
#endif
    style_footer();
    return;
  }
  if( nRec>1 ){
#ifdef LANG_RU
    @ <span class="tktError">Несколько задач (%d(nRec)) начинаются с:
#elif LANG_EN
    @ <span class="tktError">%d(nRec) tickets begin with:
#endif
    @ \"%h(zName)\"</span>
    style_footer();
    return;
  }
  if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT<br />\n", -1);
  ticket_init();
  getAllTicketFields();
................................................................................
  const char *zType;

  login_check_credentials();
  if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; }
  zUuid = PD("name","");
  zType = PD("y","a");
  if( zType[0]!='c' ){
    style_submenu_element(
#ifdef LANG_RU
      "Фиксации", "Фиксации",
#elif LANG_EN
      "Check-ins", "Check-ins",
#endif
       "%s/tkttimeline?name=%T&y=ci", g.zTop, zUuid);
  }else{
    style_submenu_element(
#ifdef LANG_RU
      "События", "События",
#elif LANG_EN
      "Timeline", "Timeline",
#endif
       "%s/tkttimeline?name=%T", g.zTop, zUuid);
  }
  style_submenu_element(
#ifdef LANG_RU
    "История", "История",
#elif LANG_EN
    "History", "History",
#endif
    "%s/tkthistory/%s", g.zTop, zUuid);
  style_submenu_element(
#ifdef LANG_RU
    "Статус", "Статус",
#elif LANG_EN
    "Status", "Status",
#endif
    "%s/info/%s", g.zTop, zUuid);
  if( zType[0]=='c' ){
    zTitle = mprintf(
#ifdef LANG_RU
      "Фиксации, связанные с задачей %h",
#elif LANG_EN
      "Check-Ins Associated With Ticket %h",
#endif
      zUuid);
  }else{
    zTitle = mprintf(
#ifdef LANG_RU
      "События, связанные с задачей %h",
#elif LANG_EN
      "Timeline Of Ticket %h",
#endif
      zUuid);
  }
  style_header(zTitle);
  free(zTitle);

  sqlite3_snprintf(6, zGlobPattern, "%s", zUuid);
  canonical16(zGlobPattern, strlen(zGlobPattern));
  tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid);
  if( tagid==0 ){
#ifdef LANG_RU
    @ Задача %h(zUuid) не найдена.
#elif LANG_EN
    @ No such ticket: %h(zUuid)
#endif
    style_footer();
    return;
  }
  zFullUuid = db_text(0, "SELECT substr(tagname, 5) FROM tag WHERE tagid=%d",
                         tagid);
  if( zType[0]=='c' ){
    zSQL = mprintf(
................................................................................
  const char *zUuid;
  int tagid;
  int nChng = 0;

  login_check_credentials();
  if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; }
  zUuid = PD("name","");
  zTitle = mprintf(
#ifdef LANG_RU
    "История задачи %h",
#elif LANG_EN
    "History Of Ticket %h",
#endif
    zUuid);
  style_submenu_element(
#ifdef LANG_RU
    "Статус", "Статус",
#elif LANG_EN
    "Status", "Status",
#endif
    "%s/info/%s", g.zTop, zUuid);
#ifdef LANG_RU
    style_submenu_element("Фиксации", "Фиксации",
#elif LANG_EN
    style_submenu_element("Check-ins", "Check-ins",
#endif
    "%s/tkttimeline?name=%s&y=ci", g.zTop, zUuid);
#ifdef LANG_RU
    style_submenu_element("События", "События",
#elif LANG_EN
    style_submenu_element("Timeline", "Timeline",
#endif
    "%s/tkttimeline?name=%s", g.zTop, zUuid);
  if( P("plaintext")!=0 ){
    style_submenu_element("Formatted", "Formatted",
                          "%R/tkthistory/%S", zUuid);
  }else{
    style_submenu_element("Plaintext", "Plaintext",
                          "%R/tkthistory/%S?plaintext", zUuid);
  }
  style_header(zTitle);
  free(zTitle);

  tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid);
  if( tagid==0 ){
#ifdef LANG_RU
    @ Задача %h(zUuid) не найдена
#elif LANG_EN
    @ No such ticket: %h(zUuid)
#endif
    style_footer();
    return;
  }
  db_prepare(&q,
    "SELECT datetime(mtime,'localtime'), objid, uuid, NULL, NULL, NULL"
    "  FROM event, blob"
    " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)"
................................................................................
    }
    nChng++;
    if( zFile!=0 ){
      const char *zSrc = db_column_text(&q, 3);
      const char *zUser = db_column_text(&q, 5);
      if( zSrc==0 || zSrc[0]==0 ){
        @ 
#ifdef LANG_RU
        @ <li>Удалено вложение "%h(zFile)"
#elif LANG_EN
        @ <li><p>Delete attachment "%h(zFile)"
#endif
      }else{
        @ 
#ifdef LANG_RU
        @ <li><p>Добавлено вложение
#elif LANG_EN
        @ <li><p>Add attachment
#endif
		@ "%z(href("%R/artifact/%S",zSrc))%h(zFile)</a>"
      }
      @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>]
#ifdef LANG_RU
      @ (rid %d(rid)) пользователем
      hyperlink_to_user(zUser,zDate," ");
#elif LANG_EN
      @ (rid %d(rid)) by
      hyperlink_to_user(zUser,zDate," on");
#endif
      hyperlink_to_date(zDate, ".</p>");
    }else{
      pTicket = manifest_get(rid, CFTYPE_TICKET);
      if( pTicket ){
        @
#ifdef LANG_RU
        @ <li>Задача изменена
        @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>]
        @ (rid %d(rid)) пользователем
        hyperlink_to_user(pTicket->zUser,zDate," ");
#elif LANG_EN
        @ <li><p>Ticket change
        @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>]
        @ (rid %d(rid)) by
        hyperlink_to_user(pTicket->zUser,zDate," on");
#endif
        hyperlink_to_date(zDate, ":");
        @ </p>
        ticket_output_change_artifact(pTicket, "a");
      }
      manifest_destroy(pTicket);
    }
  }

Changes to src/url.c.

378
379
380
381
382
383
384
385






386
387
388
389
390
391




392

393
394
395
396
397
398
399
/*
** Prompt the user for the password for g.urlUser.  Store the result
** in g.urlPasswd.
*/
void url_prompt_for_password(void){
  if( isatty(fileno(stdin)) ){
    char *zPrompt = mprintf("\rpassword for %s: ", g.urlUser);






    Blob x;
    prompt_for_password(zPrompt, &x, 0);
    free(zPrompt);
    g.urlPasswd = mprintf("%b", &x);
    blob_reset(&x);
  }else{




    fossil_fatal("missing or incorrect password for user \"%s\"",

                 g.urlUser);
  }
}

/* Preemptively prompt for a password if a username is given in the
** URL but no password.
*/






|
>
>
>
>
>
>






>
>
>
>
|
>







378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
/*
** Prompt the user for the password for g.urlUser.  Store the result
** in g.urlPasswd.
*/
void url_prompt_for_password(void){
  if( isatty(fileno(stdin)) ){
    char *zPrompt = mprintf(
#ifdef LANG_RU
		"\rПароль для %s: ",
#elif LANG_EN
		"\rpassword for %s: ",
#endif
		g.urlUser);
    Blob x;
    prompt_for_password(zPrompt, &x, 0);
    free(zPrompt);
    g.urlPasswd = mprintf("%b", &x);
    blob_reset(&x);
  }else{
    fossil_fatal(
#ifdef LANG_RU
		"Отсутствует или неверен пароль для пользователя \"%s\"",
#elif LANG_EN
		"missing or incorrect password for user \"%s\"",
#endif
                 g.urlUser);
  }
}

/* Preemptively prompt for a password if a username is given in the
** URL but no password.
*/

Changes to src/user.c.

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
...
113
114
115
116
117
118
119




120


121




122


123
124
125
126
127
128
129
#endif
/*
** getpass for Windows
*/
static char *getpass(const char *prompt){
  static char pwd[64];
  size_t i;

  fputs(prompt,stderr);
  fflush(stderr);
  for(i=0; i<sizeof(pwd)-1; ++i){
    pwd[i] = _getch();
    if(pwd[i]=='\r' || pwd[i]=='\n'){
      break;
    }
    /* BS or DEL */
................................................................................
  Blob secondTry;
  blob_zero(pPassphrase);
  blob_zero(&secondTry);
  while(1){
    prompt_for_passphrase(zPrompt, pPassphrase);
    if( verify==0 ) break;
    if( verify==1 && blob_size(pPassphrase)==0 ) break;




    prompt_for_passphrase("Retype new password: ", &secondTry);


    if( blob_compare(pPassphrase, &secondTry) ){




      fossil_print("Passphrases do not match.  Try again...\n");


    }else{
      break;
    }
  }
  blob_reset(&secondTry);
}







<
|







 







>
>
>
>
|
>
>

>
>
>
>
|
>
>







47
48
49
50
51
52
53

54
55
56
57
58
59
60
61
...
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#endif
/*
** getpass for Windows
*/
static char *getpass(const char *prompt){
  static char pwd[64];
  size_t i;

  fossil_puts(prompt,1);
  fflush(stderr);
  for(i=0; i<sizeof(pwd)-1; ++i){
    pwd[i] = _getch();
    if(pwd[i]=='\r' || pwd[i]=='\n'){
      break;
    }
    /* BS or DEL */
................................................................................
  Blob secondTry;
  blob_zero(pPassphrase);
  blob_zero(&secondTry);
  while(1){
    prompt_for_passphrase(zPrompt, pPassphrase);
    if( verify==0 ) break;
    if( verify==1 && blob_size(pPassphrase)==0 ) break;
    prompt_for_passphrase(
#ifdef LANG_RU
		"��������� ����� ������: ",
#elif LANG_EN
		"Retype new password: ",
#endif
		&secondTry);
    if( blob_compare(pPassphrase, &secondTry) ){
      fossil_print(
#ifdef LANG_RU
		  "������ �� ���������. ���������� ��� ���...\n"
#elif LANG_EN
		  "Passphrases do not match.  Try again...\n"
#endif
		  );
    }else{
      break;
    }
  }
  blob_reset(&secondTry);
}

Changes to src/wiki.c.

45
46
47
48
49
50
51









52
53
54
55
56
57
58

59
60
61
62
63
64
65
66





67
68
69

70
71
72
73
74
75
76
..
99
100
101
102
103
104
105









106
107
108
109
110
111
112

113
114
115
116
117
118
119
...
129
130
131
132
133
134
135



136
137

138
139
140
141




142
143

144
145
146



147

148
149





150
151
152
153

154



155

156



157

158
159







160
161
162
163
164
165
166
167
168

169
170
171
172
173
174
175
...
195
196
197
198
199
200
201




202

203
204
205




206

207
208
209
210
211




212

213
214
215
216




217


218
219
220
221






222
223
224
225
226
227
228
...
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
...
704
705
706
707
708
709
710



711


712



713

714



715

716

717
718
719
720
721
722
723
  return 1;
}

/*
** Output rules for well-formed wiki pages
*/
static void well_formed_wiki_name_rules(void){









  @ <ul>
  @ <li> Must not begin or end with a space.</li>
  @ <li> Must not contain any control characters, including tab or
  @      newline.</li>
  @ <li> Must not have two or more spaces in a row internally.</li>
  @ <li> Must be between 3 and 100 characters in length.</li>
  @ </ul>

}

/*
** Check a wiki name.  If it is not well-formed, then issue an error
** and return true.  If it is well-formed, return false.
*/
static int check_name(const char *z){
  if( !wiki_name_is_wellformed((const unsigned char *)z) ){





    style_header("Wiki Page Name Error");
    @ The wiki name "<span class="wikiError">%h(z)</span>" is not well-formed.
    @ Rules for wiki page names:

    well_formed_wiki_name_rules();
    style_footer();
    return 1;
  }
  return 0;
}

................................................................................
    login_check_credentials();
    g.zExtra = zPageName;
    cgi_set_parameter_nocopy("name", g.zExtra);
    g.isHome = 1;
    wiki_page();
    return;
  }









  style_header("Home");
  @ <p>This is a stub home-page for the project.
  @ To fill in this page, first go to
  @ %z(href("%R/setup_config"))setup/config</a>
  @ and establish a "Project Name".  Then create a
  @ wiki page with that name.  The content of that wiki page
  @ will be displayed in place of this message.</p>

  style_footer();
}

/*
** Return true if the given pagename is the name of the sandbox
*/
static int is_sandbox(const char *zPagename){
................................................................................
  char *zTag;
  int rid = 0;
  int isSandbox;
  char *zUuid;
  Blob wiki;
  Manifest *pWiki = 0;
  const char *zPageName;



  char *zBody = mprintf("%s","<i>Empty Page</i>");


  login_check_credentials();
  if( !g.perm.RdWiki ){ login_needed(); return; }
  zPageName = P("name");
  if( zPageName==0 ){




    style_header("Wiki");
    @ <ul>

    { char *zHomePageName = db_get("project-name",0);
      if( zHomePageName ){
        @ <li> %z(href("%R/wiki?name=%t",zHomePageName))



        @      %h(zHomePageName)</a> wiki home page.</li>

      }
    }





    @ <li> %z(href("%R/timeline?y=w"))Recent changes</a> to wiki pages.</li>
    @ <li> %z(href("%R/wiki_rules"))Formatting rules</a> for wiki.</li>
    @ <li> Use the %z(href("%R/wiki?name=Sandbox"))Sandbox</a>
    @      to experiment.</li>

    if( g.perm.NewWiki ){



      @ <li>  Create a %z(href("%R/wikinew"))new wiki page</a>.</li>

      if( g.perm.Write ){



        @ <li>   Create a %z(href("%R/eventedit"))new event</a>.</li>

      }
    }







    @ <li> %z(href("%R/wcontent"))List of All Wiki Pages</a>
    @      available on this server.</li>
    if( g.perm.ModWiki ){
      @ <li> %z(href("%R/modreq"))Tend to pending moderation requests</a></li>
    }
    @ <li>
    form_begin(0, "%R/wfind");
    @  <div>Search wiki titles: <input type="text" name="title"/>
    @  &nbsp; <input type="submit" /></div></form>

    @ </li>
    @ </ul>
    style_footer();
    return;
  }
  if( check_name(zPageName) ) return;
  isSandbox = is_sandbox(zPageName);
................................................................................
                 "%R/wdiff?name=%T&a=%d", zPageName, rid);
      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
      style_submenu_element("Details", "Details",
                   "%R/info/%S", zUuid);
    }
    if( (rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki) ){
      if( db_get_boolean("wysiwyg-wiki", 0) ){




        style_submenu_element("Edit", "Edit Wiki Page",

             "%s/wikiedit?name=%T&wysiwyg=1",
             g.zTop, zPageName);
      }else{




        style_submenu_element("Edit", "Edit Wiki Page",

             "%s/wikiedit?name=%T",
             g.zTop, zPageName);
      }
    }
    if( rid && g.perm.ApndWiki && g.perm.Attach ){




      style_submenu_element("Attach", "Add An Attachment",

           "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T",
           g.zTop, zPageName, g.zTop, zPageName);
    }
    if( rid && g.perm.ApndWiki ){




      style_submenu_element("Append", "Add A Comment", "%s/wikiappend?name=%T",


           g.zTop, zPageName);
    }
    if( g.perm.Hyperlink ){
      style_submenu_element("History", "History", "%s/whistory?name=%T",






           g.zTop, zPageName);
    }
  }
  style_set_current_page("%s?name=%T", g.zPath, zPageName);
  style_header(zPageName);
  blob_init(&wiki, zBody, -1);
  wiki_convert(&wiki, 0, 0);
................................................................................
  char *zDate;
  const char *zUser;
  const char *zRemark;
  char *zId;

  zDate = db_text(0, "SELECT datetime('now')");
  zId = db_text(0, "SELECT lower(hex(randomblob(8)))");
  blob_appendf(p, "\n\n<hr><div id=\"%s\"><i>On %s UTC %h", 
    zId, zDate, g.zLogin);
  free(zDate);
  zUser = PD("u",g.zLogin);
  if( zUser[0] && fossil_strcmp(zUser,g.zLogin) ){
    blob_appendf(p, " (claiming to be %h)", zUser);
  }
  zRemark = PD("r","");
  blob_appendf(p, " added:</i><br />\n%s</div id=\"%s\">", zRemark, zId);
}

/*
** WEBPAGE: wikiappend
** URL: /wikiappend?name=PAGENAME
*/
void wikiappend_page(void){
................................................................................
*/
void wcontent_page(void){
  Stmt q;
  int showAll = P("all")!=0;

  login_check_credentials();
  if( !g.perm.RdWiki ){ login_needed(); return; }



  style_header("Available Wiki Pages");


  if( showAll ){



    style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop);

  }else{



    style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop);

  }

  @ <ul>
  wiki_prepare_page_list(&q);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);
    int size = db_column_int(&q, 1);
    if( size>0 ){
      @ <li>%z(href("%R/wiki?name=%T",zName))%h(zName)</a></li>






>
>
>
>
>
>
>
>
>







>








>
>
>
>
>



>







 







>
>
>
>
>
>
>
>
>







>







 







>
>
>

<
>




>
>
>
>


>



>
>
>

>


>
>
>
>
>




>

>
>
>

>

>
>
>

>


>
>
>
>
>
>
>









>







 







>
>
>
>
|
>
|


>
>
>
>
|
>
|




>
>
>
>
|
>




>
>
>
>
|
>
>



|
>
>
>
>
>
>







 







|




|

|
|







 







>
>
>

>
>

>
>
>

>

>
>
>

>

>







45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
...
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
...
155
156
157
158
159
160
161
162
163
164
165

166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
...
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
...
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
...
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
  return 1;
}

/*
** Output rules for well-formed wiki pages
*/
static void well_formed_wiki_name_rules(void){
#ifdef LANG_RU
  @ <ul>
    @ <li>Не должно начинаться или заканчиваться пробелом.</li>
    @ <li> Не должно содержать управляющих символов, включая табуляцию 
    @      и перевод строки.</li>
    @ <li> Не должно быть двух и более пробелов подряд.</li>
    @ <li> Длина от 3 до 100 символов.</li>
    @ </ul>
#elif LANG_EN
  @ <ul>
  @ <li> Must not begin or end with a space.</li>
  @ <li> Must not contain any control characters, including tab or
  @      newline.</li>
  @ <li> Must not have two or more spaces in a row internally.</li>
  @ <li> Must be between 3 and 100 characters in length.</li>
  @ </ul>
#endif
}

/*
** Check a wiki name.  If it is not well-formed, then issue an error
** and return true.  If it is well-formed, return false.
*/
static int check_name(const char *z){
  if( !wiki_name_is_wellformed((const unsigned char *)z) ){
#ifdef LANG_RU
    style_header("Ошибка в названии страницы");
    @ "<span class="wikiError">%h(z)</span>" не может быть названием страницы.
    @ Правила для названий страниц:
#elif LANG_EN
    style_header("Wiki Page Name Error");
    @ The wiki name "<span class="wikiError">%h(z)</span>" is not well-formed.
    @ Rules for wiki page names:
#endif
    well_formed_wiki_name_rules();
    style_footer();
    return 1;
  }
  return 0;
}

................................................................................
    login_check_credentials();
    g.zExtra = zPageName;
    cgi_set_parameter_nocopy("name", g.zExtra);
    g.isHome = 1;
    wiki_page();
    return;
  }
#ifdef LANG_RU
  style_header("Домашняя страница");
  @ <p>Это фиктивная домашняя страница проекта.
  @ Для заполнения этой страницы сначала перейдите в
  @ <a href="%s(g.zTop)/setup_config">setup/config</a>
  @ и задайте имя проекта. Затем создайте страницу с
  @ таким же названием. Содержимое созданной страницы
  @ будет отображаться вместо этого сообщения.</p>
#elif LANG_EN
  style_header("Home");
  @ <p>This is a stub home-page for the project.
  @ To fill in this page, first go to
  @ %z(href("%R/setup_config"))setup/config</a>
  @ and establish a "Project Name".  Then create a
  @ wiki page with that name.  The content of that wiki page
  @ will be displayed in place of this message.</p>
#endif
  style_footer();
}

/*
** Return true if the given pagename is the name of the sandbox
*/
static int is_sandbox(const char *zPagename){
................................................................................
  char *zTag;
  int rid = 0;
  int isSandbox;
  char *zUuid;
  Blob wiki;
  Manifest *pWiki = 0;
  const char *zPageName;
#ifdef LANG_RU
  char *zBody = mprintf("%s","<i>Пустая страница</i>");
#elif LANG_EN
  char *zBody = mprintf("%s","<i>Empty Page</i>");

#endif
  login_check_credentials();
  if( !g.perm.RdWiki ){ login_needed(); return; }
  zPageName = P("name");
  if( zPageName==0 ){
#ifdef LANG_RU
    style_header("Вики");
    @ <ul>
#elif LANG_EN
    style_header("Wiki");
    @ <ul>
#endif
    { char *zHomePageName = db_get("project-name",0);
      if( zHomePageName ){
        @ <li> %z(href("%R/wiki?name=%t",zHomePageName))
#ifdef LANG_RU
        @      %h(zHomePageName)</a> - домашняя страница.</li>
#elif LANG_EN
        @      %h(zHomePageName)</a> wiki home page.</li>
#endif
      }
    }
#ifdef LANG_RU
    @ <li> %z(href("%R/timeline?y=w"))Последние правки</a></li>
    @ <li> %z(href("%R/wiki_rules"))Правила форматирования</a></li>
    @ <li> Используйте %z(href("%R/wiki?name=Sandbox"))песочницу</a> для экспериментов.</li>
#elif LANG_EN
    @ <li> %z(href("%R/timeline?y=w"))Recent changes</a> to wiki pages.</li>
    @ <li> %z(href("%R/wiki_rules"))Formatting rules</a> for wiki.</li>
    @ <li> Use the %z(href("%R/wiki?name=Sandbox"))Sandbox</a>
    @      to experiment.</li>
#endif
    if( g.perm.NewWiki ){
#ifdef LANG_RU
      @ <li> Создать %z(href("%R/wikinew"))новую страницу</a>.</li>
#elif LANG_EN
      @ <li>  Create a %z(href("%R/wikinew"))new wiki page</a>.</li>
#endif
      if( g.perm.Write ){
#ifdef LANG_RU
        @ <li> Создать %z(href("%R/eventedit"))новое событие</a>.</li>
#elif LANG_EN
        @ <li>   Create a %z(href("%R/eventedit"))new event</a>.</li>
#endif
      }
    }
#ifdef LANG_RU
    @ <li> %z(href("%R/wcontent"))Все страницы</a>
    @      на сервере.</li>
    @ <li> <form method="get" action="%s(g.zTop)/wfind"><div>
    @     Поиск по названиям: <input type="text" name="title"/>
    @  &nbsp; <input type="submit" value = "Искать" /></div></form>
#elif LANG_EN
    @ <li> %z(href("%R/wcontent"))List of All Wiki Pages</a>
    @      available on this server.</li>
    if( g.perm.ModWiki ){
      @ <li> %z(href("%R/modreq"))Tend to pending moderation requests</a></li>
    }
    @ <li>
    form_begin(0, "%R/wfind");
    @  <div>Search wiki titles: <input type="text" name="title"/>
    @  &nbsp; <input type="submit" /></div></form>
#endif
    @ </li>
    @ </ul>
    style_footer();
    return;
  }
  if( check_name(zPageName) ) return;
  isSandbox = is_sandbox(zPageName);
................................................................................
                 "%R/wdiff?name=%T&a=%d", zPageName, rid);
      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
      style_submenu_element("Details", "Details",
                   "%R/info/%S", zUuid);
    }
    if( (rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki) ){
      if( db_get_boolean("wysiwyg-wiki", 0) ){
        style_submenu_element(
#ifdef LANG_RU
			"Правка", "Редактировать вики-страницу"
#elif LANG_EN
			"Edit", "Edit Wiki Page"
#endif
			, "%s/wikiedit?name=%T&wysiwyg=1",
             g.zTop, zPageName);
      }else{
        style_submenu_element(
#ifdef LANG_RU
			"Правка", "Редактировать вики-страницу"
#elif LANG_EN
			"Edit", "Edit Wiki Page"
#endif
			, "%s/wikiedit?name=%T",
             g.zTop, zPageName);
      }
    }
    if( rid && g.perm.ApndWiki && g.perm.Attach ){
      style_submenu_element(
#ifdef LANG_RU
        "Добавить файл", "Добавить файл к странице",
#elif LANG_EN
        "Attach", "Add An Attachment",
#endif
           "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T",
           g.zTop, zPageName, g.zTop, zPageName);
    }
    if( rid && g.perm.ApndWiki ){
      style_submenu_element(
#ifdef LANG_RU
        "Комментировать", "Добавить комментарий к странице", 
#elif LANG_EN
        "Append", "Add A Comment", 
#endif
        "%s/wikiappend?name=%T",
           g.zTop, zPageName);
    }
    if( g.perm.Hyperlink ){
      style_submenu_element(
#ifdef LANG_RU
        "История", "Показать историю правок",
#elif LANG_EN
        "History", "History",
#endif
        "%s/whistory?name=%T",
           g.zTop, zPageName);
    }
  }
  style_set_current_page("%s?name=%T", g.zPath, zPageName);
  style_header(zPageName);
  blob_init(&wiki, zBody, -1);
  wiki_convert(&wiki, 0, 0);
................................................................................
  char *zDate;
  const char *zUser;
  const char *zRemark;
  char *zId;

  zDate = db_text(0, "SELECT datetime('now')");
  zId = db_text(0, "SELECT lower(hex(randomblob(8)))");
  blob_appendf(p, "\n\n<hr><div id=\"%s\"><i>%s %h", 
    zId, zDate, g.zLogin);
  free(zDate);
  zUser = PD("u",g.zLogin);
  if( zUser[0] && fossil_strcmp(zUser,g.zLogin) ){
    blob_appendf(p, ", представившись как %h,", zUser);
  }
  zRemark = wiki_sanify(PD("r",""));
  blob_appendf(p, " добавил:</i><br />\n%s</div id=\"%s\">", zRemark, zId);
}

/*
** WEBPAGE: wikiappend
** URL: /wikiappend?name=PAGENAME
*/
void wikiappend_page(void){
................................................................................
*/
void wcontent_page(void){
  Stmt q;
  int showAll = P("all")!=0;

  login_check_credentials();
  if( !g.perm.RdWiki ){ login_needed(); return; }
#ifdef LANG_RU
  style_header("Доступные вики-стариницы");
#elif LANG_EN
  style_header("Available Wiki Pages");
#endif
  
  if( showAll ){
#ifdef LANG_RU
    style_submenu_element("Активные", "Только активные страницы", "%s/wcontent", g.zTop);
#elif LANG_EN
    style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop);
#endif
  }else{
#ifdef LANG_RU
    style_submenu_element("Все", "Все страницы", "%s/wcontent?all=1", g.zTop);
#elif LANG_EN
    style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop);
#endif
  }
  
  @ <ul>
  wiki_prepare_page_list(&q);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);
    int size = db_column_int(&q, 1);
    if( size>0 ){
      @ <li>%z(href("%R/wiki?name=%T",zName))%h(zName)</a></li>

Changes to src/wikiformat.c.

232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
...
379
380
381
382
383
384
385

386
387
388
389
390
391
392
...
630
631
632
633
634
635
636



637
638
639
640
641
642
643

644
645
646
647
648
649
650
...
699
700
701
702
703
704
705




706
707
708
709
710
711
712
...
819
820
821
822
823
824
825


826
827
828
829
830
831
832
...
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
....
1271
1272
1273
1274
1275
1276
1277

1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290




1291
1292
1293
1294
1295
1296
1297
....
1519
1520
1521
1522
1523
1524
1525



1526
1527
1528
1529
1530
1531
1532
1533

1534
1535
1536
1537
1538
1539
1540
....
1591
1592
1593
1594
1595
1596
1597
1598
1599

1600
1601
1602
1603
1604
1605
1606
1607
....
1905
1906
1907
1908
1909
1910
1911































1912
1913
1914
1915
1916
1917
1918
#define MUTYPE_TD          0x0100   /* <td> or <th> */
#define MUTYPE_SPECIAL     0x0200   /* <nowiki> or <verbatim> */
#define MUTYPE_HYPERLINK   0x0400   /* <a> */

/*
** These markup types must have an end tag.
*/
#define MUTYPE_STACK  (MUTYPE_BLOCK | MUTYPE_FONT | MUTYPE_LIST | MUTYPE_TABLE)

/*
** This markup types are allowed for "inline" text.
*/
#define MUTYPE_INLINE (MUTYPE_FONT | MUTYPE_HYPERLINK)

static const struct AllowedMarkup {
................................................................................
#define TOKEN_NEWLINE       5  /* A single "\n" */
#define TOKEN_BUL_LI        6  /*  "  *  " */
#define TOKEN_NUM_LI        7  /*  "  #  " */
#define TOKEN_ENUM          8  /*  "  \(?\d+[.)]?  " */
#define TOKEN_INDENT        9  /*  "   " */
#define TOKEN_RAW           10 /* Output exactly (used when wiki-use-html==1) */
#define TOKEN_TEXT          11 /* None of the above */


/*
** State flags.  Save the lower 16 bits for the WIKI_* flags.
*/
#define AT_NEWLINE          0x0010000  /* At start of a line */
#define AT_PARAGRAPH        0x0020000  /* At start of a paragraph */
#define ALLOW_WIKI          0x0040000  /* Allow wiki markup */
................................................................................
** characters in that token.  Write the token type into *pTokenType.
*/
static int nextWikiToken(const char *z, Renderer *p, int *pTokenType){
  int n;
  if( z[0]=='<' ){
    n = markupLength(z);
    if( n>0 ){



      *pTokenType = TOKEN_MARKUP;
      return n;
    }else{
      *pTokenType = TOKEN_CHARACTER;
      return 1;
    }
  }

  if( z[0]=='&' && (p->inVerbatim || !isElement(z)) ){
    *pTokenType = TOKEN_CHARACTER;
    return 1;
  }
  if( (p->state & ALLOW_WIKI)!=0 ){
    if( z[0]=='\n' ){
      n = paragraphBreakLength(z);
................................................................................
** characters in that token. Write the token type into *pTokenType.
*/
static int nextRawToken(const char *z, Renderer *p, int *pTokenType){
  int n;
  if( z[0]=='[' && (n = linkLength(z))>0 ){
    *pTokenType = TOKEN_LINK;
    return n;




  }
  *pTokenType = TOKEN_RAW;
  return 1 + textLength(z+1, p->state);
}

/*
** A single markup is parsed into an instance of the following
................................................................................
static void renderMarkup(Blob *pOut, ParsedMarkup *p){
  int i;
  if( p->endTag ){
    blob_appendf(pOut, "</%s>", aMarkup[p->iCode].zName);
  }else{
    blob_appendf(pOut, "<%s", aMarkup[p->iCode].zName);
    for(i=0; i<p->nAttr; i++){


      blob_appendf(pOut, " %s", aAttribute[p->aAttr[i].iACode].zName);
      if( p->aAttr[i].zValue ){
        const char *zVal = p->aAttr[i].zValue;
        if( p->aAttr[i].iACode==ATTR_SRC && zVal[0]=='/' ){
          blob_appendf(pOut, "=\"%s%s\"", g.zTop, zVal);
        }else{
          blob_appendf(pOut, "=\"%s\"", zVal);
................................................................................
** output its end tag if it is not a </div> tag.
*/
static void popStack(Renderer *p){
  if( p->nStack ){
    int iCode;
    p->nStack--;
    iCode = p->aStack[p->nStack].iCode;
    if( iCode!=MARKUP_DIV && p->pOut ){
      blob_appendf(p->pOut, "</%s>", aMarkup[iCode].zName);
    }
  }
}

/*
** Push a new markup value onto the stack.  Enlarge the stack
................................................................................
  int tokenType;
  ParsedMarkup markup;
  int n;
  int inlineOnly = (p->state & INLINE_MARKUP_ONLY)!=0;
  int wikiHtmlOnly = (p->state & (WIKI_HTMLONLY | WIKI_LINKSONLY))!=0;
  int linksOnly = (p->state & WIKI_LINKSONLY)!=0;
  char *zOrig = z;


  /* Make sure the attribute constants and names still align
  ** following changes in the attribute list. */
  assert( fossil_strcmp(aAttribute[ATTR_WIDTH].zName, "width")==0 );

  while( z[0] ){
    if( wikiHtmlOnly ){
      n = nextRawToken(z, p, &tokenType);
    }else{
      n = nextWikiToken(z, p, &tokenType);
    }
    p->state &= ~(AT_NEWLINE|AT_PARAGRAPH);
    switch( tokenType ){




      case TOKEN_PARAGRAPH: {
        if( inlineOnly ){
          /* blob_append(p->pOut, " &para; ", -1); */
          blob_append(p->pOut, " &nbsp;&nbsp; ", -1);
        }else{
          if( p->wikiList ){
            popStackToTag(p, p->wikiList);
................................................................................
        */
        if( inlineOnly && (markup.iType&MUTYPE_INLINE)==0 ){
          /* Do nothing */
        }else

        /* Generate end-tags */
        if( markup.endTag ){



          popStackToTag(p, markup.iCode);
        }else

        /* Push <div> markup onto the stack together with the id=ID attribute.
        */
        if( markup.iCode==MARKUP_DIV ){
          pushStackWithId(p, markup.iCode, markupId(&markup),
                          (p->state & ALLOW_WIKI)!=0);

        }else

        /* Enter <verbatim> processing.  With verbatim enabled, all other
        ** markup other than the corresponding end-tag with the same ID is
        ** ignored.
        */
        if( markup.iCode==MARKUP_VERBATIM ){
................................................................................
            popStackToTag(p, markup.iCode);
            startAutoParagraph(p);
            renderMarkup(p->pOut, &markup);
            pushStack(p, markup.iCode);
          }
        }else
        {
          if( markup.iType==MUTYPE_FONT ){
            startAutoParagraph(p);

          }else if( markup.iType==MUTYPE_BLOCK || markup.iType==MUTYPE_LIST ){
            p->wantAutoParagraph = 0;
          }
          if(   markup.iCode==MARKUP_HR
             || markup.iCode==MARKUP_H1
             || markup.iCode==MARKUP_H2
             || markup.iCode==MARKUP_H3
             || markup.iCode==MARKUP_H4
................................................................................
        break;
      }
    }
    z += n;
  }
  free(renderer.aStack);
}
































/*
** Get the next HTML token.
**
** z points to the start of a token.  Return the number of
** characters in that token.
*/






|







 







>







 







>
>
>
|

|




>







 







>
>
>
>







 







>
>







 







|







 







>






|






>
>
>
>







 







>
>
>
|







>







 







|

>
|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
...
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
...
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
...
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
...
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
...
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
....
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
....
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
....
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
....
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
#define MUTYPE_TD          0x0100   /* <td> or <th> */
#define MUTYPE_SPECIAL     0x0200   /* <nowiki> or <verbatim> */
#define MUTYPE_HYPERLINK   0x0400   /* <a> */

/*
** These markup types must have an end tag.
*/
#define MUTYPE_STACK  (MUTYPE_BLOCK | MUTYPE_LIST | MUTYPE_TABLE)

/*
** This markup types are allowed for "inline" text.
*/
#define MUTYPE_INLINE (MUTYPE_FONT | MUTYPE_HYPERLINK)

static const struct AllowedMarkup {
................................................................................
#define TOKEN_NEWLINE       5  /* A single "\n" */
#define TOKEN_BUL_LI        6  /*  "  *  " */
#define TOKEN_NUM_LI        7  /*  "  #  " */
#define TOKEN_ENUM          8  /*  "  \(?\d+[.)]?  " */
#define TOKEN_INDENT        9  /*  "   " */
#define TOKEN_RAW           10 /* Output exactly (used when wiki-use-html==1) */
#define TOKEN_TEXT          11 /* None of the above */
#define TOKEN_SWHTML        12 /* switch use html */

/*
** State flags.  Save the lower 16 bits for the WIKI_* flags.
*/
#define AT_NEWLINE          0x0010000  /* At start of a line */
#define AT_PARAGRAPH        0x0020000  /* At start of a paragraph */
#define ALLOW_WIKI          0x0040000  /* Allow wiki markup */
................................................................................
** characters in that token.  Write the token type into *pTokenType.
*/
static int nextWikiToken(const char *z, Renderer *p, int *pTokenType){
  int n;
  if( z[0]=='<' ){
    n = markupLength(z);
    if( n>0 ){
      if(n == 3 && z[1] == 'x')
        *pTokenType = TOKEN_SWHTML;
      else
        *pTokenType = TOKEN_MARKUP;
      return n;
    } else {
      *pTokenType = TOKEN_CHARACTER;
      return 1;
    }
  }
  
  if( z[0]=='&' && (p->inVerbatim || !isElement(z)) ){
    *pTokenType = TOKEN_CHARACTER;
    return 1;
  }
  if( (p->state & ALLOW_WIKI)!=0 ){
    if( z[0]=='\n' ){
      n = paragraphBreakLength(z);
................................................................................
** characters in that token. Write the token type into *pTokenType.
*/
static int nextRawToken(const char *z, Renderer *p, int *pTokenType){
  int n;
  if( z[0]=='[' && (n = linkLength(z))>0 ){
    *pTokenType = TOKEN_LINK;
    return n;
  }
  if( z[0]=='<' && z[1]=='x' && z[2] == '>') {
    *pTokenType = TOKEN_SWHTML;
    return 3;
  }
  *pTokenType = TOKEN_RAW;
  return 1 + textLength(z+1, p->state);
}

/*
** A single markup is parsed into an instance of the following
................................................................................
static void renderMarkup(Blob *pOut, ParsedMarkup *p){
  int i;
  if( p->endTag ){
    blob_appendf(pOut, "</%s>", aMarkup[p->iCode].zName);
  }else{
    blob_appendf(pOut, "<%s", aMarkup[p->iCode].zName);
    for(i=0; i<p->nAttr; i++){
	  if(p->aAttr[i].iACode==ATTR_HREF && 0 == fossil_strnicmp(p->aAttr[i].zValue, "JavaScript:", sizeof("JavaScript:") - 1))
		  continue;
      blob_appendf(pOut, " %s", aAttribute[p->aAttr[i].iACode].zName);
      if( p->aAttr[i].zValue ){
        const char *zVal = p->aAttr[i].zValue;
        if( p->aAttr[i].iACode==ATTR_SRC && zVal[0]=='/' ){
          blob_appendf(pOut, "=\"%s%s\"", g.zTop, zVal);
        }else{
          blob_appendf(pOut, "=\"%s\"", zVal);
................................................................................
** output its end tag if it is not a </div> tag.
*/
static void popStack(Renderer *p){
  if( p->nStack ){
    int iCode;
    p->nStack--;
    iCode = p->aStack[p->nStack].iCode;
    if( /*iCode!=MARKUP_DIV &&*/ p->pOut ){
      blob_appendf(p->pOut, "</%s>", aMarkup[iCode].zName);
    }
  }
}

/*
** Push a new markup value onto the stack.  Enlarge the stack
................................................................................
  int tokenType;
  ParsedMarkup markup;
  int n;
  int inlineOnly = (p->state & INLINE_MARKUP_ONLY)!=0;
  int wikiHtmlOnly = (p->state & (WIKI_HTMLONLY | WIKI_LINKSONLY))!=0;
  int linksOnly = (p->state & WIKI_LINKSONLY)!=0;
  char *zOrig = z;
  int forceUseHtml = 0;

  /* Make sure the attribute constants and names still align
  ** following changes in the attribute list. */
  assert( fossil_strcmp(aAttribute[ATTR_WIDTH].zName, "width")==0 );

  while( z[0] ){
    if( wikiHtmlOnly || forceUseHtml){
      n = nextRawToken(z, p, &tokenType);
    }else{
      n = nextWikiToken(z, p, &tokenType);
    }
    p->state &= ~(AT_NEWLINE|AT_PARAGRAPH);
    switch( tokenType ){
	    case TOKEN_SWHTML: {
		    forceUseHtml = !forceUseHtml;
        break;
	    }
      case TOKEN_PARAGRAPH: {
        if( inlineOnly ){
          /* blob_append(p->pOut, " &para; ", -1); */
          blob_append(p->pOut, " &nbsp;&nbsp; ", -1);
        }else{
          if( p->wikiList ){
            popStackToTag(p, p->wikiList);
................................................................................
        */
        if( inlineOnly && (markup.iType&MUTYPE_INLINE)==0 ){
          /* Do nothing */
        }else

        /* Generate end-tags */
        if( markup.endTag ){
          if(markup.iType==MUTYPE_FONT)
            renderMarkup(p->pOut, &markup);
          else
            popStackToTag(p, markup.iCode);
        }else

        /* Push <div> markup onto the stack together with the id=ID attribute.
        */
        if( markup.iCode==MARKUP_DIV ){
          pushStackWithId(p, markup.iCode, markupId(&markup),
                          (p->state & ALLOW_WIKI)!=0);
          renderMarkup(p->pOut, &markup);
        }else

        /* Enter <verbatim> processing.  With verbatim enabled, all other
        ** markup other than the corresponding end-tag with the same ID is
        ** ignored.
        */
        if( markup.iCode==MARKUP_VERBATIM ){
................................................................................
            popStackToTag(p, markup.iCode);
            startAutoParagraph(p);
            renderMarkup(p->pOut, &markup);
            pushStack(p, markup.iCode);
          }
        }else
        {
          /*if( markup.iType==MUTYPE_FONT ){
            startAutoParagraph(p);
          }else*/
          if( markup.iType==MUTYPE_BLOCK || markup.iType==MUTYPE_LIST ){
            p->wantAutoParagraph = 0;
          }
          if(   markup.iCode==MARKUP_HR
             || markup.iCode==MARKUP_H1
             || markup.iCode==MARKUP_H2
             || markup.iCode==MARKUP_H3
             || markup.iCode==MARKUP_H4
................................................................................
        break;
      }
    }
    z += n;
  }
  free(renderer.aStack);
}
/*
** Test text before it add to wiki. Allow use of <x> only for developers or wikieditors.
*/
const char* wiki_sanify(const char *pSrc){
  static Blob tempStore = BLOB_INITIALIZER;
  int hasReplaces = 0;
  const char* z = pSrc;
  Renderer r;
  
  blob_reset(&tempStore);

  if(g.perm.Admin || g.perm.NewWiki || g.perm.Write || g.perm.WrWiki || g.perm.Setup)
    return pSrc;
  r.state = 1;
  while(*z)
  {
    int tokenType;
    int n = nextRawToken(z, &r, &tokenType);
    if(tokenType == TOKEN_SWHTML) {
      if(!hasReplaces) {
        blob_append(&tempStore, pSrc, z - pSrc);
        hasReplaces = 1;
      }
      blob_append(&tempStore, "&lt;x&gt;", 9);
    }
    else if(hasReplaces)
      blob_append(&tempStore, z, n);
    z+=n;
  }
  return hasReplaces ? blob_str(&tempStore) : pSrc;
}

/*
** Get the next HTML token.
**
** z points to the start of a token.  Return the number of
** characters in that token.
*/