Fossil SCM

Merge-in trunk

george 2020-11-17 18:46 wiki-history merge
Commit f4a75745f9d18ca50b7a2e0edfcf0d54d9bb4eb68eb67cf2cf11ba1e4467fff2
--- skins/xekri/details.txt
+++ skins/xekri/details.txt
@@ -1,4 +1,4 @@
11
timeline-arrowheads: 1
22
timeline-circle-nodes: 0
33
timeline-color-graph-lines: 1
4
-white-foreground: 0
4
+white-foreground: 1
55
--- skins/xekri/details.txt
+++ skins/xekri/details.txt
@@ -1,4 +1,4 @@
1 timeline-arrowheads: 1
2 timeline-circle-nodes: 0
3 timeline-color-graph-lines: 1
4 white-foreground: 0
5
--- skins/xekri/details.txt
+++ skins/xekri/details.txt
@@ -1,4 +1,4 @@
1 timeline-arrowheads: 1
2 timeline-circle-nodes: 0
3 timeline-color-graph-lines: 1
4 white-foreground: 1
5
+22 -3
--- src/export.c
+++ src/export.c
@@ -1088,10 +1088,11 @@
10881088
Blob sql; /* String of SQL for part of the query */
10891089
Blob comment; /* The comment text for the check-in */
10901090
int nErr = 0; /* Number of errors */
10911091
int bPhantomOk; /* True if phantom files should be ignored */
10921092
char buf[24];
1093
+ char *zEmail; /* Contact info for Git committer field */
10931094
10941095
pMan = manifest_get(rid, CFTYPE_MANIFEST, 0);
10951096
if( pMan==0 ){
10961097
/* Must be a phantom. Return without doing anything, and in particular
10971098
** without creating a mark for this check-in. */
@@ -1164,13 +1165,31 @@
11641165
fprintf(xCmd, "mark %s\n", zMark);
11651166
fossil_free(zMark);
11661167
sqlite3_snprintf(sizeof(buf), buf, "%lld",
11671168
(sqlite3_int64)((pMan->rDate-2440587.5)*86400.0)
11681169
);
1169
- fprintf(xCmd, "committer %s <%[email protected]> %s +0000\n",
1170
- pMan->zUser, pMan->zUser, buf
1171
- );
1170
+ /*
1171
+ ** Check for 'fx_' table from previous Git import, otherwise take contact info
1172
+ ** from user table for <emailaddr> in committer field. If no emailaddr, check
1173
+ ** if username is in email form, otherwise use generic '[email protected]'.
1174
+ */
1175
+ if (db_table_exists("repository", "fx_git")) {
1176
+ zEmail = db_text(0, "SELECT email FROM fx_git WHERE user=%Q", pMan->zUser);
1177
+ } else {
1178
+ zEmail = db_text(0, "SELECT info FROM user WHERE login=%Q", pMan->zUser);
1179
+ }
1180
+ /* Some repo 'info' fields return an empty string hence the second check */
1181
+ if (zEmail == NULL || zEmail[0] == '\0') {
1182
+ /* If username is in emailaddr form, don't append '@noemail.net' */
1183
+ if (strchr(pMan->zUser, '@') == NULL) {
1184
+ zEmail = mprintf("%[email protected]", pMan->zUser);
1185
+ } else {
1186
+ zEmail = fossil_strdup(pMan->zUser);
1187
+ }
1188
+ }
1189
+ fprintf(xCmd, "committer %s <%s> %s +0000\n", pMan->zUser, zEmail, buf);
1190
+ fossil_free(zEmail);
11721191
blob_init(&comment, pMan->zComment, -1);
11731192
if( blob_size(&comment)==0 ){
11741193
blob_append(&comment, "(no comment)", -1);
11751194
}
11761195
blob_appendf(&comment, "\n\nFossilOrigin-Name: %s", zUuid);
11771196
--- src/export.c
+++ src/export.c
@@ -1088,10 +1088,11 @@
1088 Blob sql; /* String of SQL for part of the query */
1089 Blob comment; /* The comment text for the check-in */
1090 int nErr = 0; /* Number of errors */
1091 int bPhantomOk; /* True if phantom files should be ignored */
1092 char buf[24];
 
1093
1094 pMan = manifest_get(rid, CFTYPE_MANIFEST, 0);
1095 if( pMan==0 ){
1096 /* Must be a phantom. Return without doing anything, and in particular
1097 ** without creating a mark for this check-in. */
@@ -1164,13 +1165,31 @@
1164 fprintf(xCmd, "mark %s\n", zMark);
1165 fossil_free(zMark);
1166 sqlite3_snprintf(sizeof(buf), buf, "%lld",
1167 (sqlite3_int64)((pMan->rDate-2440587.5)*86400.0)
1168 );
1169 fprintf(xCmd, "committer %s <%[email protected]> %s +0000\n",
1170 pMan->zUser, pMan->zUser, buf
1171 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1172 blob_init(&comment, pMan->zComment, -1);
1173 if( blob_size(&comment)==0 ){
1174 blob_append(&comment, "(no comment)", -1);
1175 }
1176 blob_appendf(&comment, "\n\nFossilOrigin-Name: %s", zUuid);
1177
--- src/export.c
+++ src/export.c
@@ -1088,10 +1088,11 @@
1088 Blob sql; /* String of SQL for part of the query */
1089 Blob comment; /* The comment text for the check-in */
1090 int nErr = 0; /* Number of errors */
1091 int bPhantomOk; /* True if phantom files should be ignored */
1092 char buf[24];
1093 char *zEmail; /* Contact info for Git committer field */
1094
1095 pMan = manifest_get(rid, CFTYPE_MANIFEST, 0);
1096 if( pMan==0 ){
1097 /* Must be a phantom. Return without doing anything, and in particular
1098 ** without creating a mark for this check-in. */
@@ -1164,13 +1165,31 @@
1165 fprintf(xCmd, "mark %s\n", zMark);
1166 fossil_free(zMark);
1167 sqlite3_snprintf(sizeof(buf), buf, "%lld",
1168 (sqlite3_int64)((pMan->rDate-2440587.5)*86400.0)
1169 );
1170 /*
1171 ** Check for 'fx_' table from previous Git import, otherwise take contact info
1172 ** from user table for <emailaddr> in committer field. If no emailaddr, check
1173 ** if username is in email form, otherwise use generic '[email protected]'.
1174 */
1175 if (db_table_exists("repository", "fx_git")) {
1176 zEmail = db_text(0, "SELECT email FROM fx_git WHERE user=%Q", pMan->zUser);
1177 } else {
1178 zEmail = db_text(0, "SELECT info FROM user WHERE login=%Q", pMan->zUser);
1179 }
1180 /* Some repo 'info' fields return an empty string hence the second check */
1181 if (zEmail == NULL || zEmail[0] == '\0') {
1182 /* If username is in emailaddr form, don't append '@noemail.net' */
1183 if (strchr(pMan->zUser, '@') == NULL) {
1184 zEmail = mprintf("%[email protected]", pMan->zUser);
1185 } else {
1186 zEmail = fossil_strdup(pMan->zUser);
1187 }
1188 }
1189 fprintf(xCmd, "committer %s <%s> %s +0000\n", pMan->zUser, zEmail, buf);
1190 fossil_free(zEmail);
1191 blob_init(&comment, pMan->zComment, -1);
1192 if( blob_size(&comment)==0 ){
1193 blob_append(&comment, "(no comment)", -1);
1194 }
1195 blob_appendf(&comment, "\n\nFossilOrigin-Name: %s", zUuid);
1196
+63 -5
--- src/import.c
+++ src/import.c
@@ -287,11 +287,14 @@
287287
import_prior_files();
288288
qsort(gg.aFile, gg.nFile, sizeof(gg.aFile[0]), mfile_cmp);
289289
blob_zero(&record);
290290
blob_appendf(&record, "C %F\n", gg.zComment);
291291
blob_appendf(&record, "D %s\n", gg.zDate);
292
- if( !g.fQuiet ) fossil_print("%.10s\r", gg.zDate);
292
+ if( !g.fQuiet ){
293
+ fossil_print("%.10s\r", gg.zDate);
294
+ fflush(stdout);
295
+ }
293296
for(i=0; i<gg.nFile; i++){
294297
const char *zUuid = gg.aFile[i].zUuid;
295298
if( zUuid==0 ) continue;
296299
blob_appendf(&record, "F %F %s", gg.aFile[i].zName, zUuid);
297300
if( gg.aFile[i].isExe ){
@@ -540,10 +543,15 @@
540543
541544
542545
static struct{
543546
const char *zMasterName; /* Name of master branch */
544547
int authorFlag; /* Use author as checkin committer */
548
+ int nGitAttr; /* Number of Git --attribute entries */
549
+ struct { /* Git --attribute details */
550
+ char *zUser;
551
+ char *zEmail;
552
+ } *gitUserInfo;
545553
} ggit;
546554
547555
/*
548556
** Read the git-fast-import format from pIn and insert the corresponding
549557
** content into the database.
@@ -664,20 +672,28 @@
664672
sqlite3_int64 secSince1970;
665673
z = strchr(zLine, ' ');
666674
while( fossil_isspace(*z) ) z++;
667675
if( (zTo=strchr(z, '>'))==NULL ) goto malformed_line;
668676
*(++zTo) = '\0';
669
- /* Lookup user by contact info. */
677
+ /*
678
+ ** If --attribute requested, lookup user in fx_ table by email address,
679
+ ** otherwise lookup Git {author,committer} contact info in user table. If
680
+ ** no matches, use email address as username for check-in attribution.
681
+ */
670682
fossil_free(gg.zUser);
671683
gg.zUser = db_text(0, "SELECT login FROM user WHERE info=%Q", z);
672684
if( gg.zUser==NULL ){
673685
/* If there is no user with this contact info,
674686
* then use the email address as the username. */
675687
if ( (z=strchr(z, '<'))==NULL ) goto malformed_line;
676688
z++;
677689
*(zTo-1) = '\0';
678690
gg.zUser = fossil_strdup(z);
691
+ }
692
+ if (ggit.nGitAttr > 0 || db_table_exists("repository", "fx_git")) {
693
+ gg.zUser = db_text(gg.zUser,
694
+ "SELECT user FROM fx_git WHERE email=%Q", z);
679695
}
680696
secSince1970 = 0;
681697
for(zTo++; fossil_isdigit(*zTo); zTo++){
682698
secSince1970 = secSince1970*10 + *zTo - '0';
683699
}
@@ -712,11 +728,11 @@
712728
pFile = import_find_file(zName, &i, gg.nFile);
713729
if( pFile==0 ){
714730
pFile = import_add_file();
715731
pFile->zName = fossil_strdup(zName);
716732
}
717
- pFile->isExe = (fossil_strcmp(zPerm, "100755")==0);
733
+ pFile->isExe = (sqlite3_strglob("*755",zPerm)==0);
718734
pFile->isLink = (fossil_strcmp(zPerm, "120000")==0);
719735
fossil_free(pFile->zUuid);
720736
if( strcmp(zUuid,"inline")==0 ){
721737
pFile->zUuid = 0;
722738
gg.pInlineFile = pFile;
@@ -752,11 +768,11 @@
752768
while( (pFile = import_find_file(zFrom, &i, mx))!=0 ){
753769
if( pFile->isFrom==0 ) continue;
754770
pNew = import_add_file();
755771
pFile = &gg.aFile[i-1];
756772
if( strlen(pFile->zName)>nFrom ){
757
- pNew->zName = mprintf("%s%s", zTo, pFile->zName[nFrom]);
773
+ pNew->zName = mprintf("%s%s", zTo, pFile->zName+nFrom);
758774
}else{
759775
pNew->zName = fossil_strdup(zTo);
760776
}
761777
pNew->isExe = pFile->isExe;
762778
pNew->isLink = pFile->isLink;
@@ -775,11 +791,11 @@
775791
while( (pFile = import_find_file(zFrom, &i, gg.nFile))!=0 ){
776792
if( pFile->isFrom==0 ) continue;
777793
pNew = import_add_file();
778794
pFile = &gg.aFile[i-1];
779795
if( strlen(pFile->zName)>nFrom ){
780
- pNew->zName = mprintf("%s%s", zTo, pFile->zName[nFrom]);
796
+ pNew->zName = mprintf("%s%s", zTo, pFile->zName+nFrom);
781797
}else{
782798
pNew->zName = fossil_strdup(zTo);
783799
}
784800
pNew->zPrior = pFile->zName;
785801
pNew->isExe = pFile->isExe;
@@ -803,10 +819,14 @@
803819
** user-readable branch name. */
804820
z = &zLine[21];
805821
next_token(&z);
806822
fossil_free(gg.zBranch);
807823
gg.zBranch = fossil_strdup(next_token(&z));
824
+ }else
825
+ if( strncmp(zLine, "property rebase-of ", 19)==0 ){
826
+ /* Breezy uses this property to record that a branch
827
+ ** was rebased. Silently ignore it. */
808828
}else
809829
{
810830
goto malformed_line;
811831
}
812832
}
@@ -1648,10 +1668,12 @@
16481668
** Options:
16491669
** --import-marks FILE Restore marks table from FILE
16501670
** --export-marks FILE Save marks table to FILE
16511671
** --rename-master NAME Renames the master branch to NAME
16521672
** --use-author Uses author as the committer
1673
+** --attribute "EMAIL USER" Attribute commits to USER
1674
+** instead of Git committer EMAIL address
16531675
**
16541676
** --svn Import from the svnadmin-dump file format. The default
16551677
** behaviour (unless overridden by --flat) is to treat 3
16561678
** folders in the SVN root as special, following the
16571679
** common layout of SVN repositories. These are (by
@@ -1690,10 +1712,15 @@
16901712
**
16911713
** --ignore-tree is useful for importing Subversion repositories which
16921714
** move branches to subdirectories of "branches/deleted" instead of
16931715
** deleting them. It can be supplied multiple times if necessary.
16941716
**
1717
+** The --attribute option takes a quoted string argument comprised of a
1718
+** Git committer email and the username to be attributed to corresponding
1719
+** check-ins in the Fossil repository. This option can be repeated. For
1720
+** example, --attribute "[email protected] drh" --attribute "[email protected] X"
1721
+**
16951722
** See also: export
16961723
*/
16971724
void import_cmd(void){
16981725
char *zPassword;
16991726
FILE *pIn;
@@ -1776,10 +1803,23 @@
17761803
markfile_out = find_option("export-marks", 0, 1);
17771804
if( !(ggit.zMasterName = find_option("rename-master", 0, 1)) ){
17781805
ggit.zMasterName = "master";
17791806
}
17801807
ggit.authorFlag = find_option("use-author", 0, 0)!=0;
1808
+ /*
1809
+ ** Extract --attribute 'emailaddr username' args that will populate
1810
+ ** new 'fx_' table to later match username for check-in attribution.
1811
+ */
1812
+ const char *zGitUser = find_option("attribute", 0, 1);
1813
+ while( zGitUser != 0 ){
1814
+ ggit.gitUserInfo = fossil_realloc(ggit.gitUserInfo, ++ggit.nGitAttr
1815
+ * sizeof(ggit.gitUserInfo[0]));
1816
+ char *currGitUser = fossil_strdup(zGitUser);
1817
+ ggit.gitUserInfo[ggit.nGitAttr-1].zEmail = next_token(&currGitUser);
1818
+ ggit.gitUserInfo[ggit.nGitAttr-1].zUser = rest_of_line(&currGitUser);
1819
+ zGitUser = find_option("attribute", 0, 1);
1820
+ }
17811821
}
17821822
verify_all_options();
17831823
17841824
if( g.argc!=3 && g.argc!=4 ){
17851825
usage("--git|--svn ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?");
@@ -1901,10 +1941,28 @@
19011941
}
19021942
fclose(f);
19031943
}
19041944
19051945
manifest_crosslink_begin();
1946
+ /*
1947
+ ** The following 'fx_' table is used to hold information needed for
1948
+ ** importing and exporting to attribute Fossil check-ins or Git commits
1949
+ ** to either a desired username or full contact information string.
1950
+ */
1951
+ if(ggit.nGitAttr > 0) {
1952
+ db_unprotect(PROTECT_ALL);
1953
+ db_multi_exec(
1954
+ "CREATE TABLE fx_git(user TEXT, email TEXT UNIQUE);"
1955
+ );
1956
+ for( int idx = 0; idx < ggit.nGitAttr; ++idx ){
1957
+ db_multi_exec(
1958
+ "INSERT OR IGNORE INTO fx_git(user, email) VALUES(%Q, %Q)",
1959
+ ggit.gitUserInfo[idx].zUser, ggit.gitUserInfo[idx].zEmail
1960
+ );
1961
+ }
1962
+ db_protect_pop();
1963
+ }
19061964
git_fast_import(pIn);
19071965
db_prepare(&q, "SELECT tcontent FROM xtag");
19081966
while( db_step(&q)==SQLITE_ROW ){
19091967
Blob record;
19101968
db_ephemeral_blob(&q, 0, &record);
19111969
--- src/import.c
+++ src/import.c
@@ -287,11 +287,14 @@
287 import_prior_files();
288 qsort(gg.aFile, gg.nFile, sizeof(gg.aFile[0]), mfile_cmp);
289 blob_zero(&record);
290 blob_appendf(&record, "C %F\n", gg.zComment);
291 blob_appendf(&record, "D %s\n", gg.zDate);
292 if( !g.fQuiet ) fossil_print("%.10s\r", gg.zDate);
 
 
 
293 for(i=0; i<gg.nFile; i++){
294 const char *zUuid = gg.aFile[i].zUuid;
295 if( zUuid==0 ) continue;
296 blob_appendf(&record, "F %F %s", gg.aFile[i].zName, zUuid);
297 if( gg.aFile[i].isExe ){
@@ -540,10 +543,15 @@
540
541
542 static struct{
543 const char *zMasterName; /* Name of master branch */
544 int authorFlag; /* Use author as checkin committer */
 
 
 
 
 
545 } ggit;
546
547 /*
548 ** Read the git-fast-import format from pIn and insert the corresponding
549 ** content into the database.
@@ -664,20 +672,28 @@
664 sqlite3_int64 secSince1970;
665 z = strchr(zLine, ' ');
666 while( fossil_isspace(*z) ) z++;
667 if( (zTo=strchr(z, '>'))==NULL ) goto malformed_line;
668 *(++zTo) = '\0';
669 /* Lookup user by contact info. */
 
 
 
 
670 fossil_free(gg.zUser);
671 gg.zUser = db_text(0, "SELECT login FROM user WHERE info=%Q", z);
672 if( gg.zUser==NULL ){
673 /* If there is no user with this contact info,
674 * then use the email address as the username. */
675 if ( (z=strchr(z, '<'))==NULL ) goto malformed_line;
676 z++;
677 *(zTo-1) = '\0';
678 gg.zUser = fossil_strdup(z);
 
 
 
 
679 }
680 secSince1970 = 0;
681 for(zTo++; fossil_isdigit(*zTo); zTo++){
682 secSince1970 = secSince1970*10 + *zTo - '0';
683 }
@@ -712,11 +728,11 @@
712 pFile = import_find_file(zName, &i, gg.nFile);
713 if( pFile==0 ){
714 pFile = import_add_file();
715 pFile->zName = fossil_strdup(zName);
716 }
717 pFile->isExe = (fossil_strcmp(zPerm, "100755")==0);
718 pFile->isLink = (fossil_strcmp(zPerm, "120000")==0);
719 fossil_free(pFile->zUuid);
720 if( strcmp(zUuid,"inline")==0 ){
721 pFile->zUuid = 0;
722 gg.pInlineFile = pFile;
@@ -752,11 +768,11 @@
752 while( (pFile = import_find_file(zFrom, &i, mx))!=0 ){
753 if( pFile->isFrom==0 ) continue;
754 pNew = import_add_file();
755 pFile = &gg.aFile[i-1];
756 if( strlen(pFile->zName)>nFrom ){
757 pNew->zName = mprintf("%s%s", zTo, pFile->zName[nFrom]);
758 }else{
759 pNew->zName = fossil_strdup(zTo);
760 }
761 pNew->isExe = pFile->isExe;
762 pNew->isLink = pFile->isLink;
@@ -775,11 +791,11 @@
775 while( (pFile = import_find_file(zFrom, &i, gg.nFile))!=0 ){
776 if( pFile->isFrom==0 ) continue;
777 pNew = import_add_file();
778 pFile = &gg.aFile[i-1];
779 if( strlen(pFile->zName)>nFrom ){
780 pNew->zName = mprintf("%s%s", zTo, pFile->zName[nFrom]);
781 }else{
782 pNew->zName = fossil_strdup(zTo);
783 }
784 pNew->zPrior = pFile->zName;
785 pNew->isExe = pFile->isExe;
@@ -803,10 +819,14 @@
803 ** user-readable branch name. */
804 z = &zLine[21];
805 next_token(&z);
806 fossil_free(gg.zBranch);
807 gg.zBranch = fossil_strdup(next_token(&z));
 
 
 
 
808 }else
809 {
810 goto malformed_line;
811 }
812 }
@@ -1648,10 +1668,12 @@
1648 ** Options:
1649 ** --import-marks FILE Restore marks table from FILE
1650 ** --export-marks FILE Save marks table to FILE
1651 ** --rename-master NAME Renames the master branch to NAME
1652 ** --use-author Uses author as the committer
 
 
1653 **
1654 ** --svn Import from the svnadmin-dump file format. The default
1655 ** behaviour (unless overridden by --flat) is to treat 3
1656 ** folders in the SVN root as special, following the
1657 ** common layout of SVN repositories. These are (by
@@ -1690,10 +1712,15 @@
1690 **
1691 ** --ignore-tree is useful for importing Subversion repositories which
1692 ** move branches to subdirectories of "branches/deleted" instead of
1693 ** deleting them. It can be supplied multiple times if necessary.
1694 **
 
 
 
 
 
1695 ** See also: export
1696 */
1697 void import_cmd(void){
1698 char *zPassword;
1699 FILE *pIn;
@@ -1776,10 +1803,23 @@
1776 markfile_out = find_option("export-marks", 0, 1);
1777 if( !(ggit.zMasterName = find_option("rename-master", 0, 1)) ){
1778 ggit.zMasterName = "master";
1779 }
1780 ggit.authorFlag = find_option("use-author", 0, 0)!=0;
 
 
 
 
 
 
 
 
 
 
 
 
 
1781 }
1782 verify_all_options();
1783
1784 if( g.argc!=3 && g.argc!=4 ){
1785 usage("--git|--svn ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?");
@@ -1901,10 +1941,28 @@
1901 }
1902 fclose(f);
1903 }
1904
1905 manifest_crosslink_begin();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1906 git_fast_import(pIn);
1907 db_prepare(&q, "SELECT tcontent FROM xtag");
1908 while( db_step(&q)==SQLITE_ROW ){
1909 Blob record;
1910 db_ephemeral_blob(&q, 0, &record);
1911
--- src/import.c
+++ src/import.c
@@ -287,11 +287,14 @@
287 import_prior_files();
288 qsort(gg.aFile, gg.nFile, sizeof(gg.aFile[0]), mfile_cmp);
289 blob_zero(&record);
290 blob_appendf(&record, "C %F\n", gg.zComment);
291 blob_appendf(&record, "D %s\n", gg.zDate);
292 if( !g.fQuiet ){
293 fossil_print("%.10s\r", gg.zDate);
294 fflush(stdout);
295 }
296 for(i=0; i<gg.nFile; i++){
297 const char *zUuid = gg.aFile[i].zUuid;
298 if( zUuid==0 ) continue;
299 blob_appendf(&record, "F %F %s", gg.aFile[i].zName, zUuid);
300 if( gg.aFile[i].isExe ){
@@ -540,10 +543,15 @@
543
544
545 static struct{
546 const char *zMasterName; /* Name of master branch */
547 int authorFlag; /* Use author as checkin committer */
548 int nGitAttr; /* Number of Git --attribute entries */
549 struct { /* Git --attribute details */
550 char *zUser;
551 char *zEmail;
552 } *gitUserInfo;
553 } ggit;
554
555 /*
556 ** Read the git-fast-import format from pIn and insert the corresponding
557 ** content into the database.
@@ -664,20 +672,28 @@
672 sqlite3_int64 secSince1970;
673 z = strchr(zLine, ' ');
674 while( fossil_isspace(*z) ) z++;
675 if( (zTo=strchr(z, '>'))==NULL ) goto malformed_line;
676 *(++zTo) = '\0';
677 /*
678 ** If --attribute requested, lookup user in fx_ table by email address,
679 ** otherwise lookup Git {author,committer} contact info in user table. If
680 ** no matches, use email address as username for check-in attribution.
681 */
682 fossil_free(gg.zUser);
683 gg.zUser = db_text(0, "SELECT login FROM user WHERE info=%Q", z);
684 if( gg.zUser==NULL ){
685 /* If there is no user with this contact info,
686 * then use the email address as the username. */
687 if ( (z=strchr(z, '<'))==NULL ) goto malformed_line;
688 z++;
689 *(zTo-1) = '\0';
690 gg.zUser = fossil_strdup(z);
691 }
692 if (ggit.nGitAttr > 0 || db_table_exists("repository", "fx_git")) {
693 gg.zUser = db_text(gg.zUser,
694 "SELECT user FROM fx_git WHERE email=%Q", z);
695 }
696 secSince1970 = 0;
697 for(zTo++; fossil_isdigit(*zTo); zTo++){
698 secSince1970 = secSince1970*10 + *zTo - '0';
699 }
@@ -712,11 +728,11 @@
728 pFile = import_find_file(zName, &i, gg.nFile);
729 if( pFile==0 ){
730 pFile = import_add_file();
731 pFile->zName = fossil_strdup(zName);
732 }
733 pFile->isExe = (sqlite3_strglob("*755",zPerm)==0);
734 pFile->isLink = (fossil_strcmp(zPerm, "120000")==0);
735 fossil_free(pFile->zUuid);
736 if( strcmp(zUuid,"inline")==0 ){
737 pFile->zUuid = 0;
738 gg.pInlineFile = pFile;
@@ -752,11 +768,11 @@
768 while( (pFile = import_find_file(zFrom, &i, mx))!=0 ){
769 if( pFile->isFrom==0 ) continue;
770 pNew = import_add_file();
771 pFile = &gg.aFile[i-1];
772 if( strlen(pFile->zName)>nFrom ){
773 pNew->zName = mprintf("%s%s", zTo, pFile->zName+nFrom);
774 }else{
775 pNew->zName = fossil_strdup(zTo);
776 }
777 pNew->isExe = pFile->isExe;
778 pNew->isLink = pFile->isLink;
@@ -775,11 +791,11 @@
791 while( (pFile = import_find_file(zFrom, &i, gg.nFile))!=0 ){
792 if( pFile->isFrom==0 ) continue;
793 pNew = import_add_file();
794 pFile = &gg.aFile[i-1];
795 if( strlen(pFile->zName)>nFrom ){
796 pNew->zName = mprintf("%s%s", zTo, pFile->zName+nFrom);
797 }else{
798 pNew->zName = fossil_strdup(zTo);
799 }
800 pNew->zPrior = pFile->zName;
801 pNew->isExe = pFile->isExe;
@@ -803,10 +819,14 @@
819 ** user-readable branch name. */
820 z = &zLine[21];
821 next_token(&z);
822 fossil_free(gg.zBranch);
823 gg.zBranch = fossil_strdup(next_token(&z));
824 }else
825 if( strncmp(zLine, "property rebase-of ", 19)==0 ){
826 /* Breezy uses this property to record that a branch
827 ** was rebased. Silently ignore it. */
828 }else
829 {
830 goto malformed_line;
831 }
832 }
@@ -1648,10 +1668,12 @@
1668 ** Options:
1669 ** --import-marks FILE Restore marks table from FILE
1670 ** --export-marks FILE Save marks table to FILE
1671 ** --rename-master NAME Renames the master branch to NAME
1672 ** --use-author Uses author as the committer
1673 ** --attribute "EMAIL USER" Attribute commits to USER
1674 ** instead of Git committer EMAIL address
1675 **
1676 ** --svn Import from the svnadmin-dump file format. The default
1677 ** behaviour (unless overridden by --flat) is to treat 3
1678 ** folders in the SVN root as special, following the
1679 ** common layout of SVN repositories. These are (by
@@ -1690,10 +1712,15 @@
1712 **
1713 ** --ignore-tree is useful for importing Subversion repositories which
1714 ** move branches to subdirectories of "branches/deleted" instead of
1715 ** deleting them. It can be supplied multiple times if necessary.
1716 **
1717 ** The --attribute option takes a quoted string argument comprised of a
1718 ** Git committer email and the username to be attributed to corresponding
1719 ** check-ins in the Fossil repository. This option can be repeated. For
1720 ** example, --attribute "[email protected] drh" --attribute "[email protected] X"
1721 **
1722 ** See also: export
1723 */
1724 void import_cmd(void){
1725 char *zPassword;
1726 FILE *pIn;
@@ -1776,10 +1803,23 @@
1803 markfile_out = find_option("export-marks", 0, 1);
1804 if( !(ggit.zMasterName = find_option("rename-master", 0, 1)) ){
1805 ggit.zMasterName = "master";
1806 }
1807 ggit.authorFlag = find_option("use-author", 0, 0)!=0;
1808 /*
1809 ** Extract --attribute 'emailaddr username' args that will populate
1810 ** new 'fx_' table to later match username for check-in attribution.
1811 */
1812 const char *zGitUser = find_option("attribute", 0, 1);
1813 while( zGitUser != 0 ){
1814 ggit.gitUserInfo = fossil_realloc(ggit.gitUserInfo, ++ggit.nGitAttr
1815 * sizeof(ggit.gitUserInfo[0]));
1816 char *currGitUser = fossil_strdup(zGitUser);
1817 ggit.gitUserInfo[ggit.nGitAttr-1].zEmail = next_token(&currGitUser);
1818 ggit.gitUserInfo[ggit.nGitAttr-1].zUser = rest_of_line(&currGitUser);
1819 zGitUser = find_option("attribute", 0, 1);
1820 }
1821 }
1822 verify_all_options();
1823
1824 if( g.argc!=3 && g.argc!=4 ){
1825 usage("--git|--svn ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?");
@@ -1901,10 +1941,28 @@
1941 }
1942 fclose(f);
1943 }
1944
1945 manifest_crosslink_begin();
1946 /*
1947 ** The following 'fx_' table is used to hold information needed for
1948 ** importing and exporting to attribute Fossil check-ins or Git commits
1949 ** to either a desired username or full contact information string.
1950 */
1951 if(ggit.nGitAttr > 0) {
1952 db_unprotect(PROTECT_ALL);
1953 db_multi_exec(
1954 "CREATE TABLE fx_git(user TEXT, email TEXT UNIQUE);"
1955 );
1956 for( int idx = 0; idx < ggit.nGitAttr; ++idx ){
1957 db_multi_exec(
1958 "INSERT OR IGNORE INTO fx_git(user, email) VALUES(%Q, %Q)",
1959 ggit.gitUserInfo[idx].zUser, ggit.gitUserInfo[idx].zEmail
1960 );
1961 }
1962 db_protect_pop();
1963 }
1964 git_fast_import(pIn);
1965 db_prepare(&q, "SELECT tcontent FROM xtag");
1966 while( db_step(&q)==SQLITE_ROW ){
1967 Blob record;
1968 db_ephemeral_blob(&q, 0, &record);
1969
+1 -1
--- src/info.c
+++ src/info.c
@@ -2398,11 +2398,11 @@
23982398
}
23992399
24002400
if( isFile ){
24012401
if( isSymbolicCI ){
24022402
zHeader = mprintf("%s at %s", file_tail(zName), zCI);
2403
- }else if( zCI ){
2403
+ }else if( zCIUuid && zCIUuid[0] ){
24042404
zHeader = mprintf("%s at [%S]", file_tail(zName), zCIUuid);
24052405
}else{
24062406
zHeader = mprintf("%s", file_tail(zName));
24072407
}
24082408
}else if( descOnly ){
24092409
--- src/info.c
+++ src/info.c
@@ -2398,11 +2398,11 @@
2398 }
2399
2400 if( isFile ){
2401 if( isSymbolicCI ){
2402 zHeader = mprintf("%s at %s", file_tail(zName), zCI);
2403 }else if( zCI ){
2404 zHeader = mprintf("%s at [%S]", file_tail(zName), zCIUuid);
2405 }else{
2406 zHeader = mprintf("%s", file_tail(zName));
2407 }
2408 }else if( descOnly ){
2409
--- src/info.c
+++ src/info.c
@@ -2398,11 +2398,11 @@
2398 }
2399
2400 if( isFile ){
2401 if( isSymbolicCI ){
2402 zHeader = mprintf("%s at %s", file_tail(zName), zCI);
2403 }else if( zCIUuid && zCIUuid[0] ){
2404 zHeader = mprintf("%s at [%S]", file_tail(zName), zCIUuid);
2405 }else{
2406 zHeader = mprintf("%s", file_tail(zName));
2407 }
2408 }else if( descOnly ){
2409
--- src/login.c
+++ src/login.c
@@ -643,10 +643,11 @@
643643
@ <p><span class="loginError">
644644
@ You entered an unknown user or an incorrect password.
645645
@ </span></p>
646646
;
647647
record_login_attempt(zUsername, zIpAddr, 0);
648
+ cgi_set_status(401, "Unauthorized");
648649
}else{
649650
/* Non-anonymous login is successful. Set a cookie of the form:
650651
**
651652
** HASH/PROJECT/LOGIN
652653
**
653654
--- src/login.c
+++ src/login.c
@@ -643,10 +643,11 @@
643 @ <p><span class="loginError">
644 @ You entered an unknown user or an incorrect password.
645 @ </span></p>
646 ;
647 record_login_attempt(zUsername, zIpAddr, 0);
 
648 }else{
649 /* Non-anonymous login is successful. Set a cookie of the form:
650 **
651 ** HASH/PROJECT/LOGIN
652 **
653
--- src/login.c
+++ src/login.c
@@ -643,10 +643,11 @@
643 @ <p><span class="loginError">
644 @ You entered an unknown user or an incorrect password.
645 @ </span></p>
646 ;
647 record_login_attempt(zUsername, zIpAddr, 0);
648 cgi_set_status(401, "Unauthorized");
649 }else{
650 /* Non-anonymous login is successful. Set a cookie of the form:
651 **
652 ** HASH/PROJECT/LOGIN
653 **
654
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -347,10 +347,11 @@
347347
int pikFlags = PIKCHR_PROCESS_NONCE
348348
| PIKCHR_PROCESS_DIV
349349
| PIKCHR_PROCESS_SRC
350350
| PIKCHR_PROCESS_ERR_PRE;
351351
Blob bSrc = empty_blob;
352
+ const char *zFgColor;
352353
353354
while( nArg>0 ){
354355
int i;
355356
for(i=0; i<nArg && !fossil_isspace(zArg[i]); i++){}
356357
if( i==6 && strncmp(zArg, "center", 6)==0 ){
@@ -370,10 +371,17 @@
370371
}
371372
while( i<nArg && fossil_isspace(zArg[i]) ){ i++; }
372373
zArg += i;
373374
nArg -= i;
374375
}
376
+ if( skin_detail_boolean("white-foreground") ){
377
+ pikFlags |= 0x02; /* PIKCHR_DARK_MODE */
378
+ }
379
+ zFgColor = skin_detail("pikchr-foreground");
380
+ if( zFgColor && zFgColor[0] ){
381
+ blob_appendf(&bSrc, "fgcolor = %s\n", zFgColor);
382
+ }
375383
blob_append(&bSrc, zSrc, nSrc)
376384
/*have to dup input to ensure a NUL-terminated source string */;
377385
pikchr_process(blob_str(&bSrc), pikFlags, 0, ob);
378386
blob_reset(&bSrc);
379387
}
380388
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -347,10 +347,11 @@
347 int pikFlags = PIKCHR_PROCESS_NONCE
348 | PIKCHR_PROCESS_DIV
349 | PIKCHR_PROCESS_SRC
350 | PIKCHR_PROCESS_ERR_PRE;
351 Blob bSrc = empty_blob;
 
352
353 while( nArg>0 ){
354 int i;
355 for(i=0; i<nArg && !fossil_isspace(zArg[i]); i++){}
356 if( i==6 && strncmp(zArg, "center", 6)==0 ){
@@ -370,10 +371,17 @@
370 }
371 while( i<nArg && fossil_isspace(zArg[i]) ){ i++; }
372 zArg += i;
373 nArg -= i;
374 }
 
 
 
 
 
 
 
375 blob_append(&bSrc, zSrc, nSrc)
376 /*have to dup input to ensure a NUL-terminated source string */;
377 pikchr_process(blob_str(&bSrc), pikFlags, 0, ob);
378 blob_reset(&bSrc);
379 }
380
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -347,10 +347,11 @@
347 int pikFlags = PIKCHR_PROCESS_NONCE
348 | PIKCHR_PROCESS_DIV
349 | PIKCHR_PROCESS_SRC
350 | PIKCHR_PROCESS_ERR_PRE;
351 Blob bSrc = empty_blob;
352 const char *zFgColor;
353
354 while( nArg>0 ){
355 int i;
356 for(i=0; i<nArg && !fossil_isspace(zArg[i]); i++){}
357 if( i==6 && strncmp(zArg, "center", 6)==0 ){
@@ -370,10 +371,17 @@
371 }
372 while( i<nArg && fossil_isspace(zArg[i]) ){ i++; }
373 zArg += i;
374 nArg -= i;
375 }
376 if( skin_detail_boolean("white-foreground") ){
377 pikFlags |= 0x02; /* PIKCHR_DARK_MODE */
378 }
379 zFgColor = skin_detail("pikchr-foreground");
380 if( zFgColor && zFgColor[0] ){
381 blob_appendf(&bSrc, "fgcolor = %s\n", zFgColor);
382 }
383 blob_append(&bSrc, zSrc, nSrc)
384 /*have to dup input to ensure a NUL-terminated source string */;
385 pikchr_process(blob_str(&bSrc), pikFlags, 0, ob);
386 blob_reset(&bSrc);
387 }
388
+15 -3
--- src/name.c
+++ src/name.c
@@ -1416,31 +1416,43 @@
14161416
** Output HTML that shows a table of all public phantoms.
14171417
*/
14181418
void table_of_public_phantoms(void){
14191419
Stmt q;
14201420
char *zRange;
1421
+ double rNow;
14211422
zRange = mprintf("IN (SELECT rid FROM phantom EXCEPT"
14221423
" SELECT rid FROM private)");
14231424
describe_artifacts(zRange);
14241425
fossil_free(zRange);
14251426
db_prepare(&q,
1426
- "SELECT rid, uuid, summary, ref"
1427
+ "SELECT rid, uuid, summary, ref,"
1428
+ " (SELECT mtime FROM blob, rcvfrom"
1429
+ " WHERE blob.uuid=ref AND rcvfrom.rcvid=blob.rcvid)"
14271430
" FROM description ORDER BY rid"
14281431
);
1432
+ rNow = db_double(0.0, "SELECT julianday('now')");
14291433
@ <table cellpadding="2" cellspacing="0" border="1">
1430
- @ <tr><th>RID<th>Description<th>Source
1434
+ @ <tr><th>RID<th>Description<th>Source<th>Age
14311435
while( db_step(&q)==SQLITE_ROW ){
14321436
int rid = db_column_int(&q,0);
14331437
const char *zUuid = db_column_text(&q, 1);
14341438
const char *zDesc = db_column_text(&q, 2);
14351439
const char *zRef = db_column_text(&q,3);
1440
+ double mtime = db_column_double(&q,4);
14361441
@ <tr><td valign="top">%d(rid)</td>
14371442
@ <td valign="top" align="left">%h(zUuid)<br>%h(zDesc)</td>
14381443
if( zRef && zRef[0] ){
14391444
@ <td valign="top">%z(href("%R/info/%!S",zRef))%!S(zRef)</a>
1445
+ if( mtime>0 ){
1446
+ char *zAge = human_readable_age(rNow - mtime);
1447
+ @ <td valign="top">%h(zAge)
1448
+ fossil_free(zAge);
1449
+ }else{
1450
+ @ <td>&nbsp;
1451
+ }
14401452
}else{
1441
- @ <td>&nbsp;
1453
+ @ <td>&nbsp;<td>&nbsp;
14421454
}
14431455
@ </tr>
14441456
}
14451457
@ </table>
14461458
db_finalize(&q);
14471459
--- src/name.c
+++ src/name.c
@@ -1416,31 +1416,43 @@
1416 ** Output HTML that shows a table of all public phantoms.
1417 */
1418 void table_of_public_phantoms(void){
1419 Stmt q;
1420 char *zRange;
 
1421 zRange = mprintf("IN (SELECT rid FROM phantom EXCEPT"
1422 " SELECT rid FROM private)");
1423 describe_artifacts(zRange);
1424 fossil_free(zRange);
1425 db_prepare(&q,
1426 "SELECT rid, uuid, summary, ref"
 
 
1427 " FROM description ORDER BY rid"
1428 );
 
1429 @ <table cellpadding="2" cellspacing="0" border="1">
1430 @ <tr><th>RID<th>Description<th>Source
1431 while( db_step(&q)==SQLITE_ROW ){
1432 int rid = db_column_int(&q,0);
1433 const char *zUuid = db_column_text(&q, 1);
1434 const char *zDesc = db_column_text(&q, 2);
1435 const char *zRef = db_column_text(&q,3);
 
1436 @ <tr><td valign="top">%d(rid)</td>
1437 @ <td valign="top" align="left">%h(zUuid)<br>%h(zDesc)</td>
1438 if( zRef && zRef[0] ){
1439 @ <td valign="top">%z(href("%R/info/%!S",zRef))%!S(zRef)</a>
 
 
 
 
 
 
 
1440 }else{
1441 @ <td>&nbsp;
1442 }
1443 @ </tr>
1444 }
1445 @ </table>
1446 db_finalize(&q);
1447
--- src/name.c
+++ src/name.c
@@ -1416,31 +1416,43 @@
1416 ** Output HTML that shows a table of all public phantoms.
1417 */
1418 void table_of_public_phantoms(void){
1419 Stmt q;
1420 char *zRange;
1421 double rNow;
1422 zRange = mprintf("IN (SELECT rid FROM phantom EXCEPT"
1423 " SELECT rid FROM private)");
1424 describe_artifacts(zRange);
1425 fossil_free(zRange);
1426 db_prepare(&q,
1427 "SELECT rid, uuid, summary, ref,"
1428 " (SELECT mtime FROM blob, rcvfrom"
1429 " WHERE blob.uuid=ref AND rcvfrom.rcvid=blob.rcvid)"
1430 " FROM description ORDER BY rid"
1431 );
1432 rNow = db_double(0.0, "SELECT julianday('now')");
1433 @ <table cellpadding="2" cellspacing="0" border="1">
1434 @ <tr><th>RID<th>Description<th>Source<th>Age
1435 while( db_step(&q)==SQLITE_ROW ){
1436 int rid = db_column_int(&q,0);
1437 const char *zUuid = db_column_text(&q, 1);
1438 const char *zDesc = db_column_text(&q, 2);
1439 const char *zRef = db_column_text(&q,3);
1440 double mtime = db_column_double(&q,4);
1441 @ <tr><td valign="top">%d(rid)</td>
1442 @ <td valign="top" align="left">%h(zUuid)<br>%h(zDesc)</td>
1443 if( zRef && zRef[0] ){
1444 @ <td valign="top">%z(href("%R/info/%!S",zRef))%!S(zRef)</a>
1445 if( mtime>0 ){
1446 char *zAge = human_readable_age(rNow - mtime);
1447 @ <td valign="top">%h(zAge)
1448 fossil_free(zAge);
1449 }else{
1450 @ <td>&nbsp;
1451 }
1452 }else{
1453 @ <td>&nbsp;<td>&nbsp;
1454 }
1455 @ </tr>
1456 }
1457 @ </table>
1458 db_finalize(&q);
1459
+318 -249
--- src/pikchr.c
+++ src/pikchr.c
@@ -363,10 +363,11 @@
363363
char bLayoutVars; /* True if cache is valid */
364364
char thenFlag; /* True if "then" seen */
365365
char samePath; /* aTPath copied by "same" */
366366
const char *zClass; /* Class name for the <svg> */
367367
int wSVG, hSVG; /* Width and height of the <svg> */
368
+ int fgcolor; /* fgcolor value, or -1 for none */
368369
/* Paths for lines are constructed here first, then transferred into
369370
** the PObj object at the end: */
370371
int nTPath; /* Number of entries on aTPath[] */
371372
int mTPath; /* For last entry, 1: x set, 2: y set */
372373
PPoint aTPath[1000]; /* Path under construction */
@@ -378,10 +379,14 @@
378379
/* Include PIKCHR_PLAINTEXT_ERRORS among the bits of mFlags on the 3rd
379380
** argument to pikchr() in order to cause error message text to come out
380381
** as text/plain instead of as text/html
381382
*/
382383
#define PIKCHR_PLAINTEXT_ERRORS 0x0001
384
+
385
+/* Include PIKCHR_DARK_MODE among the mFlag bits to invert colors.
386
+*/
387
+#define PIKCHR_DARK_MODE 0x0002
383388
384389
/*
385390
** The behavior of an object class is defined by an instance of
386391
** this structure. This is the "virtual method" table.
387392
*/
@@ -407,11 +412,11 @@
407412
static void pik_append_x(Pik*,const char*,PNum,const char*);
408413
static void pik_append_y(Pik*,const char*,PNum,const char*);
409414
static void pik_append_xy(Pik*,const char*,PNum,PNum);
410415
static void pik_append_dis(Pik*,const char*,PNum,const char*);
411416
static void pik_append_arc(Pik*,PNum,PNum,PNum,PNum);
412
-static void pik_append_clr(Pik*,const char*,PNum,const char*);
417
+static void pik_append_clr(Pik*,const char*,PNum,const char*,int);
413418
static void pik_append_style(Pik*,PObj*,int);
414419
static void pik_append_txt(Pik*,PObj*, PBox*);
415420
static void pik_draw_arrowhead(Pik*,PPoint*pFrom,PPoint*pTo,PObj*);
416421
static void pik_chop(PPoint*pFrom,PPoint*pTo,PNum);
417422
static void pik_error(Pik*,PToken*,const char*);
@@ -468,11 +473,11 @@
468473
static PObj *pik_position_assert(Pik*,PPoint*,PToken*,PPoint*);
469474
static PNum pik_dist(PPoint*,PPoint*);
470475
static void pik_add_macro(Pik*,PToken *pId,PToken *pCode);
471476
472477
473
-#line 499 "pikchr.c"
478
+#line 504 "pikchr.c"
474479
/**************** End of %include directives **********************************/
475480
/* These constants specify the various numeric values for terminal symbols.
476481
***************** Begin token definitions *************************************/
477482
#ifndef T_ID
478483
#define T_ID 1
@@ -1685,22 +1690,22 @@
16851690
** inside the C code.
16861691
*/
16871692
/********* Begin destructor definitions ***************************************/
16881693
case 98: /* statement_list */
16891694
{
1690
-#line 488 "pikchr.y"
1695
+#line 493 "pikchr.y"
16911696
pik_elist_free(p,(yypminor->yy119));
1692
-#line 1717 "pikchr.c"
1697
+#line 1722 "pikchr.c"
16931698
}
16941699
break;
16951700
case 99: /* statement */
16961701
case 100: /* unnamed_statement */
16971702
case 101: /* basetype */
16981703
{
1699
-#line 490 "pikchr.y"
1704
+#line 495 "pikchr.y"
17001705
pik_elem_free(p,(yypminor->yy38));
1701
-#line 1726 "pikchr.c"
1706
+#line 1731 "pikchr.c"
17021707
}
17031708
break;
17041709
/********* End destructor definitions *****************************************/
17051710
default: break; /* If no destructor action specified: do nothing */
17061711
}
@@ -1914,14 +1919,14 @@
19141919
#endif
19151920
while( yypParser->yytos>yypParser->yystack ) yy_pop_parser_stack(yypParser);
19161921
/* Here code is inserted which will execute if the parser
19171922
** stack every overflows */
19181923
/******** Begin %stack_overflow code ******************************************/
1919
-#line 522 "pikchr.y"
1924
+#line 527 "pikchr.y"
19201925
19211926
pik_error(p, 0, "parser stack overflow");
1922
-#line 1947 "pikchr.c"
1927
+#line 1952 "pikchr.c"
19231928
/******** End %stack_overflow code ********************************************/
19241929
pik_parserARG_STORE /* Suppress warning about unused %extra_argument var */
19251930
pik_parserCTX_STORE
19261931
}
19271932
@@ -2395,614 +2400,614 @@
23952400
** break;
23962401
*/
23972402
/********** Begin reduce actions **********************************************/
23982403
YYMINORTYPE yylhsminor;
23992404
case 0: /* document ::= statement_list */
2400
-#line 526 "pikchr.y"
2405
+#line 531 "pikchr.y"
24012406
{pik_render(p,yymsp[0].minor.yy119);}
2402
-#line 2427 "pikchr.c"
2407
+#line 2432 "pikchr.c"
24032408
break;
24042409
case 1: /* statement_list ::= statement */
2405
-#line 529 "pikchr.y"
2410
+#line 534 "pikchr.y"
24062411
{ yylhsminor.yy119 = pik_elist_append(p,0,yymsp[0].minor.yy38); }
2407
-#line 2432 "pikchr.c"
2412
+#line 2437 "pikchr.c"
24082413
yymsp[0].minor.yy119 = yylhsminor.yy119;
24092414
break;
24102415
case 2: /* statement_list ::= statement_list EOL statement */
2411
-#line 531 "pikchr.y"
2416
+#line 536 "pikchr.y"
24122417
{ yylhsminor.yy119 = pik_elist_append(p,yymsp[-2].minor.yy119,yymsp[0].minor.yy38); }
2413
-#line 2438 "pikchr.c"
2418
+#line 2443 "pikchr.c"
24142419
yymsp[-2].minor.yy119 = yylhsminor.yy119;
24152420
break;
24162421
case 3: /* statement ::= */
2417
-#line 534 "pikchr.y"
2422
+#line 539 "pikchr.y"
24182423
{ yymsp[1].minor.yy38 = 0; }
2419
-#line 2444 "pikchr.c"
2424
+#line 2449 "pikchr.c"
24202425
break;
24212426
case 4: /* statement ::= direction */
2422
-#line 535 "pikchr.y"
2427
+#line 540 "pikchr.y"
24232428
{ pik_set_direction(p,yymsp[0].minor.yy0.eCode); yylhsminor.yy38=0; }
2424
-#line 2449 "pikchr.c"
2429
+#line 2454 "pikchr.c"
24252430
yymsp[0].minor.yy38 = yylhsminor.yy38;
24262431
break;
24272432
case 5: /* statement ::= lvalue ASSIGN rvalue */
2428
-#line 536 "pikchr.y"
2433
+#line 541 "pikchr.y"
24292434
{pik_set_var(p,&yymsp[-2].minor.yy0,yymsp[0].minor.yy265,&yymsp[-1].minor.yy0); yylhsminor.yy38=0;}
2430
-#line 2455 "pikchr.c"
2435
+#line 2460 "pikchr.c"
24312436
yymsp[-2].minor.yy38 = yylhsminor.yy38;
24322437
break;
24332438
case 6: /* statement ::= PLACENAME COLON unnamed_statement */
2434
-#line 538 "pikchr.y"
2439
+#line 543 "pikchr.y"
24352440
{ yylhsminor.yy38 = yymsp[0].minor.yy38; pik_elem_setname(p,yymsp[0].minor.yy38,&yymsp[-2].minor.yy0); }
2436
-#line 2461 "pikchr.c"
2441
+#line 2466 "pikchr.c"
24372442
yymsp[-2].minor.yy38 = yylhsminor.yy38;
24382443
break;
24392444
case 7: /* statement ::= PLACENAME COLON position */
2440
-#line 540 "pikchr.y"
2445
+#line 545 "pikchr.y"
24412446
{ yylhsminor.yy38 = pik_elem_new(p,0,0,0);
24422447
if(yylhsminor.yy38){ yylhsminor.yy38->ptAt = yymsp[0].minor.yy43; pik_elem_setname(p,yylhsminor.yy38,&yymsp[-2].minor.yy0); }}
2443
-#line 2468 "pikchr.c"
2448
+#line 2473 "pikchr.c"
24442449
yymsp[-2].minor.yy38 = yylhsminor.yy38;
24452450
break;
24462451
case 8: /* statement ::= unnamed_statement */
2447
-#line 542 "pikchr.y"
2452
+#line 547 "pikchr.y"
24482453
{yylhsminor.yy38 = yymsp[0].minor.yy38;}
2449
-#line 2474 "pikchr.c"
2454
+#line 2479 "pikchr.c"
24502455
yymsp[0].minor.yy38 = yylhsminor.yy38;
24512456
break;
24522457
case 9: /* statement ::= print prlist */
2453
-#line 543 "pikchr.y"
2458
+#line 548 "pikchr.y"
24542459
{pik_append(p,"<br>\n",5); yymsp[-1].minor.yy38=0;}
2455
-#line 2480 "pikchr.c"
2460
+#line 2485 "pikchr.c"
24562461
break;
24572462
case 10: /* statement ::= ASSERT LP expr EQ expr RP */
2458
-#line 548 "pikchr.y"
2463
+#line 553 "pikchr.y"
24592464
{yymsp[-5].minor.yy38=pik_assert(p,yymsp[-3].minor.yy265,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy265);}
2460
-#line 2485 "pikchr.c"
2465
+#line 2490 "pikchr.c"
24612466
break;
24622467
case 11: /* statement ::= ASSERT LP position EQ position RP */
2463
-#line 550 "pikchr.y"
2468
+#line 555 "pikchr.y"
24642469
{yymsp[-5].minor.yy38=pik_position_assert(p,&yymsp[-3].minor.yy43,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy43);}
2465
-#line 2490 "pikchr.c"
2470
+#line 2495 "pikchr.c"
24662471
break;
24672472
case 12: /* statement ::= DEFINE ID CODEBLOCK */
2468
-#line 551 "pikchr.y"
2473
+#line 556 "pikchr.y"
24692474
{yymsp[-2].minor.yy38=0; pik_add_macro(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0);}
2470
-#line 2495 "pikchr.c"
2475
+#line 2500 "pikchr.c"
24712476
break;
24722477
case 13: /* rvalue ::= PLACENAME */
2473
-#line 562 "pikchr.y"
2478
+#line 567 "pikchr.y"
24742479
{yylhsminor.yy265 = pik_lookup_color(p,&yymsp[0].minor.yy0);}
2475
-#line 2500 "pikchr.c"
2480
+#line 2505 "pikchr.c"
24762481
yymsp[0].minor.yy265 = yylhsminor.yy265;
24772482
break;
24782483
case 14: /* pritem ::= FILL */
24792484
case 15: /* pritem ::= COLOR */ yytestcase(yyruleno==15);
24802485
case 16: /* pritem ::= THICKNESS */ yytestcase(yyruleno==16);
2481
-#line 567 "pikchr.y"
2486
+#line 572 "pikchr.y"
24822487
{pik_append_num(p,"",pik_value(p,yymsp[0].minor.yy0.z,yymsp[0].minor.yy0.n,0));}
2483
-#line 2508 "pikchr.c"
2488
+#line 2513 "pikchr.c"
24842489
break;
24852490
case 17: /* pritem ::= rvalue */
2486
-#line 570 "pikchr.y"
2491
+#line 575 "pikchr.y"
24872492
{pik_append_num(p,"",yymsp[0].minor.yy265);}
2488
-#line 2513 "pikchr.c"
2493
+#line 2518 "pikchr.c"
24892494
break;
24902495
case 18: /* pritem ::= STRING */
2491
-#line 571 "pikchr.y"
2496
+#line 576 "pikchr.y"
24922497
{pik_append_text(p,yymsp[0].minor.yy0.z+1,yymsp[0].minor.yy0.n-2,0);}
2493
-#line 2518 "pikchr.c"
2498
+#line 2523 "pikchr.c"
24942499
break;
24952500
case 19: /* prsep ::= COMMA */
2496
-#line 572 "pikchr.y"
2501
+#line 577 "pikchr.y"
24972502
{pik_append(p, " ", 1);}
2498
-#line 2523 "pikchr.c"
2503
+#line 2528 "pikchr.c"
24992504
break;
25002505
case 20: /* unnamed_statement ::= basetype attribute_list */
2501
-#line 575 "pikchr.y"
2506
+#line 580 "pikchr.y"
25022507
{yylhsminor.yy38 = yymsp[-1].minor.yy38; pik_after_adding_attributes(p,yylhsminor.yy38);}
2503
-#line 2528 "pikchr.c"
2508
+#line 2533 "pikchr.c"
25042509
yymsp[-1].minor.yy38 = yylhsminor.yy38;
25052510
break;
25062511
case 21: /* basetype ::= CLASSNAME */
2507
-#line 577 "pikchr.y"
2512
+#line 582 "pikchr.y"
25082513
{yylhsminor.yy38 = pik_elem_new(p,&yymsp[0].minor.yy0,0,0); }
2509
-#line 2534 "pikchr.c"
2514
+#line 2539 "pikchr.c"
25102515
yymsp[0].minor.yy38 = yylhsminor.yy38;
25112516
break;
25122517
case 22: /* basetype ::= STRING textposition */
2513
-#line 579 "pikchr.y"
2518
+#line 584 "pikchr.y"
25142519
{yymsp[-1].minor.yy0.eCode = yymsp[0].minor.yy196; yylhsminor.yy38 = pik_elem_new(p,0,&yymsp[-1].minor.yy0,0); }
2515
-#line 2540 "pikchr.c"
2520
+#line 2545 "pikchr.c"
25162521
yymsp[-1].minor.yy38 = yylhsminor.yy38;
25172522
break;
25182523
case 23: /* basetype ::= LB savelist statement_list RB */
2519
-#line 581 "pikchr.y"
2524
+#line 586 "pikchr.y"
25202525
{ p->list = yymsp[-2].minor.yy119; yymsp[-3].minor.yy38 = pik_elem_new(p,0,0,yymsp[-1].minor.yy119); if(yymsp[-3].minor.yy38) yymsp[-3].minor.yy38->errTok = yymsp[0].minor.yy0; }
2521
-#line 2546 "pikchr.c"
2526
+#line 2551 "pikchr.c"
25222527
break;
25232528
case 24: /* savelist ::= */
2524
-#line 586 "pikchr.y"
2529
+#line 591 "pikchr.y"
25252530
{yymsp[1].minor.yy119 = p->list; p->list = 0;}
2526
-#line 2551 "pikchr.c"
2531
+#line 2556 "pikchr.c"
25272532
break;
25282533
case 25: /* relexpr ::= expr */
2529
-#line 593 "pikchr.y"
2534
+#line 598 "pikchr.y"
25302535
{yylhsminor.yy200.rAbs = yymsp[0].minor.yy265; yylhsminor.yy200.rRel = 0;}
2531
-#line 2556 "pikchr.c"
2536
+#line 2561 "pikchr.c"
25322537
yymsp[0].minor.yy200 = yylhsminor.yy200;
25332538
break;
25342539
case 26: /* relexpr ::= expr PERCENT */
2535
-#line 594 "pikchr.y"
2540
+#line 599 "pikchr.y"
25362541
{yylhsminor.yy200.rAbs = 0; yylhsminor.yy200.rRel = yymsp[-1].minor.yy265/100;}
2537
-#line 2562 "pikchr.c"
2542
+#line 2567 "pikchr.c"
25382543
yymsp[-1].minor.yy200 = yylhsminor.yy200;
25392544
break;
25402545
case 27: /* optrelexpr ::= */
2541
-#line 596 "pikchr.y"
2546
+#line 601 "pikchr.y"
25422547
{yymsp[1].minor.yy200.rAbs = 0; yymsp[1].minor.yy200.rRel = 1.0;}
2543
-#line 2568 "pikchr.c"
2548
+#line 2573 "pikchr.c"
25442549
break;
25452550
case 28: /* attribute_list ::= relexpr alist */
2546
-#line 598 "pikchr.y"
2551
+#line 603 "pikchr.y"
25472552
{pik_add_direction(p,0,&yymsp[-1].minor.yy200);}
2548
-#line 2573 "pikchr.c"
2553
+#line 2578 "pikchr.c"
25492554
break;
25502555
case 29: /* attribute ::= numproperty relexpr */
2551
-#line 602 "pikchr.y"
2556
+#line 607 "pikchr.y"
25522557
{ pik_set_numprop(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy200); }
2553
-#line 2578 "pikchr.c"
2558
+#line 2583 "pikchr.c"
25542559
break;
25552560
case 30: /* attribute ::= dashproperty expr */
2556
-#line 603 "pikchr.y"
2561
+#line 608 "pikchr.y"
25572562
{ pik_set_dashed(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy265); }
2558
-#line 2583 "pikchr.c"
2563
+#line 2588 "pikchr.c"
25592564
break;
25602565
case 31: /* attribute ::= dashproperty */
2561
-#line 604 "pikchr.y"
2566
+#line 609 "pikchr.y"
25622567
{ pik_set_dashed(p,&yymsp[0].minor.yy0,0); }
2563
-#line 2588 "pikchr.c"
2568
+#line 2593 "pikchr.c"
25642569
break;
25652570
case 32: /* attribute ::= colorproperty rvalue */
2566
-#line 605 "pikchr.y"
2571
+#line 610 "pikchr.y"
25672572
{ pik_set_clrprop(p,&yymsp[-1].minor.yy0,yymsp[0].minor.yy265); }
2568
-#line 2593 "pikchr.c"
2573
+#line 2598 "pikchr.c"
25692574
break;
25702575
case 33: /* attribute ::= go direction optrelexpr */
2571
-#line 606 "pikchr.y"
2576
+#line 611 "pikchr.y"
25722577
{ pik_add_direction(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy200);}
2573
-#line 2598 "pikchr.c"
2578
+#line 2603 "pikchr.c"
25742579
break;
25752580
case 34: /* attribute ::= go direction even position */
2576
-#line 607 "pikchr.y"
2581
+#line 612 "pikchr.y"
25772582
{pik_evenwith(p,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy43);}
2578
-#line 2603 "pikchr.c"
2583
+#line 2608 "pikchr.c"
25792584
break;
25802585
case 35: /* attribute ::= CLOSE */
2581
-#line 608 "pikchr.y"
2586
+#line 613 "pikchr.y"
25822587
{ pik_close_path(p,&yymsp[0].minor.yy0); }
2583
-#line 2608 "pikchr.c"
2588
+#line 2613 "pikchr.c"
25842589
break;
25852590
case 36: /* attribute ::= CHOP */
2586
-#line 609 "pikchr.y"
2591
+#line 614 "pikchr.y"
25872592
{ p->cur->bChop = 1; }
2588
-#line 2613 "pikchr.c"
2593
+#line 2618 "pikchr.c"
25892594
break;
25902595
case 37: /* attribute ::= FROM position */
2591
-#line 610 "pikchr.y"
2596
+#line 615 "pikchr.y"
25922597
{ pik_set_from(p,p->cur,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy43); }
2593
-#line 2618 "pikchr.c"
2598
+#line 2623 "pikchr.c"
25942599
break;
25952600
case 38: /* attribute ::= TO position */
2596
-#line 611 "pikchr.y"
2601
+#line 616 "pikchr.y"
25972602
{ pik_add_to(p,p->cur,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy43); }
2598
-#line 2623 "pikchr.c"
2603
+#line 2628 "pikchr.c"
25992604
break;
26002605
case 39: /* attribute ::= THEN */
2601
-#line 612 "pikchr.y"
2606
+#line 617 "pikchr.y"
26022607
{ pik_then(p, &yymsp[0].minor.yy0, p->cur); }
2603
-#line 2628 "pikchr.c"
2608
+#line 2633 "pikchr.c"
26042609
break;
26052610
case 40: /* attribute ::= THEN optrelexpr HEADING expr */
26062611
case 42: /* attribute ::= GO optrelexpr HEADING expr */ yytestcase(yyruleno==42);
2607
-#line 614 "pikchr.y"
2612
+#line 619 "pikchr.y"
26082613
{pik_move_hdg(p,&yymsp[-2].minor.yy200,&yymsp[-1].minor.yy0,yymsp[0].minor.yy265,0,&yymsp[-3].minor.yy0);}
2609
-#line 2634 "pikchr.c"
2614
+#line 2639 "pikchr.c"
26102615
break;
26112616
case 41: /* attribute ::= THEN optrelexpr EDGEPT */
26122617
case 43: /* attribute ::= GO optrelexpr EDGEPT */ yytestcase(yyruleno==43);
2613
-#line 615 "pikchr.y"
2618
+#line 620 "pikchr.y"
26142619
{pik_move_hdg(p,&yymsp[-1].minor.yy200,0,0,&yymsp[0].minor.yy0,&yymsp[-2].minor.yy0);}
2615
-#line 2640 "pikchr.c"
2620
+#line 2645 "pikchr.c"
26162621
break;
26172622
case 44: /* attribute ::= AT position */
2618
-#line 620 "pikchr.y"
2623
+#line 625 "pikchr.y"
26192624
{ pik_set_at(p,0,&yymsp[0].minor.yy43,&yymsp[-1].minor.yy0); }
2620
-#line 2645 "pikchr.c"
2625
+#line 2650 "pikchr.c"
26212626
break;
26222627
case 45: /* attribute ::= SAME */
2623
-#line 622 "pikchr.y"
2628
+#line 627 "pikchr.y"
26242629
{pik_same(p,0,&yymsp[0].minor.yy0);}
2625
-#line 2650 "pikchr.c"
2630
+#line 2655 "pikchr.c"
26262631
break;
26272632
case 46: /* attribute ::= SAME AS object */
2628
-#line 623 "pikchr.y"
2633
+#line 628 "pikchr.y"
26292634
{pik_same(p,yymsp[0].minor.yy38,&yymsp[-2].minor.yy0);}
2630
-#line 2655 "pikchr.c"
2635
+#line 2660 "pikchr.c"
26312636
break;
26322637
case 47: /* attribute ::= STRING textposition */
2633
-#line 624 "pikchr.y"
2638
+#line 629 "pikchr.y"
26342639
{pik_add_txt(p,&yymsp[-1].minor.yy0,yymsp[0].minor.yy196);}
2635
-#line 2660 "pikchr.c"
2640
+#line 2665 "pikchr.c"
26362641
break;
26372642
case 48: /* attribute ::= FIT */
2638
-#line 625 "pikchr.y"
2643
+#line 630 "pikchr.y"
26392644
{pik_size_to_fit(p,&yymsp[0].minor.yy0,3); }
2640
-#line 2665 "pikchr.c"
2645
+#line 2670 "pikchr.c"
26412646
break;
26422647
case 49: /* attribute ::= BEHIND object */
2643
-#line 626 "pikchr.y"
2648
+#line 631 "pikchr.y"
26442649
{pik_behind(p,yymsp[0].minor.yy38);}
2645
-#line 2670 "pikchr.c"
2650
+#line 2675 "pikchr.c"
26462651
break;
26472652
case 50: /* withclause ::= DOT_E edge AT position */
26482653
case 51: /* withclause ::= edge AT position */ yytestcase(yyruleno==51);
2649
-#line 634 "pikchr.y"
2654
+#line 639 "pikchr.y"
26502655
{ pik_set_at(p,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy43,&yymsp[-1].minor.yy0); }
2651
-#line 2676 "pikchr.c"
2656
+#line 2681 "pikchr.c"
26522657
break;
26532658
case 52: /* numproperty ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS */
2654
-#line 638 "pikchr.y"
2659
+#line 643 "pikchr.y"
26552660
{yylhsminor.yy0 = yymsp[0].minor.yy0;}
2656
-#line 2681 "pikchr.c"
2661
+#line 2686 "pikchr.c"
26572662
yymsp[0].minor.yy0 = yylhsminor.yy0;
26582663
break;
26592664
case 53: /* boolproperty ::= CW */
2660
-#line 649 "pikchr.y"
2665
+#line 654 "pikchr.y"
26612666
{p->cur->cw = 1;}
2662
-#line 2687 "pikchr.c"
2667
+#line 2692 "pikchr.c"
26632668
break;
26642669
case 54: /* boolproperty ::= CCW */
2665
-#line 650 "pikchr.y"
2670
+#line 655 "pikchr.y"
26662671
{p->cur->cw = 0;}
2667
-#line 2692 "pikchr.c"
2672
+#line 2697 "pikchr.c"
26682673
break;
26692674
case 55: /* boolproperty ::= LARROW */
2670
-#line 651 "pikchr.y"
2675
+#line 656 "pikchr.y"
26712676
{p->cur->larrow=1; p->cur->rarrow=0; }
2672
-#line 2697 "pikchr.c"
2677
+#line 2702 "pikchr.c"
26732678
break;
26742679
case 56: /* boolproperty ::= RARROW */
2675
-#line 652 "pikchr.y"
2680
+#line 657 "pikchr.y"
26762681
{p->cur->larrow=0; p->cur->rarrow=1; }
2677
-#line 2702 "pikchr.c"
2682
+#line 2707 "pikchr.c"
26782683
break;
26792684
case 57: /* boolproperty ::= LRARROW */
2680
-#line 653 "pikchr.y"
2685
+#line 658 "pikchr.y"
26812686
{p->cur->larrow=1; p->cur->rarrow=1; }
2682
-#line 2707 "pikchr.c"
2687
+#line 2712 "pikchr.c"
26832688
break;
26842689
case 58: /* boolproperty ::= INVIS */
2685
-#line 654 "pikchr.y"
2690
+#line 659 "pikchr.y"
26862691
{p->cur->sw = 0.0;}
2687
-#line 2712 "pikchr.c"
2692
+#line 2717 "pikchr.c"
26882693
break;
26892694
case 59: /* boolproperty ::= THICK */
2690
-#line 655 "pikchr.y"
2695
+#line 660 "pikchr.y"
26912696
{p->cur->sw *= 1.5;}
2692
-#line 2717 "pikchr.c"
2697
+#line 2722 "pikchr.c"
26932698
break;
26942699
case 60: /* boolproperty ::= THIN */
2695
-#line 656 "pikchr.y"
2700
+#line 661 "pikchr.y"
26962701
{p->cur->sw *= 0.67;}
2697
-#line 2722 "pikchr.c"
2702
+#line 2727 "pikchr.c"
26982703
break;
26992704
case 61: /* boolproperty ::= SOLID */
2700
-#line 657 "pikchr.y"
2705
+#line 662 "pikchr.y"
27012706
{p->cur->sw = pik_value(p,"thickness",9,0);
27022707
p->cur->dotted = p->cur->dashed = 0.0;}
2703
-#line 2728 "pikchr.c"
2708
+#line 2733 "pikchr.c"
27042709
break;
27052710
case 62: /* textposition ::= */
2706
-#line 660 "pikchr.y"
2711
+#line 665 "pikchr.y"
27072712
{yymsp[1].minor.yy196 = 0;}
2708
-#line 2733 "pikchr.c"
2713
+#line 2738 "pikchr.c"
27092714
break;
27102715
case 63: /* textposition ::= textposition CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|ALIGNED|BIG|SMALL */
2711
-#line 663 "pikchr.y"
2716
+#line 668 "pikchr.y"
27122717
{yylhsminor.yy196 = pik_text_position(yymsp[-1].minor.yy196,&yymsp[0].minor.yy0);}
2713
-#line 2738 "pikchr.c"
2718
+#line 2743 "pikchr.c"
27142719
yymsp[-1].minor.yy196 = yylhsminor.yy196;
27152720
break;
27162721
case 64: /* position ::= expr COMMA expr */
2717
-#line 666 "pikchr.y"
2722
+#line 671 "pikchr.y"
27182723
{yylhsminor.yy43.x=yymsp[-2].minor.yy265; yylhsminor.yy43.y=yymsp[0].minor.yy265;}
2719
-#line 2744 "pikchr.c"
2724
+#line 2749 "pikchr.c"
27202725
yymsp[-2].minor.yy43 = yylhsminor.yy43;
27212726
break;
27222727
case 65: /* position ::= place PLUS expr COMMA expr */
2723
-#line 668 "pikchr.y"
2728
+#line 673 "pikchr.y"
27242729
{yylhsminor.yy43.x=yymsp[-4].minor.yy43.x+yymsp[-2].minor.yy265; yylhsminor.yy43.y=yymsp[-4].minor.yy43.y+yymsp[0].minor.yy265;}
2725
-#line 2750 "pikchr.c"
2730
+#line 2755 "pikchr.c"
27262731
yymsp[-4].minor.yy43 = yylhsminor.yy43;
27272732
break;
27282733
case 66: /* position ::= place MINUS expr COMMA expr */
2729
-#line 669 "pikchr.y"
2734
+#line 674 "pikchr.y"
27302735
{yylhsminor.yy43.x=yymsp[-4].minor.yy43.x-yymsp[-2].minor.yy265; yylhsminor.yy43.y=yymsp[-4].minor.yy43.y-yymsp[0].minor.yy265;}
2731
-#line 2756 "pikchr.c"
2736
+#line 2761 "pikchr.c"
27322737
yymsp[-4].minor.yy43 = yylhsminor.yy43;
27332738
break;
27342739
case 67: /* position ::= place PLUS LP expr COMMA expr RP */
2735
-#line 671 "pikchr.y"
2740
+#line 676 "pikchr.y"
27362741
{yylhsminor.yy43.x=yymsp[-6].minor.yy43.x+yymsp[-3].minor.yy265; yylhsminor.yy43.y=yymsp[-6].minor.yy43.y+yymsp[-1].minor.yy265;}
2737
-#line 2762 "pikchr.c"
2742
+#line 2767 "pikchr.c"
27382743
yymsp[-6].minor.yy43 = yylhsminor.yy43;
27392744
break;
27402745
case 68: /* position ::= place MINUS LP expr COMMA expr RP */
2741
-#line 673 "pikchr.y"
2746
+#line 678 "pikchr.y"
27422747
{yylhsminor.yy43.x=yymsp[-6].minor.yy43.x-yymsp[-3].minor.yy265; yylhsminor.yy43.y=yymsp[-6].minor.yy43.y-yymsp[-1].minor.yy265;}
2743
-#line 2768 "pikchr.c"
2748
+#line 2773 "pikchr.c"
27442749
yymsp[-6].minor.yy43 = yylhsminor.yy43;
27452750
break;
27462751
case 69: /* position ::= LP position COMMA position RP */
2747
-#line 674 "pikchr.y"
2752
+#line 679 "pikchr.y"
27482753
{yymsp[-4].minor.yy43.x=yymsp[-3].minor.yy43.x; yymsp[-4].minor.yy43.y=yymsp[-1].minor.yy43.y;}
2749
-#line 2774 "pikchr.c"
2754
+#line 2779 "pikchr.c"
27502755
break;
27512756
case 70: /* position ::= LP position RP */
2752
-#line 675 "pikchr.y"
2757
+#line 680 "pikchr.y"
27532758
{yymsp[-2].minor.yy43=yymsp[-1].minor.yy43;}
2754
-#line 2779 "pikchr.c"
2759
+#line 2784 "pikchr.c"
27552760
break;
27562761
case 71: /* position ::= expr between position AND position */
2757
-#line 677 "pikchr.y"
2762
+#line 682 "pikchr.y"
27582763
{yylhsminor.yy43 = pik_position_between(yymsp[-4].minor.yy265,yymsp[-2].minor.yy43,yymsp[0].minor.yy43);}
2759
-#line 2784 "pikchr.c"
2764
+#line 2789 "pikchr.c"
27602765
yymsp[-4].minor.yy43 = yylhsminor.yy43;
27612766
break;
27622767
case 72: /* position ::= expr LT position COMMA position GT */
2763
-#line 679 "pikchr.y"
2768
+#line 684 "pikchr.y"
27642769
{yylhsminor.yy43 = pik_position_between(yymsp[-5].minor.yy265,yymsp[-3].minor.yy43,yymsp[-1].minor.yy43);}
2765
-#line 2790 "pikchr.c"
2770
+#line 2795 "pikchr.c"
27662771
yymsp[-5].minor.yy43 = yylhsminor.yy43;
27672772
break;
27682773
case 73: /* position ::= expr ABOVE position */
2769
-#line 680 "pikchr.y"
2774
+#line 685 "pikchr.y"
27702775
{yylhsminor.yy43=yymsp[0].minor.yy43; yylhsminor.yy43.y += yymsp[-2].minor.yy265;}
2771
-#line 2796 "pikchr.c"
2776
+#line 2801 "pikchr.c"
27722777
yymsp[-2].minor.yy43 = yylhsminor.yy43;
27732778
break;
27742779
case 74: /* position ::= expr BELOW position */
2775
-#line 681 "pikchr.y"
2780
+#line 686 "pikchr.y"
27762781
{yylhsminor.yy43=yymsp[0].minor.yy43; yylhsminor.yy43.y -= yymsp[-2].minor.yy265;}
2777
-#line 2802 "pikchr.c"
2782
+#line 2807 "pikchr.c"
27782783
yymsp[-2].minor.yy43 = yylhsminor.yy43;
27792784
break;
27802785
case 75: /* position ::= expr LEFT OF position */
2781
-#line 682 "pikchr.y"
2786
+#line 687 "pikchr.y"
27822787
{yylhsminor.yy43=yymsp[0].minor.yy43; yylhsminor.yy43.x -= yymsp[-3].minor.yy265;}
2783
-#line 2808 "pikchr.c"
2788
+#line 2813 "pikchr.c"
27842789
yymsp[-3].minor.yy43 = yylhsminor.yy43;
27852790
break;
27862791
case 76: /* position ::= expr RIGHT OF position */
2787
-#line 683 "pikchr.y"
2792
+#line 688 "pikchr.y"
27882793
{yylhsminor.yy43=yymsp[0].minor.yy43; yylhsminor.yy43.x += yymsp[-3].minor.yy265;}
2789
-#line 2814 "pikchr.c"
2794
+#line 2819 "pikchr.c"
27902795
yymsp[-3].minor.yy43 = yylhsminor.yy43;
27912796
break;
27922797
case 77: /* position ::= expr ON HEADING EDGEPT OF position */
2793
-#line 685 "pikchr.y"
2798
+#line 690 "pikchr.y"
27942799
{yylhsminor.yy43 = pik_position_at_hdg(yymsp[-5].minor.yy265,&yymsp[-2].minor.yy0,yymsp[0].minor.yy43);}
2795
-#line 2820 "pikchr.c"
2800
+#line 2825 "pikchr.c"
27962801
yymsp[-5].minor.yy43 = yylhsminor.yy43;
27972802
break;
27982803
case 78: /* position ::= expr HEADING EDGEPT OF position */
2799
-#line 687 "pikchr.y"
2804
+#line 692 "pikchr.y"
28002805
{yylhsminor.yy43 = pik_position_at_hdg(yymsp[-4].minor.yy265,&yymsp[-2].minor.yy0,yymsp[0].minor.yy43);}
2801
-#line 2826 "pikchr.c"
2806
+#line 2831 "pikchr.c"
28022807
yymsp[-4].minor.yy43 = yylhsminor.yy43;
28032808
break;
28042809
case 79: /* position ::= expr EDGEPT OF position */
2805
-#line 689 "pikchr.y"
2810
+#line 694 "pikchr.y"
28062811
{yylhsminor.yy43 = pik_position_at_hdg(yymsp[-3].minor.yy265,&yymsp[-2].minor.yy0,yymsp[0].minor.yy43);}
2807
-#line 2832 "pikchr.c"
2812
+#line 2837 "pikchr.c"
28082813
yymsp[-3].minor.yy43 = yylhsminor.yy43;
28092814
break;
28102815
case 80: /* position ::= expr ON HEADING expr FROM position */
2811
-#line 691 "pikchr.y"
2816
+#line 696 "pikchr.y"
28122817
{yylhsminor.yy43 = pik_position_at_angle(yymsp[-5].minor.yy265,yymsp[-2].minor.yy265,yymsp[0].minor.yy43);}
2813
-#line 2838 "pikchr.c"
2818
+#line 2843 "pikchr.c"
28142819
yymsp[-5].minor.yy43 = yylhsminor.yy43;
28152820
break;
28162821
case 81: /* position ::= expr HEADING expr FROM position */
2817
-#line 693 "pikchr.y"
2822
+#line 698 "pikchr.y"
28182823
{yylhsminor.yy43 = pik_position_at_angle(yymsp[-4].minor.yy265,yymsp[-2].minor.yy265,yymsp[0].minor.yy43);}
2819
-#line 2844 "pikchr.c"
2824
+#line 2849 "pikchr.c"
28202825
yymsp[-4].minor.yy43 = yylhsminor.yy43;
28212826
break;
28222827
case 82: /* place ::= edge OF object */
2823
-#line 705 "pikchr.y"
2828
+#line 710 "pikchr.y"
28242829
{yylhsminor.yy43 = pik_place_of_elem(p,yymsp[0].minor.yy38,&yymsp[-2].minor.yy0);}
2825
-#line 2850 "pikchr.c"
2830
+#line 2855 "pikchr.c"
28262831
yymsp[-2].minor.yy43 = yylhsminor.yy43;
28272832
break;
28282833
case 83: /* place2 ::= object */
2829
-#line 706 "pikchr.y"
2834
+#line 711 "pikchr.y"
28302835
{yylhsminor.yy43 = pik_place_of_elem(p,yymsp[0].minor.yy38,0);}
2831
-#line 2856 "pikchr.c"
2836
+#line 2861 "pikchr.c"
28322837
yymsp[0].minor.yy43 = yylhsminor.yy43;
28332838
break;
28342839
case 84: /* place2 ::= object DOT_E edge */
2835
-#line 707 "pikchr.y"
2840
+#line 712 "pikchr.y"
28362841
{yylhsminor.yy43 = pik_place_of_elem(p,yymsp[-2].minor.yy38,&yymsp[0].minor.yy0);}
2837
-#line 2862 "pikchr.c"
2842
+#line 2867 "pikchr.c"
28382843
yymsp[-2].minor.yy43 = yylhsminor.yy43;
28392844
break;
28402845
case 85: /* place2 ::= NTH VERTEX OF object */
2841
-#line 708 "pikchr.y"
2846
+#line 713 "pikchr.y"
28422847
{yylhsminor.yy43 = pik_nth_vertex(p,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,yymsp[0].minor.yy38);}
2843
-#line 2868 "pikchr.c"
2848
+#line 2873 "pikchr.c"
28442849
yymsp[-3].minor.yy43 = yylhsminor.yy43;
28452850
break;
28462851
case 86: /* object ::= nth */
2847
-#line 720 "pikchr.y"
2852
+#line 725 "pikchr.y"
28482853
{yylhsminor.yy38 = pik_find_nth(p,0,&yymsp[0].minor.yy0);}
2849
-#line 2874 "pikchr.c"
2854
+#line 2879 "pikchr.c"
28502855
yymsp[0].minor.yy38 = yylhsminor.yy38;
28512856
break;
28522857
case 87: /* object ::= nth OF|IN object */
2853
-#line 721 "pikchr.y"
2858
+#line 726 "pikchr.y"
28542859
{yylhsminor.yy38 = pik_find_nth(p,yymsp[0].minor.yy38,&yymsp[-2].minor.yy0);}
2855
-#line 2880 "pikchr.c"
2860
+#line 2885 "pikchr.c"
28562861
yymsp[-2].minor.yy38 = yylhsminor.yy38;
28572862
break;
28582863
case 88: /* objectname ::= PLACENAME */
2859
-#line 723 "pikchr.y"
2864
+#line 728 "pikchr.y"
28602865
{yylhsminor.yy38 = pik_find_byname(p,0,&yymsp[0].minor.yy0);}
2861
-#line 2886 "pikchr.c"
2866
+#line 2891 "pikchr.c"
28622867
yymsp[0].minor.yy38 = yylhsminor.yy38;
28632868
break;
28642869
case 89: /* objectname ::= objectname DOT_U PLACENAME */
2865
-#line 725 "pikchr.y"
2870
+#line 730 "pikchr.y"
28662871
{yylhsminor.yy38 = pik_find_byname(p,yymsp[-2].minor.yy38,&yymsp[0].minor.yy0);}
2867
-#line 2892 "pikchr.c"
2872
+#line 2897 "pikchr.c"
28682873
yymsp[-2].minor.yy38 = yylhsminor.yy38;
28692874
break;
28702875
case 90: /* nth ::= NTH CLASSNAME */
2871
-#line 727 "pikchr.y"
2876
+#line 732 "pikchr.y"
28722877
{yylhsminor.yy0=yymsp[0].minor.yy0; yylhsminor.yy0.eCode = pik_nth_value(p,&yymsp[-1].minor.yy0); }
2873
-#line 2898 "pikchr.c"
2878
+#line 2903 "pikchr.c"
28742879
yymsp[-1].minor.yy0 = yylhsminor.yy0;
28752880
break;
28762881
case 91: /* nth ::= NTH LAST CLASSNAME */
2877
-#line 728 "pikchr.y"
2882
+#line 733 "pikchr.y"
28782883
{yylhsminor.yy0=yymsp[0].minor.yy0; yylhsminor.yy0.eCode = -pik_nth_value(p,&yymsp[-2].minor.yy0); }
2879
-#line 2904 "pikchr.c"
2884
+#line 2909 "pikchr.c"
28802885
yymsp[-2].minor.yy0 = yylhsminor.yy0;
28812886
break;
28822887
case 92: /* nth ::= LAST CLASSNAME */
2883
-#line 729 "pikchr.y"
2888
+#line 734 "pikchr.y"
28842889
{yymsp[-1].minor.yy0=yymsp[0].minor.yy0; yymsp[-1].minor.yy0.eCode = -1;}
2885
-#line 2910 "pikchr.c"
2890
+#line 2915 "pikchr.c"
28862891
break;
28872892
case 93: /* nth ::= LAST */
2888
-#line 730 "pikchr.y"
2893
+#line 735 "pikchr.y"
28892894
{yylhsminor.yy0=yymsp[0].minor.yy0; yylhsminor.yy0.eCode = -1;}
2890
-#line 2915 "pikchr.c"
2895
+#line 2920 "pikchr.c"
28912896
yymsp[0].minor.yy0 = yylhsminor.yy0;
28922897
break;
28932898
case 94: /* nth ::= NTH LB RB */
2894
-#line 731 "pikchr.y"
2899
+#line 736 "pikchr.y"
28952900
{yylhsminor.yy0=yymsp[-1].minor.yy0; yylhsminor.yy0.eCode = pik_nth_value(p,&yymsp[-2].minor.yy0);}
2896
-#line 2921 "pikchr.c"
2901
+#line 2926 "pikchr.c"
28972902
yymsp[-2].minor.yy0 = yylhsminor.yy0;
28982903
break;
28992904
case 95: /* nth ::= NTH LAST LB RB */
2900
-#line 732 "pikchr.y"
2905
+#line 737 "pikchr.y"
29012906
{yylhsminor.yy0=yymsp[-1].minor.yy0; yylhsminor.yy0.eCode = -pik_nth_value(p,&yymsp[-3].minor.yy0);}
2902
-#line 2927 "pikchr.c"
2907
+#line 2932 "pikchr.c"
29032908
yymsp[-3].minor.yy0 = yylhsminor.yy0;
29042909
break;
29052910
case 96: /* nth ::= LAST LB RB */
2906
-#line 733 "pikchr.y"
2911
+#line 738 "pikchr.y"
29072912
{yymsp[-2].minor.yy0=yymsp[-1].minor.yy0; yymsp[-2].minor.yy0.eCode = -1; }
2908
-#line 2933 "pikchr.c"
2913
+#line 2938 "pikchr.c"
29092914
break;
29102915
case 97: /* expr ::= expr PLUS expr */
2911
-#line 735 "pikchr.y"
2916
+#line 740 "pikchr.y"
29122917
{yylhsminor.yy265=yymsp[-2].minor.yy265+yymsp[0].minor.yy265;}
2913
-#line 2938 "pikchr.c"
2918
+#line 2943 "pikchr.c"
29142919
yymsp[-2].minor.yy265 = yylhsminor.yy265;
29152920
break;
29162921
case 98: /* expr ::= expr MINUS expr */
2917
-#line 736 "pikchr.y"
2922
+#line 741 "pikchr.y"
29182923
{yylhsminor.yy265=yymsp[-2].minor.yy265-yymsp[0].minor.yy265;}
2919
-#line 2944 "pikchr.c"
2924
+#line 2949 "pikchr.c"
29202925
yymsp[-2].minor.yy265 = yylhsminor.yy265;
29212926
break;
29222927
case 99: /* expr ::= expr STAR expr */
2923
-#line 737 "pikchr.y"
2928
+#line 742 "pikchr.y"
29242929
{yylhsminor.yy265=yymsp[-2].minor.yy265*yymsp[0].minor.yy265;}
2925
-#line 2950 "pikchr.c"
2930
+#line 2955 "pikchr.c"
29262931
yymsp[-2].minor.yy265 = yylhsminor.yy265;
29272932
break;
29282933
case 100: /* expr ::= expr SLASH expr */
2929
-#line 738 "pikchr.y"
2934
+#line 743 "pikchr.y"
29302935
{
29312936
if( yymsp[0].minor.yy265==0.0 ){ pik_error(p, &yymsp[-1].minor.yy0, "division by zero"); yylhsminor.yy265 = 0.0; }
29322937
else{ yylhsminor.yy265 = yymsp[-2].minor.yy265/yymsp[0].minor.yy265; }
29332938
}
2934
-#line 2959 "pikchr.c"
2939
+#line 2964 "pikchr.c"
29352940
yymsp[-2].minor.yy265 = yylhsminor.yy265;
29362941
break;
29372942
case 101: /* expr ::= MINUS expr */
2938
-#line 742 "pikchr.y"
2943
+#line 747 "pikchr.y"
29392944
{yymsp[-1].minor.yy265=-yymsp[0].minor.yy265;}
2940
-#line 2965 "pikchr.c"
2945
+#line 2970 "pikchr.c"
29412946
break;
29422947
case 102: /* expr ::= PLUS expr */
2943
-#line 743 "pikchr.y"
2948
+#line 748 "pikchr.y"
29442949
{yymsp[-1].minor.yy265=yymsp[0].minor.yy265;}
2945
-#line 2970 "pikchr.c"
2950
+#line 2975 "pikchr.c"
29462951
break;
29472952
case 103: /* expr ::= LP expr RP */
2948
-#line 744 "pikchr.y"
2953
+#line 749 "pikchr.y"
29492954
{yymsp[-2].minor.yy265=yymsp[-1].minor.yy265;}
2950
-#line 2975 "pikchr.c"
2955
+#line 2980 "pikchr.c"
29512956
break;
29522957
case 104: /* expr ::= LP FILL|COLOR|THICKNESS RP */
2953
-#line 745 "pikchr.y"
2958
+#line 750 "pikchr.y"
29542959
{yymsp[-2].minor.yy265=pik_get_var(p,&yymsp[-1].minor.yy0);}
2955
-#line 2980 "pikchr.c"
2960
+#line 2985 "pikchr.c"
29562961
break;
29572962
case 105: /* expr ::= NUMBER */
2958
-#line 746 "pikchr.y"
2963
+#line 751 "pikchr.y"
29592964
{yylhsminor.yy265=pik_atof(&yymsp[0].minor.yy0);}
2960
-#line 2985 "pikchr.c"
2965
+#line 2990 "pikchr.c"
29612966
yymsp[0].minor.yy265 = yylhsminor.yy265;
29622967
break;
29632968
case 106: /* expr ::= ID */
2964
-#line 747 "pikchr.y"
2969
+#line 752 "pikchr.y"
29652970
{yylhsminor.yy265=pik_get_var(p,&yymsp[0].minor.yy0);}
2966
-#line 2991 "pikchr.c"
2971
+#line 2996 "pikchr.c"
29672972
yymsp[0].minor.yy265 = yylhsminor.yy265;
29682973
break;
29692974
case 107: /* expr ::= FUNC1 LP expr RP */
2970
-#line 748 "pikchr.y"
2975
+#line 753 "pikchr.y"
29712976
{yylhsminor.yy265 = pik_func(p,&yymsp[-3].minor.yy0,yymsp[-1].minor.yy265,0.0);}
2972
-#line 2997 "pikchr.c"
2977
+#line 3002 "pikchr.c"
29732978
yymsp[-3].minor.yy265 = yylhsminor.yy265;
29742979
break;
29752980
case 108: /* expr ::= FUNC2 LP expr COMMA expr RP */
2976
-#line 749 "pikchr.y"
2981
+#line 754 "pikchr.y"
29772982
{yylhsminor.yy265 = pik_func(p,&yymsp[-5].minor.yy0,yymsp[-3].minor.yy265,yymsp[-1].minor.yy265);}
2978
-#line 3003 "pikchr.c"
2983
+#line 3008 "pikchr.c"
29792984
yymsp[-5].minor.yy265 = yylhsminor.yy265;
29802985
break;
29812986
case 109: /* expr ::= DIST LP position COMMA position RP */
2982
-#line 750 "pikchr.y"
2987
+#line 755 "pikchr.y"
29832988
{yymsp[-5].minor.yy265 = pik_dist(&yymsp[-3].minor.yy43,&yymsp[-1].minor.yy43);}
2984
-#line 3009 "pikchr.c"
2989
+#line 3014 "pikchr.c"
29852990
break;
29862991
case 110: /* expr ::= place2 DOT_XY X */
2987
-#line 751 "pikchr.y"
2992
+#line 756 "pikchr.y"
29882993
{yylhsminor.yy265 = yymsp[-2].minor.yy43.x;}
2989
-#line 3014 "pikchr.c"
2994
+#line 3019 "pikchr.c"
29902995
yymsp[-2].minor.yy265 = yylhsminor.yy265;
29912996
break;
29922997
case 111: /* expr ::= place2 DOT_XY Y */
2993
-#line 752 "pikchr.y"
2998
+#line 757 "pikchr.y"
29942999
{yylhsminor.yy265 = yymsp[-2].minor.yy43.y;}
2995
-#line 3020 "pikchr.c"
3000
+#line 3025 "pikchr.c"
29963001
yymsp[-2].minor.yy265 = yylhsminor.yy265;
29973002
break;
29983003
case 112: /* expr ::= object DOT_L numproperty */
29993004
case 113: /* expr ::= object DOT_L dashproperty */ yytestcase(yyruleno==113);
30003005
case 114: /* expr ::= object DOT_L colorproperty */ yytestcase(yyruleno==114);
3001
-#line 753 "pikchr.y"
3006
+#line 758 "pikchr.y"
30023007
{yylhsminor.yy265=pik_property_of(yymsp[-2].minor.yy38,&yymsp[0].minor.yy0);}
3003
-#line 3028 "pikchr.c"
3008
+#line 3033 "pikchr.c"
30043009
yymsp[-2].minor.yy265 = yylhsminor.yy265;
30053010
break;
30063011
default:
30073012
/* (115) lvalue ::= ID */ yytestcase(yyruleno==115);
30083013
/* (116) lvalue ::= FILL */ yytestcase(yyruleno==116);
@@ -3101,19 +3106,19 @@
31013106
){
31023107
pik_parserARG_FETCH
31033108
pik_parserCTX_FETCH
31043109
#define TOKEN yyminor
31053110
/************ Begin %syntax_error code ****************************************/
3106
-#line 514 "pikchr.y"
3111
+#line 519 "pikchr.y"
31073112
31083113
if( TOKEN.z && TOKEN.z[0] ){
31093114
pik_error(p, &TOKEN, "syntax error");
31103115
}else{
31113116
pik_error(p, 0, "syntax error");
31123117
}
31133118
UNUSED_PARAMETER(yymajor);
3114
-#line 3139 "pikchr.c"
3119
+#line 3144 "pikchr.c"
31153120
/************ End %syntax_error code ******************************************/
31163121
pik_parserARG_STORE /* Suppress warning about unused %extra_argument variable */
31173122
pik_parserCTX_STORE
31183123
}
31193124
@@ -3342,16 +3347,17 @@
33423347
#else
33433348
(void)iToken;
33443349
return 0;
33453350
#endif
33463351
}
3347
-#line 758 "pikchr.y"
3352
+#line 763 "pikchr.y"
33483353
33493354
33503355
3351
-/* Chart of the 140 official HTML color names with their
3352
-** corresponding RGB value.
3356
+/* Chart of the 148 official CSS color names with their
3357
+** corresponding RGB values thru Color Module Level 4:
3358
+** https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
33533359
**
33543360
** Two new names "None" and "Off" are added with a value
33553361
** of -1.
33563362
*/
33573363
static const struct {
@@ -3359,11 +3365,11 @@
33593365
int val; /* RGB value */
33603366
} aColor[] = {
33613367
{ "AliceBlue", 0xf0f8ff },
33623368
{ "AntiqueWhite", 0xfaebd7 },
33633369
{ "Aqua", 0x00ffff },
3364
- { "AquaMarine", 0x7fffd4 },
3370
+ { "Aquamarine", 0x7fffd4 },
33653371
{ "Azure", 0xf0ffff },
33663372
{ "Beige", 0xf5f5dc },
33673373
{ "Bisque", 0xffe4c4 },
33683374
{ "Black", 0x000000 },
33693375
{ "BlanchedAlmond", 0xffebcd },
@@ -3373,19 +3379,20 @@
33733379
{ "BurlyWood", 0xdeb887 },
33743380
{ "CadetBlue", 0x5f9ea0 },
33753381
{ "Chartreuse", 0x7fff00 },
33763382
{ "Chocolate", 0xd2691e },
33773383
{ "Coral", 0xff7f50 },
3378
- { "CornFlowerBlue", 0x6495ed },
3384
+ { "CornflowerBlue", 0x6495ed },
33793385
{ "Cornsilk", 0xfff8dc },
33803386
{ "Crimson", 0xdc143c },
33813387
{ "Cyan", 0x00ffff },
33823388
{ "DarkBlue", 0x00008b },
33833389
{ "DarkCyan", 0x008b8b },
3384
- { "DarkGoldenRod", 0xb8860b },
3390
+ { "DarkGoldenrod", 0xb8860b },
33853391
{ "DarkGray", 0xa9a9a9 },
33863392
{ "DarkGreen", 0x006400 },
3393
+ { "DarkGrey", 0xa9a9a9 },
33873394
{ "DarkKhaki", 0xbdb76b },
33883395
{ "DarkMagenta", 0x8b008b },
33893396
{ "DarkOliveGreen", 0x556b2f },
33903397
{ "DarkOrange", 0xff8c00 },
33913398
{ "DarkOrchid", 0x9932cc },
@@ -3392,28 +3399,31 @@
33923399
{ "DarkRed", 0x8b0000 },
33933400
{ "DarkSalmon", 0xe9967a },
33943401
{ "DarkSeaGreen", 0x8fbc8f },
33953402
{ "DarkSlateBlue", 0x483d8b },
33963403
{ "DarkSlateGray", 0x2f4f4f },
3404
+ { "DarkSlateGrey", 0x2f4f4f },
33973405
{ "DarkTurquoise", 0x00ced1 },
33983406
{ "DarkViolet", 0x9400d3 },
33993407
{ "DeepPink", 0xff1493 },
34003408
{ "DeepSkyBlue", 0x00bfff },
34013409
{ "DimGray", 0x696969 },
3410
+ { "DimGrey", 0x696969 },
34023411
{ "DodgerBlue", 0x1e90ff },
3403
- { "FireBrick", 0xb22222 },
3412
+ { "Firebrick", 0xb22222 },
34043413
{ "FloralWhite", 0xfffaf0 },
34053414
{ "ForestGreen", 0x228b22 },
34063415
{ "Fuchsia", 0xff00ff },
34073416
{ "Gainsboro", 0xdcdcdc },
34083417
{ "GhostWhite", 0xf8f8ff },
34093418
{ "Gold", 0xffd700 },
3410
- { "GoldenRod", 0xdaa520 },
3419
+ { "Goldenrod", 0xdaa520 },
34113420
{ "Gray", 0x808080 },
34123421
{ "Green", 0x008000 },
34133422
{ "GreenYellow", 0xadff2f },
3414
- { "HoneyDew", 0xf0fff0 },
3423
+ { "Grey", 0x808080 },
3424
+ { "Honeydew", 0xf0fff0 },
34153425
{ "HotPink", 0xff69b4 },
34163426
{ "IndianRed", 0xcd5c5c },
34173427
{ "Indigo", 0x4b0082 },
34183428
{ "Ivory", 0xfffff0 },
34193429
{ "Khaki", 0xf0e68c },
@@ -3425,26 +3435,28 @@
34253435
{ "LightCoral", 0xf08080 },
34263436
{ "LightCyan", 0xe0ffff },
34273437
{ "LightGoldenrodYellow", 0xfafad2 },
34283438
{ "LightGray", 0xd3d3d3 },
34293439
{ "LightGreen", 0x90ee90 },
3440
+ { "LightGrey", 0xd3d3d3 },
34303441
{ "LightPink", 0xffb6c1 },
34313442
{ "LightSalmon", 0xffa07a },
34323443
{ "LightSeaGreen", 0x20b2aa },
34333444
{ "LightSkyBlue", 0x87cefa },
34343445
{ "LightSlateGray", 0x778899 },
3446
+ { "LightSlateGrey", 0x778899 },
34353447
{ "LightSteelBlue", 0xb0c4de },
34363448
{ "LightYellow", 0xffffe0 },
34373449
{ "Lime", 0x00ff00 },
34383450
{ "LimeGreen", 0x32cd32 },
34393451
{ "Linen", 0xfaf0e6 },
34403452
{ "Magenta", 0xff00ff },
34413453
{ "Maroon", 0x800000 },
3442
- { "MediumAquaMarine", 0x66cdaa },
3454
+ { "MediumAquamarine", 0x66cdaa },
34433455
{ "MediumBlue", 0x0000cd },
34443456
{ "MediumOrchid", 0xba55d3 },
3445
- { "MediumPurple", 0x9370d8 },
3457
+ { "MediumPurple", 0x9370db },
34463458
{ "MediumSeaGreen", 0x3cb371 },
34473459
{ "MediumSlateBlue", 0x7b68ee },
34483460
{ "MediumSpringGreen", 0x00fa9a },
34493461
{ "MediumTurquoise", 0x48d1cc },
34503462
{ "MediumVioletRed", 0xc71585 },
@@ -3460,11 +3472,11 @@
34603472
{ "Olive", 0x808000 },
34613473
{ "OliveDrab", 0x6b8e23 },
34623474
{ "Orange", 0xffa500 },
34633475
{ "OrangeRed", 0xff4500 },
34643476
{ "Orchid", 0xda70d6 },
3465
- { "PaleGoldenRod", 0xeee8aa },
3477
+ { "PaleGoldenrod", 0xeee8aa },
34663478
{ "PaleGreen", 0x98fb98 },
34673479
{ "PaleTurquoise", 0xafeeee },
34683480
{ "PaleVioletRed", 0xdb7093 },
34693481
{ "PapayaWhip", 0xffefd5 },
34703482
{ "PeachPuff", 0xffdab9 },
@@ -3471,23 +3483,25 @@
34713483
{ "Peru", 0xcd853f },
34723484
{ "Pink", 0xffc0cb },
34733485
{ "Plum", 0xdda0dd },
34743486
{ "PowderBlue", 0xb0e0e6 },
34753487
{ "Purple", 0x800080 },
3488
+ { "RebeccaPurple", 0x663399 },
34763489
{ "Red", 0xff0000 },
34773490
{ "RosyBrown", 0xbc8f8f },
34783491
{ "RoyalBlue", 0x4169e1 },
34793492
{ "SaddleBrown", 0x8b4513 },
34803493
{ "Salmon", 0xfa8072 },
34813494
{ "SandyBrown", 0xf4a460 },
34823495
{ "SeaGreen", 0x2e8b57 },
3483
- { "SeaShell", 0xfff5ee },
3496
+ { "Seashell", 0xfff5ee },
34843497
{ "Sienna", 0xa0522d },
34853498
{ "Silver", 0xc0c0c0 },
34863499
{ "SkyBlue", 0x87ceeb },
34873500
{ "SlateBlue", 0x6a5acd },
34883501
{ "SlateGray", 0x708090 },
3502
+ { "SlateGrey", 0x708090 },
34893503
{ "Snow", 0xfffafa },
34903504
{ "SpringGreen", 0x00ff7f },
34913505
{ "SteelBlue", 0x4682b4 },
34923506
{ "Tan", 0xd2b48c },
34933507
{ "Teal", 0x008080 },
@@ -4445,11 +4459,11 @@
44454459
bx = f->x + e1*dx;
44464460
by = f->y + e1*dy;
44474461
pik_append_xy(p,"<polygon points=\"", t->x, t->y);
44484462
pik_append_xy(p," ",bx-ddx, by-ddy);
44494463
pik_append_xy(p," ",bx+ddx, by+ddy);
4450
- pik_append_clr(p,"\" style=\"fill:",pObj->color,"\"/>\n");
4464
+ pik_append_clr(p,"\" style=\"fill:",pObj->color,"\"/>\n",0);
44514465
pik_chop(f,t,h/2);
44524466
}
44534467
44544468
/*
44554469
** Compute the relative offset to an edge location from the reference for a
@@ -4552,10 +4566,45 @@
45524566
(double)pPt->x, (double)pPt->y);
45534567
buf[sizeof(buf)-1] = 0;
45544568
pik_append(p, z, -1);
45554569
pik_append(p, buf, -1);
45564570
}
4571
+
4572
+/*
4573
+** Invert the RGB color so that it is appropriate for dark mode.
4574
+*/
4575
+static int pik_color_to_dark_mode(int x, int isBg){
4576
+ int r, g, b;
4577
+ int mn, mx;
4578
+ x = 0xffffff - x;
4579
+ r = (x>>16) & 0xff;
4580
+ g = (x>>8) & 0xff;
4581
+ b = x & 0xff;
4582
+ mx = r;
4583
+ if( g>mx ) mx = g;
4584
+ if( b>mx ) mx = b;
4585
+ mn = r;
4586
+ if( g<mn ) mn = g;
4587
+ if( b<mn ) mn = b;
4588
+ r = mn + (mx-r);
4589
+ g = mn + (mx-g);
4590
+ b = mn + (mx-b);
4591
+ if( isBg ){
4592
+ if( mx>127 ){
4593
+ r = (127*r)/mx;
4594
+ g = (127*g)/mx;
4595
+ b = (127*b)/mx;
4596
+ }
4597
+ }else{
4598
+ if( mn<128 && mx>mn ){
4599
+ r = 127 + ((r-mn)*128)/(mx-mn);
4600
+ g = 127 + ((g-mn)*128)/(mx-mn);
4601
+ b = 127 + ((b-mn)*128)/(mx-mn);
4602
+ }
4603
+ }
4604
+ return r*0x10000 + g*0x100 + b;
4605
+}
45574606
45584607
/* Append a PNum value surrounded by text. Do coordinate transformations
45594608
** on the value.
45604609
*/
45614610
static void pik_append_x(Pik *p, const char *z1, PNum v, const char *z2){
@@ -4585,16 +4634,22 @@
45854634
char buf[200];
45864635
snprintf(buf, sizeof(buf)-1, "%s%g%s", z1, p->rScale*v, z2);
45874636
buf[sizeof(buf)-1] = 0;
45884637
pik_append(p, buf, -1);
45894638
}
4590
-static void pik_append_clr(Pik *p, const char *z1, PNum v, const char *z2){
4639
+static void pik_append_clr(Pik *p,const char *z1,PNum v,const char *z2,int bg){
45914640
char buf[200];
45924641
int x = (int)v;
4593
- int r = (x>>16) & 0xff;
4594
- int g = (x>>8) & 0xff;
4595
- int b = x & 0xff;
4642
+ int r, g, b;
4643
+ if( x==0 && p->fgcolor>0 && !bg ){
4644
+ x = p->fgcolor;
4645
+ }else if( p->mFlags & PIKCHR_DARK_MODE ){
4646
+ x = pik_color_to_dark_mode(x,bg);
4647
+ }
4648
+ r = (x>>16) & 0xff;
4649
+ g = (x>>8) & 0xff;
4650
+ b = x & 0xff;
45964651
snprintf(buf, sizeof(buf)-1, "%srgb(%d,%d,%d)%s", z1, r, g, b, z2);
45974652
buf[sizeof(buf)-1] = 0;
45984653
pik_append(p, buf, -1);
45994654
}
46004655
@@ -4617,21 +4672,21 @@
46174672
** the caller wants to add some more.
46184673
*/
46194674
static void pik_append_style(Pik *p, PObj *pObj, int bFill){
46204675
pik_append(p, " style=\"", -1);
46214676
if( pObj->fill>=0 && bFill ){
4622
- pik_append_clr(p, "fill:", pObj->fill, ";");
4677
+ pik_append_clr(p, "fill:", pObj->fill, ";",1);
46234678
}else{
46244679
pik_append(p,"fill:none;",-1);
46254680
}
46264681
if( pObj->sw>0.0 && pObj->color>=0.0 ){
46274682
PNum sw = pObj->sw;
46284683
pik_append_dis(p, "stroke-width:", sw, ";");
46294684
if( pObj->nPath>2 && pObj->rad<=pObj->sw ){
46304685
pik_append(p, "stroke-linejoin:round;", -1);
46314686
}
4632
- pik_append_clr(p, "stroke:",pObj->color,";");
4687
+ pik_append_clr(p, "stroke:",pObj->color,";",0);
46334688
if( pObj->dotted>0.0 ){
46344689
PNum v = pObj->dotted;
46354690
if( sw<2.1/p->rScale ) sw = 2.1/p->rScale;
46364691
pik_append_dis(p,"stroke-dasharray:",sw,"");
46374692
pik_append_dis(p,",",v,";");
@@ -4882,11 +4937,11 @@
48824937
}
48834938
if( t->eCode & TP_BOLD ){
48844939
pik_append(p, " font-weight=\"bold\"", -1);
48854940
}
48864941
if( pObj->color>=0.0 ){
4887
- pik_append_clr(p, " fill=\"", pObj->color, "\"");
4942
+ pik_append_clr(p, " fill=\"", pObj->color, "\"",0);
48884943
}
48894944
xtraFontScale *= p->fontScale;
48904945
if( xtraFontScale<=0.99 || xtraFontScale>=1.01 ){
48914946
pik_append_num(p, " font-size=\"", xtraFontScale*100.0);
48924947
pik_append(p, "%\"", 2);
@@ -6827,18 +6882,26 @@
68276882
PNum thickness; /* Stroke width */
68286883
PNum margin; /* Extra bounding box margin */
68296884
PNum w, h; /* Drawing width and height */
68306885
PNum wArrow;
68316886
PNum pikScale; /* Value of the "scale" variable */
6887
+ int miss = 0;
68326888
68336889
/* Set up rendering parameters */
68346890
pik_compute_layout_settings(p);
68356891
thickness = pik_value(p,"thickness",9,0);
68366892
if( thickness<=0.01 ) thickness = 0.01;
68376893
margin = pik_value(p,"margin",6,0);
68386894
margin += thickness;
68396895
wArrow = p->wArrow*thickness;
6896
+ p->fgcolor = (int)pik_value(p,"fgcolor",7,&miss);
6897
+ if( miss ){
6898
+ PToken t;
6899
+ t.z = "fgcolor";
6900
+ t.n = 7;
6901
+ p->fgcolor = (int)pik_lookup_color(0, &t);
6902
+ }
68406903
68416904
/* Compute a bounding box over all objects so that we can know
68426905
** how big to declare the SVG canvas */
68436906
pik_bbox_init(&p->bbox);
68446907
pik_bbox_add_elist(p, pList, wArrow);
@@ -7620,10 +7683,11 @@
76207683
int i;
76217684
int bSvgOnly = 0; /* Output SVG only. No HTML wrapper */
76227685
int bDontStop = 0; /* Continue in spite of errors */
76237686
int exitCode = 0; /* What to return */
76247687
int mFlags = 0; /* mFlags argument to pikchr() */
7688
+ const char *zStyle = ""; /* Extra styling */
76257689
const char *zHtmlHdr =
76267690
"<!DOCTYPE html>\n"
76277691
"<html lang=\"en-US\">\n"
76287692
"<head>\n<title>PIKCHR Test</title>\n"
76297693
"<style>\n"
@@ -7657,10 +7721,14 @@
76577721
char *z = argv[i];
76587722
z++;
76597723
if( z[0]=='-' ) z++;
76607724
if( strcmp(z,"dont-stop")==0 ){
76617725
bDontStop = 1;
7726
+ }else
7727
+ if( strcmp(z,"dark-mode")==0 ){
7728
+ zStyle = "color:white;background-color:black;";
7729
+ mFlags |= PIKCHR_DARK_MODE;
76627730
}else
76637731
if( strcmp(z,"svg-only")==0 ){
76647732
if( zHtmlHdr==0 ){
76657733
fprintf(stderr, "the \"%s\" option must come first\n",argv[i]);
76667734
exit(1);
@@ -7706,11 +7774,12 @@
77067774
printf("<h1>File %s</h1>\n", argv[i]);
77077775
if( w<0 ){
77087776
printf("<p>ERROR</p>\n%s\n", zOut);
77097777
}else{
77107778
printf("<div id=\"svg-%d\" onclick=\"toggleHidden('svg-%d')\">\n",i,i);
7711
- printf("<div style='border:3px solid lightgray;max-width:%dpx;'>\n",w);
7779
+ printf("<div style='border:3px solid lightgray;max-width:%dpx;%s'>\n",
7780
+ w,zStyle);
77127781
printf("%s</div>\n", zOut);
77137782
printf("<pre class='hidden'>");
77147783
print_escape_html(zIn);
77157784
printf("</pre>\n</div>\n");
77167785
}
@@ -7783,6 +7852,6 @@
77837852
77847853
77857854
#endif /* PIKCHR_TCL */
77867855
77877856
7788
-#line 7813 "pikchr.c"
7857
+#line 7882 "pikchr.c"
77897858
--- src/pikchr.c
+++ src/pikchr.c
@@ -363,10 +363,11 @@
363 char bLayoutVars; /* True if cache is valid */
364 char thenFlag; /* True if "then" seen */
365 char samePath; /* aTPath copied by "same" */
366 const char *zClass; /* Class name for the <svg> */
367 int wSVG, hSVG; /* Width and height of the <svg> */
 
368 /* Paths for lines are constructed here first, then transferred into
369 ** the PObj object at the end: */
370 int nTPath; /* Number of entries on aTPath[] */
371 int mTPath; /* For last entry, 1: x set, 2: y set */
372 PPoint aTPath[1000]; /* Path under construction */
@@ -378,10 +379,14 @@
378 /* Include PIKCHR_PLAINTEXT_ERRORS among the bits of mFlags on the 3rd
379 ** argument to pikchr() in order to cause error message text to come out
380 ** as text/plain instead of as text/html
381 */
382 #define PIKCHR_PLAINTEXT_ERRORS 0x0001
 
 
 
 
383
384 /*
385 ** The behavior of an object class is defined by an instance of
386 ** this structure. This is the "virtual method" table.
387 */
@@ -407,11 +412,11 @@
407 static void pik_append_x(Pik*,const char*,PNum,const char*);
408 static void pik_append_y(Pik*,const char*,PNum,const char*);
409 static void pik_append_xy(Pik*,const char*,PNum,PNum);
410 static void pik_append_dis(Pik*,const char*,PNum,const char*);
411 static void pik_append_arc(Pik*,PNum,PNum,PNum,PNum);
412 static void pik_append_clr(Pik*,const char*,PNum,const char*);
413 static void pik_append_style(Pik*,PObj*,int);
414 static void pik_append_txt(Pik*,PObj*, PBox*);
415 static void pik_draw_arrowhead(Pik*,PPoint*pFrom,PPoint*pTo,PObj*);
416 static void pik_chop(PPoint*pFrom,PPoint*pTo,PNum);
417 static void pik_error(Pik*,PToken*,const char*);
@@ -468,11 +473,11 @@
468 static PObj *pik_position_assert(Pik*,PPoint*,PToken*,PPoint*);
469 static PNum pik_dist(PPoint*,PPoint*);
470 static void pik_add_macro(Pik*,PToken *pId,PToken *pCode);
471
472
473 #line 499 "pikchr.c"
474 /**************** End of %include directives **********************************/
475 /* These constants specify the various numeric values for terminal symbols.
476 ***************** Begin token definitions *************************************/
477 #ifndef T_ID
478 #define T_ID 1
@@ -1685,22 +1690,22 @@
1685 ** inside the C code.
1686 */
1687 /********* Begin destructor definitions ***************************************/
1688 case 98: /* statement_list */
1689 {
1690 #line 488 "pikchr.y"
1691 pik_elist_free(p,(yypminor->yy119));
1692 #line 1717 "pikchr.c"
1693 }
1694 break;
1695 case 99: /* statement */
1696 case 100: /* unnamed_statement */
1697 case 101: /* basetype */
1698 {
1699 #line 490 "pikchr.y"
1700 pik_elem_free(p,(yypminor->yy38));
1701 #line 1726 "pikchr.c"
1702 }
1703 break;
1704 /********* End destructor definitions *****************************************/
1705 default: break; /* If no destructor action specified: do nothing */
1706 }
@@ -1914,14 +1919,14 @@
1914 #endif
1915 while( yypParser->yytos>yypParser->yystack ) yy_pop_parser_stack(yypParser);
1916 /* Here code is inserted which will execute if the parser
1917 ** stack every overflows */
1918 /******** Begin %stack_overflow code ******************************************/
1919 #line 522 "pikchr.y"
1920
1921 pik_error(p, 0, "parser stack overflow");
1922 #line 1947 "pikchr.c"
1923 /******** End %stack_overflow code ********************************************/
1924 pik_parserARG_STORE /* Suppress warning about unused %extra_argument var */
1925 pik_parserCTX_STORE
1926 }
1927
@@ -2395,614 +2400,614 @@
2395 ** break;
2396 */
2397 /********** Begin reduce actions **********************************************/
2398 YYMINORTYPE yylhsminor;
2399 case 0: /* document ::= statement_list */
2400 #line 526 "pikchr.y"
2401 {pik_render(p,yymsp[0].minor.yy119);}
2402 #line 2427 "pikchr.c"
2403 break;
2404 case 1: /* statement_list ::= statement */
2405 #line 529 "pikchr.y"
2406 { yylhsminor.yy119 = pik_elist_append(p,0,yymsp[0].minor.yy38); }
2407 #line 2432 "pikchr.c"
2408 yymsp[0].minor.yy119 = yylhsminor.yy119;
2409 break;
2410 case 2: /* statement_list ::= statement_list EOL statement */
2411 #line 531 "pikchr.y"
2412 { yylhsminor.yy119 = pik_elist_append(p,yymsp[-2].minor.yy119,yymsp[0].minor.yy38); }
2413 #line 2438 "pikchr.c"
2414 yymsp[-2].minor.yy119 = yylhsminor.yy119;
2415 break;
2416 case 3: /* statement ::= */
2417 #line 534 "pikchr.y"
2418 { yymsp[1].minor.yy38 = 0; }
2419 #line 2444 "pikchr.c"
2420 break;
2421 case 4: /* statement ::= direction */
2422 #line 535 "pikchr.y"
2423 { pik_set_direction(p,yymsp[0].minor.yy0.eCode); yylhsminor.yy38=0; }
2424 #line 2449 "pikchr.c"
2425 yymsp[0].minor.yy38 = yylhsminor.yy38;
2426 break;
2427 case 5: /* statement ::= lvalue ASSIGN rvalue */
2428 #line 536 "pikchr.y"
2429 {pik_set_var(p,&yymsp[-2].minor.yy0,yymsp[0].minor.yy265,&yymsp[-1].minor.yy0); yylhsminor.yy38=0;}
2430 #line 2455 "pikchr.c"
2431 yymsp[-2].minor.yy38 = yylhsminor.yy38;
2432 break;
2433 case 6: /* statement ::= PLACENAME COLON unnamed_statement */
2434 #line 538 "pikchr.y"
2435 { yylhsminor.yy38 = yymsp[0].minor.yy38; pik_elem_setname(p,yymsp[0].minor.yy38,&yymsp[-2].minor.yy0); }
2436 #line 2461 "pikchr.c"
2437 yymsp[-2].minor.yy38 = yylhsminor.yy38;
2438 break;
2439 case 7: /* statement ::= PLACENAME COLON position */
2440 #line 540 "pikchr.y"
2441 { yylhsminor.yy38 = pik_elem_new(p,0,0,0);
2442 if(yylhsminor.yy38){ yylhsminor.yy38->ptAt = yymsp[0].minor.yy43; pik_elem_setname(p,yylhsminor.yy38,&yymsp[-2].minor.yy0); }}
2443 #line 2468 "pikchr.c"
2444 yymsp[-2].minor.yy38 = yylhsminor.yy38;
2445 break;
2446 case 8: /* statement ::= unnamed_statement */
2447 #line 542 "pikchr.y"
2448 {yylhsminor.yy38 = yymsp[0].minor.yy38;}
2449 #line 2474 "pikchr.c"
2450 yymsp[0].minor.yy38 = yylhsminor.yy38;
2451 break;
2452 case 9: /* statement ::= print prlist */
2453 #line 543 "pikchr.y"
2454 {pik_append(p,"<br>\n",5); yymsp[-1].minor.yy38=0;}
2455 #line 2480 "pikchr.c"
2456 break;
2457 case 10: /* statement ::= ASSERT LP expr EQ expr RP */
2458 #line 548 "pikchr.y"
2459 {yymsp[-5].minor.yy38=pik_assert(p,yymsp[-3].minor.yy265,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy265);}
2460 #line 2485 "pikchr.c"
2461 break;
2462 case 11: /* statement ::= ASSERT LP position EQ position RP */
2463 #line 550 "pikchr.y"
2464 {yymsp[-5].minor.yy38=pik_position_assert(p,&yymsp[-3].minor.yy43,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy43);}
2465 #line 2490 "pikchr.c"
2466 break;
2467 case 12: /* statement ::= DEFINE ID CODEBLOCK */
2468 #line 551 "pikchr.y"
2469 {yymsp[-2].minor.yy38=0; pik_add_macro(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0);}
2470 #line 2495 "pikchr.c"
2471 break;
2472 case 13: /* rvalue ::= PLACENAME */
2473 #line 562 "pikchr.y"
2474 {yylhsminor.yy265 = pik_lookup_color(p,&yymsp[0].minor.yy0);}
2475 #line 2500 "pikchr.c"
2476 yymsp[0].minor.yy265 = yylhsminor.yy265;
2477 break;
2478 case 14: /* pritem ::= FILL */
2479 case 15: /* pritem ::= COLOR */ yytestcase(yyruleno==15);
2480 case 16: /* pritem ::= THICKNESS */ yytestcase(yyruleno==16);
2481 #line 567 "pikchr.y"
2482 {pik_append_num(p,"",pik_value(p,yymsp[0].minor.yy0.z,yymsp[0].minor.yy0.n,0));}
2483 #line 2508 "pikchr.c"
2484 break;
2485 case 17: /* pritem ::= rvalue */
2486 #line 570 "pikchr.y"
2487 {pik_append_num(p,"",yymsp[0].minor.yy265);}
2488 #line 2513 "pikchr.c"
2489 break;
2490 case 18: /* pritem ::= STRING */
2491 #line 571 "pikchr.y"
2492 {pik_append_text(p,yymsp[0].minor.yy0.z+1,yymsp[0].minor.yy0.n-2,0);}
2493 #line 2518 "pikchr.c"
2494 break;
2495 case 19: /* prsep ::= COMMA */
2496 #line 572 "pikchr.y"
2497 {pik_append(p, " ", 1);}
2498 #line 2523 "pikchr.c"
2499 break;
2500 case 20: /* unnamed_statement ::= basetype attribute_list */
2501 #line 575 "pikchr.y"
2502 {yylhsminor.yy38 = yymsp[-1].minor.yy38; pik_after_adding_attributes(p,yylhsminor.yy38);}
2503 #line 2528 "pikchr.c"
2504 yymsp[-1].minor.yy38 = yylhsminor.yy38;
2505 break;
2506 case 21: /* basetype ::= CLASSNAME */
2507 #line 577 "pikchr.y"
2508 {yylhsminor.yy38 = pik_elem_new(p,&yymsp[0].minor.yy0,0,0); }
2509 #line 2534 "pikchr.c"
2510 yymsp[0].minor.yy38 = yylhsminor.yy38;
2511 break;
2512 case 22: /* basetype ::= STRING textposition */
2513 #line 579 "pikchr.y"
2514 {yymsp[-1].minor.yy0.eCode = yymsp[0].minor.yy196; yylhsminor.yy38 = pik_elem_new(p,0,&yymsp[-1].minor.yy0,0); }
2515 #line 2540 "pikchr.c"
2516 yymsp[-1].minor.yy38 = yylhsminor.yy38;
2517 break;
2518 case 23: /* basetype ::= LB savelist statement_list RB */
2519 #line 581 "pikchr.y"
2520 { p->list = yymsp[-2].minor.yy119; yymsp[-3].minor.yy38 = pik_elem_new(p,0,0,yymsp[-1].minor.yy119); if(yymsp[-3].minor.yy38) yymsp[-3].minor.yy38->errTok = yymsp[0].minor.yy0; }
2521 #line 2546 "pikchr.c"
2522 break;
2523 case 24: /* savelist ::= */
2524 #line 586 "pikchr.y"
2525 {yymsp[1].minor.yy119 = p->list; p->list = 0;}
2526 #line 2551 "pikchr.c"
2527 break;
2528 case 25: /* relexpr ::= expr */
2529 #line 593 "pikchr.y"
2530 {yylhsminor.yy200.rAbs = yymsp[0].minor.yy265; yylhsminor.yy200.rRel = 0;}
2531 #line 2556 "pikchr.c"
2532 yymsp[0].minor.yy200 = yylhsminor.yy200;
2533 break;
2534 case 26: /* relexpr ::= expr PERCENT */
2535 #line 594 "pikchr.y"
2536 {yylhsminor.yy200.rAbs = 0; yylhsminor.yy200.rRel = yymsp[-1].minor.yy265/100;}
2537 #line 2562 "pikchr.c"
2538 yymsp[-1].minor.yy200 = yylhsminor.yy200;
2539 break;
2540 case 27: /* optrelexpr ::= */
2541 #line 596 "pikchr.y"
2542 {yymsp[1].minor.yy200.rAbs = 0; yymsp[1].minor.yy200.rRel = 1.0;}
2543 #line 2568 "pikchr.c"
2544 break;
2545 case 28: /* attribute_list ::= relexpr alist */
2546 #line 598 "pikchr.y"
2547 {pik_add_direction(p,0,&yymsp[-1].minor.yy200);}
2548 #line 2573 "pikchr.c"
2549 break;
2550 case 29: /* attribute ::= numproperty relexpr */
2551 #line 602 "pikchr.y"
2552 { pik_set_numprop(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy200); }
2553 #line 2578 "pikchr.c"
2554 break;
2555 case 30: /* attribute ::= dashproperty expr */
2556 #line 603 "pikchr.y"
2557 { pik_set_dashed(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy265); }
2558 #line 2583 "pikchr.c"
2559 break;
2560 case 31: /* attribute ::= dashproperty */
2561 #line 604 "pikchr.y"
2562 { pik_set_dashed(p,&yymsp[0].minor.yy0,0); }
2563 #line 2588 "pikchr.c"
2564 break;
2565 case 32: /* attribute ::= colorproperty rvalue */
2566 #line 605 "pikchr.y"
2567 { pik_set_clrprop(p,&yymsp[-1].minor.yy0,yymsp[0].minor.yy265); }
2568 #line 2593 "pikchr.c"
2569 break;
2570 case 33: /* attribute ::= go direction optrelexpr */
2571 #line 606 "pikchr.y"
2572 { pik_add_direction(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy200);}
2573 #line 2598 "pikchr.c"
2574 break;
2575 case 34: /* attribute ::= go direction even position */
2576 #line 607 "pikchr.y"
2577 {pik_evenwith(p,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy43);}
2578 #line 2603 "pikchr.c"
2579 break;
2580 case 35: /* attribute ::= CLOSE */
2581 #line 608 "pikchr.y"
2582 { pik_close_path(p,&yymsp[0].minor.yy0); }
2583 #line 2608 "pikchr.c"
2584 break;
2585 case 36: /* attribute ::= CHOP */
2586 #line 609 "pikchr.y"
2587 { p->cur->bChop = 1; }
2588 #line 2613 "pikchr.c"
2589 break;
2590 case 37: /* attribute ::= FROM position */
2591 #line 610 "pikchr.y"
2592 { pik_set_from(p,p->cur,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy43); }
2593 #line 2618 "pikchr.c"
2594 break;
2595 case 38: /* attribute ::= TO position */
2596 #line 611 "pikchr.y"
2597 { pik_add_to(p,p->cur,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy43); }
2598 #line 2623 "pikchr.c"
2599 break;
2600 case 39: /* attribute ::= THEN */
2601 #line 612 "pikchr.y"
2602 { pik_then(p, &yymsp[0].minor.yy0, p->cur); }
2603 #line 2628 "pikchr.c"
2604 break;
2605 case 40: /* attribute ::= THEN optrelexpr HEADING expr */
2606 case 42: /* attribute ::= GO optrelexpr HEADING expr */ yytestcase(yyruleno==42);
2607 #line 614 "pikchr.y"
2608 {pik_move_hdg(p,&yymsp[-2].minor.yy200,&yymsp[-1].minor.yy0,yymsp[0].minor.yy265,0,&yymsp[-3].minor.yy0);}
2609 #line 2634 "pikchr.c"
2610 break;
2611 case 41: /* attribute ::= THEN optrelexpr EDGEPT */
2612 case 43: /* attribute ::= GO optrelexpr EDGEPT */ yytestcase(yyruleno==43);
2613 #line 615 "pikchr.y"
2614 {pik_move_hdg(p,&yymsp[-1].minor.yy200,0,0,&yymsp[0].minor.yy0,&yymsp[-2].minor.yy0);}
2615 #line 2640 "pikchr.c"
2616 break;
2617 case 44: /* attribute ::= AT position */
2618 #line 620 "pikchr.y"
2619 { pik_set_at(p,0,&yymsp[0].minor.yy43,&yymsp[-1].minor.yy0); }
2620 #line 2645 "pikchr.c"
2621 break;
2622 case 45: /* attribute ::= SAME */
2623 #line 622 "pikchr.y"
2624 {pik_same(p,0,&yymsp[0].minor.yy0);}
2625 #line 2650 "pikchr.c"
2626 break;
2627 case 46: /* attribute ::= SAME AS object */
2628 #line 623 "pikchr.y"
2629 {pik_same(p,yymsp[0].minor.yy38,&yymsp[-2].minor.yy0);}
2630 #line 2655 "pikchr.c"
2631 break;
2632 case 47: /* attribute ::= STRING textposition */
2633 #line 624 "pikchr.y"
2634 {pik_add_txt(p,&yymsp[-1].minor.yy0,yymsp[0].minor.yy196);}
2635 #line 2660 "pikchr.c"
2636 break;
2637 case 48: /* attribute ::= FIT */
2638 #line 625 "pikchr.y"
2639 {pik_size_to_fit(p,&yymsp[0].minor.yy0,3); }
2640 #line 2665 "pikchr.c"
2641 break;
2642 case 49: /* attribute ::= BEHIND object */
2643 #line 626 "pikchr.y"
2644 {pik_behind(p,yymsp[0].minor.yy38);}
2645 #line 2670 "pikchr.c"
2646 break;
2647 case 50: /* withclause ::= DOT_E edge AT position */
2648 case 51: /* withclause ::= edge AT position */ yytestcase(yyruleno==51);
2649 #line 634 "pikchr.y"
2650 { pik_set_at(p,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy43,&yymsp[-1].minor.yy0); }
2651 #line 2676 "pikchr.c"
2652 break;
2653 case 52: /* numproperty ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS */
2654 #line 638 "pikchr.y"
2655 {yylhsminor.yy0 = yymsp[0].minor.yy0;}
2656 #line 2681 "pikchr.c"
2657 yymsp[0].minor.yy0 = yylhsminor.yy0;
2658 break;
2659 case 53: /* boolproperty ::= CW */
2660 #line 649 "pikchr.y"
2661 {p->cur->cw = 1;}
2662 #line 2687 "pikchr.c"
2663 break;
2664 case 54: /* boolproperty ::= CCW */
2665 #line 650 "pikchr.y"
2666 {p->cur->cw = 0;}
2667 #line 2692 "pikchr.c"
2668 break;
2669 case 55: /* boolproperty ::= LARROW */
2670 #line 651 "pikchr.y"
2671 {p->cur->larrow=1; p->cur->rarrow=0; }
2672 #line 2697 "pikchr.c"
2673 break;
2674 case 56: /* boolproperty ::= RARROW */
2675 #line 652 "pikchr.y"
2676 {p->cur->larrow=0; p->cur->rarrow=1; }
2677 #line 2702 "pikchr.c"
2678 break;
2679 case 57: /* boolproperty ::= LRARROW */
2680 #line 653 "pikchr.y"
2681 {p->cur->larrow=1; p->cur->rarrow=1; }
2682 #line 2707 "pikchr.c"
2683 break;
2684 case 58: /* boolproperty ::= INVIS */
2685 #line 654 "pikchr.y"
2686 {p->cur->sw = 0.0;}
2687 #line 2712 "pikchr.c"
2688 break;
2689 case 59: /* boolproperty ::= THICK */
2690 #line 655 "pikchr.y"
2691 {p->cur->sw *= 1.5;}
2692 #line 2717 "pikchr.c"
2693 break;
2694 case 60: /* boolproperty ::= THIN */
2695 #line 656 "pikchr.y"
2696 {p->cur->sw *= 0.67;}
2697 #line 2722 "pikchr.c"
2698 break;
2699 case 61: /* boolproperty ::= SOLID */
2700 #line 657 "pikchr.y"
2701 {p->cur->sw = pik_value(p,"thickness",9,0);
2702 p->cur->dotted = p->cur->dashed = 0.0;}
2703 #line 2728 "pikchr.c"
2704 break;
2705 case 62: /* textposition ::= */
2706 #line 660 "pikchr.y"
2707 {yymsp[1].minor.yy196 = 0;}
2708 #line 2733 "pikchr.c"
2709 break;
2710 case 63: /* textposition ::= textposition CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|ALIGNED|BIG|SMALL */
2711 #line 663 "pikchr.y"
2712 {yylhsminor.yy196 = pik_text_position(yymsp[-1].minor.yy196,&yymsp[0].minor.yy0);}
2713 #line 2738 "pikchr.c"
2714 yymsp[-1].minor.yy196 = yylhsminor.yy196;
2715 break;
2716 case 64: /* position ::= expr COMMA expr */
2717 #line 666 "pikchr.y"
2718 {yylhsminor.yy43.x=yymsp[-2].minor.yy265; yylhsminor.yy43.y=yymsp[0].minor.yy265;}
2719 #line 2744 "pikchr.c"
2720 yymsp[-2].minor.yy43 = yylhsminor.yy43;
2721 break;
2722 case 65: /* position ::= place PLUS expr COMMA expr */
2723 #line 668 "pikchr.y"
2724 {yylhsminor.yy43.x=yymsp[-4].minor.yy43.x+yymsp[-2].minor.yy265; yylhsminor.yy43.y=yymsp[-4].minor.yy43.y+yymsp[0].minor.yy265;}
2725 #line 2750 "pikchr.c"
2726 yymsp[-4].minor.yy43 = yylhsminor.yy43;
2727 break;
2728 case 66: /* position ::= place MINUS expr COMMA expr */
2729 #line 669 "pikchr.y"
2730 {yylhsminor.yy43.x=yymsp[-4].minor.yy43.x-yymsp[-2].minor.yy265; yylhsminor.yy43.y=yymsp[-4].minor.yy43.y-yymsp[0].minor.yy265;}
2731 #line 2756 "pikchr.c"
2732 yymsp[-4].minor.yy43 = yylhsminor.yy43;
2733 break;
2734 case 67: /* position ::= place PLUS LP expr COMMA expr RP */
2735 #line 671 "pikchr.y"
2736 {yylhsminor.yy43.x=yymsp[-6].minor.yy43.x+yymsp[-3].minor.yy265; yylhsminor.yy43.y=yymsp[-6].minor.yy43.y+yymsp[-1].minor.yy265;}
2737 #line 2762 "pikchr.c"
2738 yymsp[-6].minor.yy43 = yylhsminor.yy43;
2739 break;
2740 case 68: /* position ::= place MINUS LP expr COMMA expr RP */
2741 #line 673 "pikchr.y"
2742 {yylhsminor.yy43.x=yymsp[-6].minor.yy43.x-yymsp[-3].minor.yy265; yylhsminor.yy43.y=yymsp[-6].minor.yy43.y-yymsp[-1].minor.yy265;}
2743 #line 2768 "pikchr.c"
2744 yymsp[-6].minor.yy43 = yylhsminor.yy43;
2745 break;
2746 case 69: /* position ::= LP position COMMA position RP */
2747 #line 674 "pikchr.y"
2748 {yymsp[-4].minor.yy43.x=yymsp[-3].minor.yy43.x; yymsp[-4].minor.yy43.y=yymsp[-1].minor.yy43.y;}
2749 #line 2774 "pikchr.c"
2750 break;
2751 case 70: /* position ::= LP position RP */
2752 #line 675 "pikchr.y"
2753 {yymsp[-2].minor.yy43=yymsp[-1].minor.yy43;}
2754 #line 2779 "pikchr.c"
2755 break;
2756 case 71: /* position ::= expr between position AND position */
2757 #line 677 "pikchr.y"
2758 {yylhsminor.yy43 = pik_position_between(yymsp[-4].minor.yy265,yymsp[-2].minor.yy43,yymsp[0].minor.yy43);}
2759 #line 2784 "pikchr.c"
2760 yymsp[-4].minor.yy43 = yylhsminor.yy43;
2761 break;
2762 case 72: /* position ::= expr LT position COMMA position GT */
2763 #line 679 "pikchr.y"
2764 {yylhsminor.yy43 = pik_position_between(yymsp[-5].minor.yy265,yymsp[-3].minor.yy43,yymsp[-1].minor.yy43);}
2765 #line 2790 "pikchr.c"
2766 yymsp[-5].minor.yy43 = yylhsminor.yy43;
2767 break;
2768 case 73: /* position ::= expr ABOVE position */
2769 #line 680 "pikchr.y"
2770 {yylhsminor.yy43=yymsp[0].minor.yy43; yylhsminor.yy43.y += yymsp[-2].minor.yy265;}
2771 #line 2796 "pikchr.c"
2772 yymsp[-2].minor.yy43 = yylhsminor.yy43;
2773 break;
2774 case 74: /* position ::= expr BELOW position */
2775 #line 681 "pikchr.y"
2776 {yylhsminor.yy43=yymsp[0].minor.yy43; yylhsminor.yy43.y -= yymsp[-2].minor.yy265;}
2777 #line 2802 "pikchr.c"
2778 yymsp[-2].minor.yy43 = yylhsminor.yy43;
2779 break;
2780 case 75: /* position ::= expr LEFT OF position */
2781 #line 682 "pikchr.y"
2782 {yylhsminor.yy43=yymsp[0].minor.yy43; yylhsminor.yy43.x -= yymsp[-3].minor.yy265;}
2783 #line 2808 "pikchr.c"
2784 yymsp[-3].minor.yy43 = yylhsminor.yy43;
2785 break;
2786 case 76: /* position ::= expr RIGHT OF position */
2787 #line 683 "pikchr.y"
2788 {yylhsminor.yy43=yymsp[0].minor.yy43; yylhsminor.yy43.x += yymsp[-3].minor.yy265;}
2789 #line 2814 "pikchr.c"
2790 yymsp[-3].minor.yy43 = yylhsminor.yy43;
2791 break;
2792 case 77: /* position ::= expr ON HEADING EDGEPT OF position */
2793 #line 685 "pikchr.y"
2794 {yylhsminor.yy43 = pik_position_at_hdg(yymsp[-5].minor.yy265,&yymsp[-2].minor.yy0,yymsp[0].minor.yy43);}
2795 #line 2820 "pikchr.c"
2796 yymsp[-5].minor.yy43 = yylhsminor.yy43;
2797 break;
2798 case 78: /* position ::= expr HEADING EDGEPT OF position */
2799 #line 687 "pikchr.y"
2800 {yylhsminor.yy43 = pik_position_at_hdg(yymsp[-4].minor.yy265,&yymsp[-2].minor.yy0,yymsp[0].minor.yy43);}
2801 #line 2826 "pikchr.c"
2802 yymsp[-4].minor.yy43 = yylhsminor.yy43;
2803 break;
2804 case 79: /* position ::= expr EDGEPT OF position */
2805 #line 689 "pikchr.y"
2806 {yylhsminor.yy43 = pik_position_at_hdg(yymsp[-3].minor.yy265,&yymsp[-2].minor.yy0,yymsp[0].minor.yy43);}
2807 #line 2832 "pikchr.c"
2808 yymsp[-3].minor.yy43 = yylhsminor.yy43;
2809 break;
2810 case 80: /* position ::= expr ON HEADING expr FROM position */
2811 #line 691 "pikchr.y"
2812 {yylhsminor.yy43 = pik_position_at_angle(yymsp[-5].minor.yy265,yymsp[-2].minor.yy265,yymsp[0].minor.yy43);}
2813 #line 2838 "pikchr.c"
2814 yymsp[-5].minor.yy43 = yylhsminor.yy43;
2815 break;
2816 case 81: /* position ::= expr HEADING expr FROM position */
2817 #line 693 "pikchr.y"
2818 {yylhsminor.yy43 = pik_position_at_angle(yymsp[-4].minor.yy265,yymsp[-2].minor.yy265,yymsp[0].minor.yy43);}
2819 #line 2844 "pikchr.c"
2820 yymsp[-4].minor.yy43 = yylhsminor.yy43;
2821 break;
2822 case 82: /* place ::= edge OF object */
2823 #line 705 "pikchr.y"
2824 {yylhsminor.yy43 = pik_place_of_elem(p,yymsp[0].minor.yy38,&yymsp[-2].minor.yy0);}
2825 #line 2850 "pikchr.c"
2826 yymsp[-2].minor.yy43 = yylhsminor.yy43;
2827 break;
2828 case 83: /* place2 ::= object */
2829 #line 706 "pikchr.y"
2830 {yylhsminor.yy43 = pik_place_of_elem(p,yymsp[0].minor.yy38,0);}
2831 #line 2856 "pikchr.c"
2832 yymsp[0].minor.yy43 = yylhsminor.yy43;
2833 break;
2834 case 84: /* place2 ::= object DOT_E edge */
2835 #line 707 "pikchr.y"
2836 {yylhsminor.yy43 = pik_place_of_elem(p,yymsp[-2].minor.yy38,&yymsp[0].minor.yy0);}
2837 #line 2862 "pikchr.c"
2838 yymsp[-2].minor.yy43 = yylhsminor.yy43;
2839 break;
2840 case 85: /* place2 ::= NTH VERTEX OF object */
2841 #line 708 "pikchr.y"
2842 {yylhsminor.yy43 = pik_nth_vertex(p,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,yymsp[0].minor.yy38);}
2843 #line 2868 "pikchr.c"
2844 yymsp[-3].minor.yy43 = yylhsminor.yy43;
2845 break;
2846 case 86: /* object ::= nth */
2847 #line 720 "pikchr.y"
2848 {yylhsminor.yy38 = pik_find_nth(p,0,&yymsp[0].minor.yy0);}
2849 #line 2874 "pikchr.c"
2850 yymsp[0].minor.yy38 = yylhsminor.yy38;
2851 break;
2852 case 87: /* object ::= nth OF|IN object */
2853 #line 721 "pikchr.y"
2854 {yylhsminor.yy38 = pik_find_nth(p,yymsp[0].minor.yy38,&yymsp[-2].minor.yy0);}
2855 #line 2880 "pikchr.c"
2856 yymsp[-2].minor.yy38 = yylhsminor.yy38;
2857 break;
2858 case 88: /* objectname ::= PLACENAME */
2859 #line 723 "pikchr.y"
2860 {yylhsminor.yy38 = pik_find_byname(p,0,&yymsp[0].minor.yy0);}
2861 #line 2886 "pikchr.c"
2862 yymsp[0].minor.yy38 = yylhsminor.yy38;
2863 break;
2864 case 89: /* objectname ::= objectname DOT_U PLACENAME */
2865 #line 725 "pikchr.y"
2866 {yylhsminor.yy38 = pik_find_byname(p,yymsp[-2].minor.yy38,&yymsp[0].minor.yy0);}
2867 #line 2892 "pikchr.c"
2868 yymsp[-2].minor.yy38 = yylhsminor.yy38;
2869 break;
2870 case 90: /* nth ::= NTH CLASSNAME */
2871 #line 727 "pikchr.y"
2872 {yylhsminor.yy0=yymsp[0].minor.yy0; yylhsminor.yy0.eCode = pik_nth_value(p,&yymsp[-1].minor.yy0); }
2873 #line 2898 "pikchr.c"
2874 yymsp[-1].minor.yy0 = yylhsminor.yy0;
2875 break;
2876 case 91: /* nth ::= NTH LAST CLASSNAME */
2877 #line 728 "pikchr.y"
2878 {yylhsminor.yy0=yymsp[0].minor.yy0; yylhsminor.yy0.eCode = -pik_nth_value(p,&yymsp[-2].minor.yy0); }
2879 #line 2904 "pikchr.c"
2880 yymsp[-2].minor.yy0 = yylhsminor.yy0;
2881 break;
2882 case 92: /* nth ::= LAST CLASSNAME */
2883 #line 729 "pikchr.y"
2884 {yymsp[-1].minor.yy0=yymsp[0].minor.yy0; yymsp[-1].minor.yy0.eCode = -1;}
2885 #line 2910 "pikchr.c"
2886 break;
2887 case 93: /* nth ::= LAST */
2888 #line 730 "pikchr.y"
2889 {yylhsminor.yy0=yymsp[0].minor.yy0; yylhsminor.yy0.eCode = -1;}
2890 #line 2915 "pikchr.c"
2891 yymsp[0].minor.yy0 = yylhsminor.yy0;
2892 break;
2893 case 94: /* nth ::= NTH LB RB */
2894 #line 731 "pikchr.y"
2895 {yylhsminor.yy0=yymsp[-1].minor.yy0; yylhsminor.yy0.eCode = pik_nth_value(p,&yymsp[-2].minor.yy0);}
2896 #line 2921 "pikchr.c"
2897 yymsp[-2].minor.yy0 = yylhsminor.yy0;
2898 break;
2899 case 95: /* nth ::= NTH LAST LB RB */
2900 #line 732 "pikchr.y"
2901 {yylhsminor.yy0=yymsp[-1].minor.yy0; yylhsminor.yy0.eCode = -pik_nth_value(p,&yymsp[-3].minor.yy0);}
2902 #line 2927 "pikchr.c"
2903 yymsp[-3].minor.yy0 = yylhsminor.yy0;
2904 break;
2905 case 96: /* nth ::= LAST LB RB */
2906 #line 733 "pikchr.y"
2907 {yymsp[-2].minor.yy0=yymsp[-1].minor.yy0; yymsp[-2].minor.yy0.eCode = -1; }
2908 #line 2933 "pikchr.c"
2909 break;
2910 case 97: /* expr ::= expr PLUS expr */
2911 #line 735 "pikchr.y"
2912 {yylhsminor.yy265=yymsp[-2].minor.yy265+yymsp[0].minor.yy265;}
2913 #line 2938 "pikchr.c"
2914 yymsp[-2].minor.yy265 = yylhsminor.yy265;
2915 break;
2916 case 98: /* expr ::= expr MINUS expr */
2917 #line 736 "pikchr.y"
2918 {yylhsminor.yy265=yymsp[-2].minor.yy265-yymsp[0].minor.yy265;}
2919 #line 2944 "pikchr.c"
2920 yymsp[-2].minor.yy265 = yylhsminor.yy265;
2921 break;
2922 case 99: /* expr ::= expr STAR expr */
2923 #line 737 "pikchr.y"
2924 {yylhsminor.yy265=yymsp[-2].minor.yy265*yymsp[0].minor.yy265;}
2925 #line 2950 "pikchr.c"
2926 yymsp[-2].minor.yy265 = yylhsminor.yy265;
2927 break;
2928 case 100: /* expr ::= expr SLASH expr */
2929 #line 738 "pikchr.y"
2930 {
2931 if( yymsp[0].minor.yy265==0.0 ){ pik_error(p, &yymsp[-1].minor.yy0, "division by zero"); yylhsminor.yy265 = 0.0; }
2932 else{ yylhsminor.yy265 = yymsp[-2].minor.yy265/yymsp[0].minor.yy265; }
2933 }
2934 #line 2959 "pikchr.c"
2935 yymsp[-2].minor.yy265 = yylhsminor.yy265;
2936 break;
2937 case 101: /* expr ::= MINUS expr */
2938 #line 742 "pikchr.y"
2939 {yymsp[-1].minor.yy265=-yymsp[0].minor.yy265;}
2940 #line 2965 "pikchr.c"
2941 break;
2942 case 102: /* expr ::= PLUS expr */
2943 #line 743 "pikchr.y"
2944 {yymsp[-1].minor.yy265=yymsp[0].minor.yy265;}
2945 #line 2970 "pikchr.c"
2946 break;
2947 case 103: /* expr ::= LP expr RP */
2948 #line 744 "pikchr.y"
2949 {yymsp[-2].minor.yy265=yymsp[-1].minor.yy265;}
2950 #line 2975 "pikchr.c"
2951 break;
2952 case 104: /* expr ::= LP FILL|COLOR|THICKNESS RP */
2953 #line 745 "pikchr.y"
2954 {yymsp[-2].minor.yy265=pik_get_var(p,&yymsp[-1].minor.yy0);}
2955 #line 2980 "pikchr.c"
2956 break;
2957 case 105: /* expr ::= NUMBER */
2958 #line 746 "pikchr.y"
2959 {yylhsminor.yy265=pik_atof(&yymsp[0].minor.yy0);}
2960 #line 2985 "pikchr.c"
2961 yymsp[0].minor.yy265 = yylhsminor.yy265;
2962 break;
2963 case 106: /* expr ::= ID */
2964 #line 747 "pikchr.y"
2965 {yylhsminor.yy265=pik_get_var(p,&yymsp[0].minor.yy0);}
2966 #line 2991 "pikchr.c"
2967 yymsp[0].minor.yy265 = yylhsminor.yy265;
2968 break;
2969 case 107: /* expr ::= FUNC1 LP expr RP */
2970 #line 748 "pikchr.y"
2971 {yylhsminor.yy265 = pik_func(p,&yymsp[-3].minor.yy0,yymsp[-1].minor.yy265,0.0);}
2972 #line 2997 "pikchr.c"
2973 yymsp[-3].minor.yy265 = yylhsminor.yy265;
2974 break;
2975 case 108: /* expr ::= FUNC2 LP expr COMMA expr RP */
2976 #line 749 "pikchr.y"
2977 {yylhsminor.yy265 = pik_func(p,&yymsp[-5].minor.yy0,yymsp[-3].minor.yy265,yymsp[-1].minor.yy265);}
2978 #line 3003 "pikchr.c"
2979 yymsp[-5].minor.yy265 = yylhsminor.yy265;
2980 break;
2981 case 109: /* expr ::= DIST LP position COMMA position RP */
2982 #line 750 "pikchr.y"
2983 {yymsp[-5].minor.yy265 = pik_dist(&yymsp[-3].minor.yy43,&yymsp[-1].minor.yy43);}
2984 #line 3009 "pikchr.c"
2985 break;
2986 case 110: /* expr ::= place2 DOT_XY X */
2987 #line 751 "pikchr.y"
2988 {yylhsminor.yy265 = yymsp[-2].minor.yy43.x;}
2989 #line 3014 "pikchr.c"
2990 yymsp[-2].minor.yy265 = yylhsminor.yy265;
2991 break;
2992 case 111: /* expr ::= place2 DOT_XY Y */
2993 #line 752 "pikchr.y"
2994 {yylhsminor.yy265 = yymsp[-2].minor.yy43.y;}
2995 #line 3020 "pikchr.c"
2996 yymsp[-2].minor.yy265 = yylhsminor.yy265;
2997 break;
2998 case 112: /* expr ::= object DOT_L numproperty */
2999 case 113: /* expr ::= object DOT_L dashproperty */ yytestcase(yyruleno==113);
3000 case 114: /* expr ::= object DOT_L colorproperty */ yytestcase(yyruleno==114);
3001 #line 753 "pikchr.y"
3002 {yylhsminor.yy265=pik_property_of(yymsp[-2].minor.yy38,&yymsp[0].minor.yy0);}
3003 #line 3028 "pikchr.c"
3004 yymsp[-2].minor.yy265 = yylhsminor.yy265;
3005 break;
3006 default:
3007 /* (115) lvalue ::= ID */ yytestcase(yyruleno==115);
3008 /* (116) lvalue ::= FILL */ yytestcase(yyruleno==116);
@@ -3101,19 +3106,19 @@
3101 ){
3102 pik_parserARG_FETCH
3103 pik_parserCTX_FETCH
3104 #define TOKEN yyminor
3105 /************ Begin %syntax_error code ****************************************/
3106 #line 514 "pikchr.y"
3107
3108 if( TOKEN.z && TOKEN.z[0] ){
3109 pik_error(p, &TOKEN, "syntax error");
3110 }else{
3111 pik_error(p, 0, "syntax error");
3112 }
3113 UNUSED_PARAMETER(yymajor);
3114 #line 3139 "pikchr.c"
3115 /************ End %syntax_error code ******************************************/
3116 pik_parserARG_STORE /* Suppress warning about unused %extra_argument variable */
3117 pik_parserCTX_STORE
3118 }
3119
@@ -3342,16 +3347,17 @@
3342 #else
3343 (void)iToken;
3344 return 0;
3345 #endif
3346 }
3347 #line 758 "pikchr.y"
3348
3349
3350
3351 /* Chart of the 140 official HTML color names with their
3352 ** corresponding RGB value.
 
3353 **
3354 ** Two new names "None" and "Off" are added with a value
3355 ** of -1.
3356 */
3357 static const struct {
@@ -3359,11 +3365,11 @@
3359 int val; /* RGB value */
3360 } aColor[] = {
3361 { "AliceBlue", 0xf0f8ff },
3362 { "AntiqueWhite", 0xfaebd7 },
3363 { "Aqua", 0x00ffff },
3364 { "AquaMarine", 0x7fffd4 },
3365 { "Azure", 0xf0ffff },
3366 { "Beige", 0xf5f5dc },
3367 { "Bisque", 0xffe4c4 },
3368 { "Black", 0x000000 },
3369 { "BlanchedAlmond", 0xffebcd },
@@ -3373,19 +3379,20 @@
3373 { "BurlyWood", 0xdeb887 },
3374 { "CadetBlue", 0x5f9ea0 },
3375 { "Chartreuse", 0x7fff00 },
3376 { "Chocolate", 0xd2691e },
3377 { "Coral", 0xff7f50 },
3378 { "CornFlowerBlue", 0x6495ed },
3379 { "Cornsilk", 0xfff8dc },
3380 { "Crimson", 0xdc143c },
3381 { "Cyan", 0x00ffff },
3382 { "DarkBlue", 0x00008b },
3383 { "DarkCyan", 0x008b8b },
3384 { "DarkGoldenRod", 0xb8860b },
3385 { "DarkGray", 0xa9a9a9 },
3386 { "DarkGreen", 0x006400 },
 
3387 { "DarkKhaki", 0xbdb76b },
3388 { "DarkMagenta", 0x8b008b },
3389 { "DarkOliveGreen", 0x556b2f },
3390 { "DarkOrange", 0xff8c00 },
3391 { "DarkOrchid", 0x9932cc },
@@ -3392,28 +3399,31 @@
3392 { "DarkRed", 0x8b0000 },
3393 { "DarkSalmon", 0xe9967a },
3394 { "DarkSeaGreen", 0x8fbc8f },
3395 { "DarkSlateBlue", 0x483d8b },
3396 { "DarkSlateGray", 0x2f4f4f },
 
3397 { "DarkTurquoise", 0x00ced1 },
3398 { "DarkViolet", 0x9400d3 },
3399 { "DeepPink", 0xff1493 },
3400 { "DeepSkyBlue", 0x00bfff },
3401 { "DimGray", 0x696969 },
 
3402 { "DodgerBlue", 0x1e90ff },
3403 { "FireBrick", 0xb22222 },
3404 { "FloralWhite", 0xfffaf0 },
3405 { "ForestGreen", 0x228b22 },
3406 { "Fuchsia", 0xff00ff },
3407 { "Gainsboro", 0xdcdcdc },
3408 { "GhostWhite", 0xf8f8ff },
3409 { "Gold", 0xffd700 },
3410 { "GoldenRod", 0xdaa520 },
3411 { "Gray", 0x808080 },
3412 { "Green", 0x008000 },
3413 { "GreenYellow", 0xadff2f },
3414 { "HoneyDew", 0xf0fff0 },
 
3415 { "HotPink", 0xff69b4 },
3416 { "IndianRed", 0xcd5c5c },
3417 { "Indigo", 0x4b0082 },
3418 { "Ivory", 0xfffff0 },
3419 { "Khaki", 0xf0e68c },
@@ -3425,26 +3435,28 @@
3425 { "LightCoral", 0xf08080 },
3426 { "LightCyan", 0xe0ffff },
3427 { "LightGoldenrodYellow", 0xfafad2 },
3428 { "LightGray", 0xd3d3d3 },
3429 { "LightGreen", 0x90ee90 },
 
3430 { "LightPink", 0xffb6c1 },
3431 { "LightSalmon", 0xffa07a },
3432 { "LightSeaGreen", 0x20b2aa },
3433 { "LightSkyBlue", 0x87cefa },
3434 { "LightSlateGray", 0x778899 },
 
3435 { "LightSteelBlue", 0xb0c4de },
3436 { "LightYellow", 0xffffe0 },
3437 { "Lime", 0x00ff00 },
3438 { "LimeGreen", 0x32cd32 },
3439 { "Linen", 0xfaf0e6 },
3440 { "Magenta", 0xff00ff },
3441 { "Maroon", 0x800000 },
3442 { "MediumAquaMarine", 0x66cdaa },
3443 { "MediumBlue", 0x0000cd },
3444 { "MediumOrchid", 0xba55d3 },
3445 { "MediumPurple", 0x9370d8 },
3446 { "MediumSeaGreen", 0x3cb371 },
3447 { "MediumSlateBlue", 0x7b68ee },
3448 { "MediumSpringGreen", 0x00fa9a },
3449 { "MediumTurquoise", 0x48d1cc },
3450 { "MediumVioletRed", 0xc71585 },
@@ -3460,11 +3472,11 @@
3460 { "Olive", 0x808000 },
3461 { "OliveDrab", 0x6b8e23 },
3462 { "Orange", 0xffa500 },
3463 { "OrangeRed", 0xff4500 },
3464 { "Orchid", 0xda70d6 },
3465 { "PaleGoldenRod", 0xeee8aa },
3466 { "PaleGreen", 0x98fb98 },
3467 { "PaleTurquoise", 0xafeeee },
3468 { "PaleVioletRed", 0xdb7093 },
3469 { "PapayaWhip", 0xffefd5 },
3470 { "PeachPuff", 0xffdab9 },
@@ -3471,23 +3483,25 @@
3471 { "Peru", 0xcd853f },
3472 { "Pink", 0xffc0cb },
3473 { "Plum", 0xdda0dd },
3474 { "PowderBlue", 0xb0e0e6 },
3475 { "Purple", 0x800080 },
 
3476 { "Red", 0xff0000 },
3477 { "RosyBrown", 0xbc8f8f },
3478 { "RoyalBlue", 0x4169e1 },
3479 { "SaddleBrown", 0x8b4513 },
3480 { "Salmon", 0xfa8072 },
3481 { "SandyBrown", 0xf4a460 },
3482 { "SeaGreen", 0x2e8b57 },
3483 { "SeaShell", 0xfff5ee },
3484 { "Sienna", 0xa0522d },
3485 { "Silver", 0xc0c0c0 },
3486 { "SkyBlue", 0x87ceeb },
3487 { "SlateBlue", 0x6a5acd },
3488 { "SlateGray", 0x708090 },
 
3489 { "Snow", 0xfffafa },
3490 { "SpringGreen", 0x00ff7f },
3491 { "SteelBlue", 0x4682b4 },
3492 { "Tan", 0xd2b48c },
3493 { "Teal", 0x008080 },
@@ -4445,11 +4459,11 @@
4445 bx = f->x + e1*dx;
4446 by = f->y + e1*dy;
4447 pik_append_xy(p,"<polygon points=\"", t->x, t->y);
4448 pik_append_xy(p," ",bx-ddx, by-ddy);
4449 pik_append_xy(p," ",bx+ddx, by+ddy);
4450 pik_append_clr(p,"\" style=\"fill:",pObj->color,"\"/>\n");
4451 pik_chop(f,t,h/2);
4452 }
4453
4454 /*
4455 ** Compute the relative offset to an edge location from the reference for a
@@ -4552,10 +4566,45 @@
4552 (double)pPt->x, (double)pPt->y);
4553 buf[sizeof(buf)-1] = 0;
4554 pik_append(p, z, -1);
4555 pik_append(p, buf, -1);
4556 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4557
4558 /* Append a PNum value surrounded by text. Do coordinate transformations
4559 ** on the value.
4560 */
4561 static void pik_append_x(Pik *p, const char *z1, PNum v, const char *z2){
@@ -4585,16 +4634,22 @@
4585 char buf[200];
4586 snprintf(buf, sizeof(buf)-1, "%s%g%s", z1, p->rScale*v, z2);
4587 buf[sizeof(buf)-1] = 0;
4588 pik_append(p, buf, -1);
4589 }
4590 static void pik_append_clr(Pik *p, const char *z1, PNum v, const char *z2){
4591 char buf[200];
4592 int x = (int)v;
4593 int r = (x>>16) & 0xff;
4594 int g = (x>>8) & 0xff;
4595 int b = x & 0xff;
 
 
 
 
 
 
4596 snprintf(buf, sizeof(buf)-1, "%srgb(%d,%d,%d)%s", z1, r, g, b, z2);
4597 buf[sizeof(buf)-1] = 0;
4598 pik_append(p, buf, -1);
4599 }
4600
@@ -4617,21 +4672,21 @@
4617 ** the caller wants to add some more.
4618 */
4619 static void pik_append_style(Pik *p, PObj *pObj, int bFill){
4620 pik_append(p, " style=\"", -1);
4621 if( pObj->fill>=0 && bFill ){
4622 pik_append_clr(p, "fill:", pObj->fill, ";");
4623 }else{
4624 pik_append(p,"fill:none;",-1);
4625 }
4626 if( pObj->sw>0.0 && pObj->color>=0.0 ){
4627 PNum sw = pObj->sw;
4628 pik_append_dis(p, "stroke-width:", sw, ";");
4629 if( pObj->nPath>2 && pObj->rad<=pObj->sw ){
4630 pik_append(p, "stroke-linejoin:round;", -1);
4631 }
4632 pik_append_clr(p, "stroke:",pObj->color,";");
4633 if( pObj->dotted>0.0 ){
4634 PNum v = pObj->dotted;
4635 if( sw<2.1/p->rScale ) sw = 2.1/p->rScale;
4636 pik_append_dis(p,"stroke-dasharray:",sw,"");
4637 pik_append_dis(p,",",v,";");
@@ -4882,11 +4937,11 @@
4882 }
4883 if( t->eCode & TP_BOLD ){
4884 pik_append(p, " font-weight=\"bold\"", -1);
4885 }
4886 if( pObj->color>=0.0 ){
4887 pik_append_clr(p, " fill=\"", pObj->color, "\"");
4888 }
4889 xtraFontScale *= p->fontScale;
4890 if( xtraFontScale<=0.99 || xtraFontScale>=1.01 ){
4891 pik_append_num(p, " font-size=\"", xtraFontScale*100.0);
4892 pik_append(p, "%\"", 2);
@@ -6827,18 +6882,26 @@
6827 PNum thickness; /* Stroke width */
6828 PNum margin; /* Extra bounding box margin */
6829 PNum w, h; /* Drawing width and height */
6830 PNum wArrow;
6831 PNum pikScale; /* Value of the "scale" variable */
 
6832
6833 /* Set up rendering parameters */
6834 pik_compute_layout_settings(p);
6835 thickness = pik_value(p,"thickness",9,0);
6836 if( thickness<=0.01 ) thickness = 0.01;
6837 margin = pik_value(p,"margin",6,0);
6838 margin += thickness;
6839 wArrow = p->wArrow*thickness;
 
 
 
 
 
 
 
6840
6841 /* Compute a bounding box over all objects so that we can know
6842 ** how big to declare the SVG canvas */
6843 pik_bbox_init(&p->bbox);
6844 pik_bbox_add_elist(p, pList, wArrow);
@@ -7620,10 +7683,11 @@
7620 int i;
7621 int bSvgOnly = 0; /* Output SVG only. No HTML wrapper */
7622 int bDontStop = 0; /* Continue in spite of errors */
7623 int exitCode = 0; /* What to return */
7624 int mFlags = 0; /* mFlags argument to pikchr() */
 
7625 const char *zHtmlHdr =
7626 "<!DOCTYPE html>\n"
7627 "<html lang=\"en-US\">\n"
7628 "<head>\n<title>PIKCHR Test</title>\n"
7629 "<style>\n"
@@ -7657,10 +7721,14 @@
7657 char *z = argv[i];
7658 z++;
7659 if( z[0]=='-' ) z++;
7660 if( strcmp(z,"dont-stop")==0 ){
7661 bDontStop = 1;
 
 
 
 
7662 }else
7663 if( strcmp(z,"svg-only")==0 ){
7664 if( zHtmlHdr==0 ){
7665 fprintf(stderr, "the \"%s\" option must come first\n",argv[i]);
7666 exit(1);
@@ -7706,11 +7774,12 @@
7706 printf("<h1>File %s</h1>\n", argv[i]);
7707 if( w<0 ){
7708 printf("<p>ERROR</p>\n%s\n", zOut);
7709 }else{
7710 printf("<div id=\"svg-%d\" onclick=\"toggleHidden('svg-%d')\">\n",i,i);
7711 printf("<div style='border:3px solid lightgray;max-width:%dpx;'>\n",w);
 
7712 printf("%s</div>\n", zOut);
7713 printf("<pre class='hidden'>");
7714 print_escape_html(zIn);
7715 printf("</pre>\n</div>\n");
7716 }
@@ -7783,6 +7852,6 @@
7783
7784
7785 #endif /* PIKCHR_TCL */
7786
7787
7788 #line 7813 "pikchr.c"
7789
--- src/pikchr.c
+++ src/pikchr.c
@@ -363,10 +363,11 @@
363 char bLayoutVars; /* True if cache is valid */
364 char thenFlag; /* True if "then" seen */
365 char samePath; /* aTPath copied by "same" */
366 const char *zClass; /* Class name for the <svg> */
367 int wSVG, hSVG; /* Width and height of the <svg> */
368 int fgcolor; /* fgcolor value, or -1 for none */
369 /* Paths for lines are constructed here first, then transferred into
370 ** the PObj object at the end: */
371 int nTPath; /* Number of entries on aTPath[] */
372 int mTPath; /* For last entry, 1: x set, 2: y set */
373 PPoint aTPath[1000]; /* Path under construction */
@@ -378,10 +379,14 @@
379 /* Include PIKCHR_PLAINTEXT_ERRORS among the bits of mFlags on the 3rd
380 ** argument to pikchr() in order to cause error message text to come out
381 ** as text/plain instead of as text/html
382 */
383 #define PIKCHR_PLAINTEXT_ERRORS 0x0001
384
385 /* Include PIKCHR_DARK_MODE among the mFlag bits to invert colors.
386 */
387 #define PIKCHR_DARK_MODE 0x0002
388
389 /*
390 ** The behavior of an object class is defined by an instance of
391 ** this structure. This is the "virtual method" table.
392 */
@@ -407,11 +412,11 @@
412 static void pik_append_x(Pik*,const char*,PNum,const char*);
413 static void pik_append_y(Pik*,const char*,PNum,const char*);
414 static void pik_append_xy(Pik*,const char*,PNum,PNum);
415 static void pik_append_dis(Pik*,const char*,PNum,const char*);
416 static void pik_append_arc(Pik*,PNum,PNum,PNum,PNum);
417 static void pik_append_clr(Pik*,const char*,PNum,const char*,int);
418 static void pik_append_style(Pik*,PObj*,int);
419 static void pik_append_txt(Pik*,PObj*, PBox*);
420 static void pik_draw_arrowhead(Pik*,PPoint*pFrom,PPoint*pTo,PObj*);
421 static void pik_chop(PPoint*pFrom,PPoint*pTo,PNum);
422 static void pik_error(Pik*,PToken*,const char*);
@@ -468,11 +473,11 @@
473 static PObj *pik_position_assert(Pik*,PPoint*,PToken*,PPoint*);
474 static PNum pik_dist(PPoint*,PPoint*);
475 static void pik_add_macro(Pik*,PToken *pId,PToken *pCode);
476
477
478 #line 504 "pikchr.c"
479 /**************** End of %include directives **********************************/
480 /* These constants specify the various numeric values for terminal symbols.
481 ***************** Begin token definitions *************************************/
482 #ifndef T_ID
483 #define T_ID 1
@@ -1685,22 +1690,22 @@
1690 ** inside the C code.
1691 */
1692 /********* Begin destructor definitions ***************************************/
1693 case 98: /* statement_list */
1694 {
1695 #line 493 "pikchr.y"
1696 pik_elist_free(p,(yypminor->yy119));
1697 #line 1722 "pikchr.c"
1698 }
1699 break;
1700 case 99: /* statement */
1701 case 100: /* unnamed_statement */
1702 case 101: /* basetype */
1703 {
1704 #line 495 "pikchr.y"
1705 pik_elem_free(p,(yypminor->yy38));
1706 #line 1731 "pikchr.c"
1707 }
1708 break;
1709 /********* End destructor definitions *****************************************/
1710 default: break; /* If no destructor action specified: do nothing */
1711 }
@@ -1914,14 +1919,14 @@
1919 #endif
1920 while( yypParser->yytos>yypParser->yystack ) yy_pop_parser_stack(yypParser);
1921 /* Here code is inserted which will execute if the parser
1922 ** stack every overflows */
1923 /******** Begin %stack_overflow code ******************************************/
1924 #line 527 "pikchr.y"
1925
1926 pik_error(p, 0, "parser stack overflow");
1927 #line 1952 "pikchr.c"
1928 /******** End %stack_overflow code ********************************************/
1929 pik_parserARG_STORE /* Suppress warning about unused %extra_argument var */
1930 pik_parserCTX_STORE
1931 }
1932
@@ -2395,614 +2400,614 @@
2400 ** break;
2401 */
2402 /********** Begin reduce actions **********************************************/
2403 YYMINORTYPE yylhsminor;
2404 case 0: /* document ::= statement_list */
2405 #line 531 "pikchr.y"
2406 {pik_render(p,yymsp[0].minor.yy119);}
2407 #line 2432 "pikchr.c"
2408 break;
2409 case 1: /* statement_list ::= statement */
2410 #line 534 "pikchr.y"
2411 { yylhsminor.yy119 = pik_elist_append(p,0,yymsp[0].minor.yy38); }
2412 #line 2437 "pikchr.c"
2413 yymsp[0].minor.yy119 = yylhsminor.yy119;
2414 break;
2415 case 2: /* statement_list ::= statement_list EOL statement */
2416 #line 536 "pikchr.y"
2417 { yylhsminor.yy119 = pik_elist_append(p,yymsp[-2].minor.yy119,yymsp[0].minor.yy38); }
2418 #line 2443 "pikchr.c"
2419 yymsp[-2].minor.yy119 = yylhsminor.yy119;
2420 break;
2421 case 3: /* statement ::= */
2422 #line 539 "pikchr.y"
2423 { yymsp[1].minor.yy38 = 0; }
2424 #line 2449 "pikchr.c"
2425 break;
2426 case 4: /* statement ::= direction */
2427 #line 540 "pikchr.y"
2428 { pik_set_direction(p,yymsp[0].minor.yy0.eCode); yylhsminor.yy38=0; }
2429 #line 2454 "pikchr.c"
2430 yymsp[0].minor.yy38 = yylhsminor.yy38;
2431 break;
2432 case 5: /* statement ::= lvalue ASSIGN rvalue */
2433 #line 541 "pikchr.y"
2434 {pik_set_var(p,&yymsp[-2].minor.yy0,yymsp[0].minor.yy265,&yymsp[-1].minor.yy0); yylhsminor.yy38=0;}
2435 #line 2460 "pikchr.c"
2436 yymsp[-2].minor.yy38 = yylhsminor.yy38;
2437 break;
2438 case 6: /* statement ::= PLACENAME COLON unnamed_statement */
2439 #line 543 "pikchr.y"
2440 { yylhsminor.yy38 = yymsp[0].minor.yy38; pik_elem_setname(p,yymsp[0].minor.yy38,&yymsp[-2].minor.yy0); }
2441 #line 2466 "pikchr.c"
2442 yymsp[-2].minor.yy38 = yylhsminor.yy38;
2443 break;
2444 case 7: /* statement ::= PLACENAME COLON position */
2445 #line 545 "pikchr.y"
2446 { yylhsminor.yy38 = pik_elem_new(p,0,0,0);
2447 if(yylhsminor.yy38){ yylhsminor.yy38->ptAt = yymsp[0].minor.yy43; pik_elem_setname(p,yylhsminor.yy38,&yymsp[-2].minor.yy0); }}
2448 #line 2473 "pikchr.c"
2449 yymsp[-2].minor.yy38 = yylhsminor.yy38;
2450 break;
2451 case 8: /* statement ::= unnamed_statement */
2452 #line 547 "pikchr.y"
2453 {yylhsminor.yy38 = yymsp[0].minor.yy38;}
2454 #line 2479 "pikchr.c"
2455 yymsp[0].minor.yy38 = yylhsminor.yy38;
2456 break;
2457 case 9: /* statement ::= print prlist */
2458 #line 548 "pikchr.y"
2459 {pik_append(p,"<br>\n",5); yymsp[-1].minor.yy38=0;}
2460 #line 2485 "pikchr.c"
2461 break;
2462 case 10: /* statement ::= ASSERT LP expr EQ expr RP */
2463 #line 553 "pikchr.y"
2464 {yymsp[-5].minor.yy38=pik_assert(p,yymsp[-3].minor.yy265,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy265);}
2465 #line 2490 "pikchr.c"
2466 break;
2467 case 11: /* statement ::= ASSERT LP position EQ position RP */
2468 #line 555 "pikchr.y"
2469 {yymsp[-5].minor.yy38=pik_position_assert(p,&yymsp[-3].minor.yy43,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy43);}
2470 #line 2495 "pikchr.c"
2471 break;
2472 case 12: /* statement ::= DEFINE ID CODEBLOCK */
2473 #line 556 "pikchr.y"
2474 {yymsp[-2].minor.yy38=0; pik_add_macro(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0);}
2475 #line 2500 "pikchr.c"
2476 break;
2477 case 13: /* rvalue ::= PLACENAME */
2478 #line 567 "pikchr.y"
2479 {yylhsminor.yy265 = pik_lookup_color(p,&yymsp[0].minor.yy0);}
2480 #line 2505 "pikchr.c"
2481 yymsp[0].minor.yy265 = yylhsminor.yy265;
2482 break;
2483 case 14: /* pritem ::= FILL */
2484 case 15: /* pritem ::= COLOR */ yytestcase(yyruleno==15);
2485 case 16: /* pritem ::= THICKNESS */ yytestcase(yyruleno==16);
2486 #line 572 "pikchr.y"
2487 {pik_append_num(p,"",pik_value(p,yymsp[0].minor.yy0.z,yymsp[0].minor.yy0.n,0));}
2488 #line 2513 "pikchr.c"
2489 break;
2490 case 17: /* pritem ::= rvalue */
2491 #line 575 "pikchr.y"
2492 {pik_append_num(p,"",yymsp[0].minor.yy265);}
2493 #line 2518 "pikchr.c"
2494 break;
2495 case 18: /* pritem ::= STRING */
2496 #line 576 "pikchr.y"
2497 {pik_append_text(p,yymsp[0].minor.yy0.z+1,yymsp[0].minor.yy0.n-2,0);}
2498 #line 2523 "pikchr.c"
2499 break;
2500 case 19: /* prsep ::= COMMA */
2501 #line 577 "pikchr.y"
2502 {pik_append(p, " ", 1);}
2503 #line 2528 "pikchr.c"
2504 break;
2505 case 20: /* unnamed_statement ::= basetype attribute_list */
2506 #line 580 "pikchr.y"
2507 {yylhsminor.yy38 = yymsp[-1].minor.yy38; pik_after_adding_attributes(p,yylhsminor.yy38);}
2508 #line 2533 "pikchr.c"
2509 yymsp[-1].minor.yy38 = yylhsminor.yy38;
2510 break;
2511 case 21: /* basetype ::= CLASSNAME */
2512 #line 582 "pikchr.y"
2513 {yylhsminor.yy38 = pik_elem_new(p,&yymsp[0].minor.yy0,0,0); }
2514 #line 2539 "pikchr.c"
2515 yymsp[0].minor.yy38 = yylhsminor.yy38;
2516 break;
2517 case 22: /* basetype ::= STRING textposition */
2518 #line 584 "pikchr.y"
2519 {yymsp[-1].minor.yy0.eCode = yymsp[0].minor.yy196; yylhsminor.yy38 = pik_elem_new(p,0,&yymsp[-1].minor.yy0,0); }
2520 #line 2545 "pikchr.c"
2521 yymsp[-1].minor.yy38 = yylhsminor.yy38;
2522 break;
2523 case 23: /* basetype ::= LB savelist statement_list RB */
2524 #line 586 "pikchr.y"
2525 { p->list = yymsp[-2].minor.yy119; yymsp[-3].minor.yy38 = pik_elem_new(p,0,0,yymsp[-1].minor.yy119); if(yymsp[-3].minor.yy38) yymsp[-3].minor.yy38->errTok = yymsp[0].minor.yy0; }
2526 #line 2551 "pikchr.c"
2527 break;
2528 case 24: /* savelist ::= */
2529 #line 591 "pikchr.y"
2530 {yymsp[1].minor.yy119 = p->list; p->list = 0;}
2531 #line 2556 "pikchr.c"
2532 break;
2533 case 25: /* relexpr ::= expr */
2534 #line 598 "pikchr.y"
2535 {yylhsminor.yy200.rAbs = yymsp[0].minor.yy265; yylhsminor.yy200.rRel = 0;}
2536 #line 2561 "pikchr.c"
2537 yymsp[0].minor.yy200 = yylhsminor.yy200;
2538 break;
2539 case 26: /* relexpr ::= expr PERCENT */
2540 #line 599 "pikchr.y"
2541 {yylhsminor.yy200.rAbs = 0; yylhsminor.yy200.rRel = yymsp[-1].minor.yy265/100;}
2542 #line 2567 "pikchr.c"
2543 yymsp[-1].minor.yy200 = yylhsminor.yy200;
2544 break;
2545 case 27: /* optrelexpr ::= */
2546 #line 601 "pikchr.y"
2547 {yymsp[1].minor.yy200.rAbs = 0; yymsp[1].minor.yy200.rRel = 1.0;}
2548 #line 2573 "pikchr.c"
2549 break;
2550 case 28: /* attribute_list ::= relexpr alist */
2551 #line 603 "pikchr.y"
2552 {pik_add_direction(p,0,&yymsp[-1].minor.yy200);}
2553 #line 2578 "pikchr.c"
2554 break;
2555 case 29: /* attribute ::= numproperty relexpr */
2556 #line 607 "pikchr.y"
2557 { pik_set_numprop(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy200); }
2558 #line 2583 "pikchr.c"
2559 break;
2560 case 30: /* attribute ::= dashproperty expr */
2561 #line 608 "pikchr.y"
2562 { pik_set_dashed(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy265); }
2563 #line 2588 "pikchr.c"
2564 break;
2565 case 31: /* attribute ::= dashproperty */
2566 #line 609 "pikchr.y"
2567 { pik_set_dashed(p,&yymsp[0].minor.yy0,0); }
2568 #line 2593 "pikchr.c"
2569 break;
2570 case 32: /* attribute ::= colorproperty rvalue */
2571 #line 610 "pikchr.y"
2572 { pik_set_clrprop(p,&yymsp[-1].minor.yy0,yymsp[0].minor.yy265); }
2573 #line 2598 "pikchr.c"
2574 break;
2575 case 33: /* attribute ::= go direction optrelexpr */
2576 #line 611 "pikchr.y"
2577 { pik_add_direction(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy200);}
2578 #line 2603 "pikchr.c"
2579 break;
2580 case 34: /* attribute ::= go direction even position */
2581 #line 612 "pikchr.y"
2582 {pik_evenwith(p,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy43);}
2583 #line 2608 "pikchr.c"
2584 break;
2585 case 35: /* attribute ::= CLOSE */
2586 #line 613 "pikchr.y"
2587 { pik_close_path(p,&yymsp[0].minor.yy0); }
2588 #line 2613 "pikchr.c"
2589 break;
2590 case 36: /* attribute ::= CHOP */
2591 #line 614 "pikchr.y"
2592 { p->cur->bChop = 1; }
2593 #line 2618 "pikchr.c"
2594 break;
2595 case 37: /* attribute ::= FROM position */
2596 #line 615 "pikchr.y"
2597 { pik_set_from(p,p->cur,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy43); }
2598 #line 2623 "pikchr.c"
2599 break;
2600 case 38: /* attribute ::= TO position */
2601 #line 616 "pikchr.y"
2602 { pik_add_to(p,p->cur,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy43); }
2603 #line 2628 "pikchr.c"
2604 break;
2605 case 39: /* attribute ::= THEN */
2606 #line 617 "pikchr.y"
2607 { pik_then(p, &yymsp[0].minor.yy0, p->cur); }
2608 #line 2633 "pikchr.c"
2609 break;
2610 case 40: /* attribute ::= THEN optrelexpr HEADING expr */
2611 case 42: /* attribute ::= GO optrelexpr HEADING expr */ yytestcase(yyruleno==42);
2612 #line 619 "pikchr.y"
2613 {pik_move_hdg(p,&yymsp[-2].minor.yy200,&yymsp[-1].minor.yy0,yymsp[0].minor.yy265,0,&yymsp[-3].minor.yy0);}
2614 #line 2639 "pikchr.c"
2615 break;
2616 case 41: /* attribute ::= THEN optrelexpr EDGEPT */
2617 case 43: /* attribute ::= GO optrelexpr EDGEPT */ yytestcase(yyruleno==43);
2618 #line 620 "pikchr.y"
2619 {pik_move_hdg(p,&yymsp[-1].minor.yy200,0,0,&yymsp[0].minor.yy0,&yymsp[-2].minor.yy0);}
2620 #line 2645 "pikchr.c"
2621 break;
2622 case 44: /* attribute ::= AT position */
2623 #line 625 "pikchr.y"
2624 { pik_set_at(p,0,&yymsp[0].minor.yy43,&yymsp[-1].minor.yy0); }
2625 #line 2650 "pikchr.c"
2626 break;
2627 case 45: /* attribute ::= SAME */
2628 #line 627 "pikchr.y"
2629 {pik_same(p,0,&yymsp[0].minor.yy0);}
2630 #line 2655 "pikchr.c"
2631 break;
2632 case 46: /* attribute ::= SAME AS object */
2633 #line 628 "pikchr.y"
2634 {pik_same(p,yymsp[0].minor.yy38,&yymsp[-2].minor.yy0);}
2635 #line 2660 "pikchr.c"
2636 break;
2637 case 47: /* attribute ::= STRING textposition */
2638 #line 629 "pikchr.y"
2639 {pik_add_txt(p,&yymsp[-1].minor.yy0,yymsp[0].minor.yy196);}
2640 #line 2665 "pikchr.c"
2641 break;
2642 case 48: /* attribute ::= FIT */
2643 #line 630 "pikchr.y"
2644 {pik_size_to_fit(p,&yymsp[0].minor.yy0,3); }
2645 #line 2670 "pikchr.c"
2646 break;
2647 case 49: /* attribute ::= BEHIND object */
2648 #line 631 "pikchr.y"
2649 {pik_behind(p,yymsp[0].minor.yy38);}
2650 #line 2675 "pikchr.c"
2651 break;
2652 case 50: /* withclause ::= DOT_E edge AT position */
2653 case 51: /* withclause ::= edge AT position */ yytestcase(yyruleno==51);
2654 #line 639 "pikchr.y"
2655 { pik_set_at(p,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy43,&yymsp[-1].minor.yy0); }
2656 #line 2681 "pikchr.c"
2657 break;
2658 case 52: /* numproperty ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS */
2659 #line 643 "pikchr.y"
2660 {yylhsminor.yy0 = yymsp[0].minor.yy0;}
2661 #line 2686 "pikchr.c"
2662 yymsp[0].minor.yy0 = yylhsminor.yy0;
2663 break;
2664 case 53: /* boolproperty ::= CW */
2665 #line 654 "pikchr.y"
2666 {p->cur->cw = 1;}
2667 #line 2692 "pikchr.c"
2668 break;
2669 case 54: /* boolproperty ::= CCW */
2670 #line 655 "pikchr.y"
2671 {p->cur->cw = 0;}
2672 #line 2697 "pikchr.c"
2673 break;
2674 case 55: /* boolproperty ::= LARROW */
2675 #line 656 "pikchr.y"
2676 {p->cur->larrow=1; p->cur->rarrow=0; }
2677 #line 2702 "pikchr.c"
2678 break;
2679 case 56: /* boolproperty ::= RARROW */
2680 #line 657 "pikchr.y"
2681 {p->cur->larrow=0; p->cur->rarrow=1; }
2682 #line 2707 "pikchr.c"
2683 break;
2684 case 57: /* boolproperty ::= LRARROW */
2685 #line 658 "pikchr.y"
2686 {p->cur->larrow=1; p->cur->rarrow=1; }
2687 #line 2712 "pikchr.c"
2688 break;
2689 case 58: /* boolproperty ::= INVIS */
2690 #line 659 "pikchr.y"
2691 {p->cur->sw = 0.0;}
2692 #line 2717 "pikchr.c"
2693 break;
2694 case 59: /* boolproperty ::= THICK */
2695 #line 660 "pikchr.y"
2696 {p->cur->sw *= 1.5;}
2697 #line 2722 "pikchr.c"
2698 break;
2699 case 60: /* boolproperty ::= THIN */
2700 #line 661 "pikchr.y"
2701 {p->cur->sw *= 0.67;}
2702 #line 2727 "pikchr.c"
2703 break;
2704 case 61: /* boolproperty ::= SOLID */
2705 #line 662 "pikchr.y"
2706 {p->cur->sw = pik_value(p,"thickness",9,0);
2707 p->cur->dotted = p->cur->dashed = 0.0;}
2708 #line 2733 "pikchr.c"
2709 break;
2710 case 62: /* textposition ::= */
2711 #line 665 "pikchr.y"
2712 {yymsp[1].minor.yy196 = 0;}
2713 #line 2738 "pikchr.c"
2714 break;
2715 case 63: /* textposition ::= textposition CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|ALIGNED|BIG|SMALL */
2716 #line 668 "pikchr.y"
2717 {yylhsminor.yy196 = pik_text_position(yymsp[-1].minor.yy196,&yymsp[0].minor.yy0);}
2718 #line 2743 "pikchr.c"
2719 yymsp[-1].minor.yy196 = yylhsminor.yy196;
2720 break;
2721 case 64: /* position ::= expr COMMA expr */
2722 #line 671 "pikchr.y"
2723 {yylhsminor.yy43.x=yymsp[-2].minor.yy265; yylhsminor.yy43.y=yymsp[0].minor.yy265;}
2724 #line 2749 "pikchr.c"
2725 yymsp[-2].minor.yy43 = yylhsminor.yy43;
2726 break;
2727 case 65: /* position ::= place PLUS expr COMMA expr */
2728 #line 673 "pikchr.y"
2729 {yylhsminor.yy43.x=yymsp[-4].minor.yy43.x+yymsp[-2].minor.yy265; yylhsminor.yy43.y=yymsp[-4].minor.yy43.y+yymsp[0].minor.yy265;}
2730 #line 2755 "pikchr.c"
2731 yymsp[-4].minor.yy43 = yylhsminor.yy43;
2732 break;
2733 case 66: /* position ::= place MINUS expr COMMA expr */
2734 #line 674 "pikchr.y"
2735 {yylhsminor.yy43.x=yymsp[-4].minor.yy43.x-yymsp[-2].minor.yy265; yylhsminor.yy43.y=yymsp[-4].minor.yy43.y-yymsp[0].minor.yy265;}
2736 #line 2761 "pikchr.c"
2737 yymsp[-4].minor.yy43 = yylhsminor.yy43;
2738 break;
2739 case 67: /* position ::= place PLUS LP expr COMMA expr RP */
2740 #line 676 "pikchr.y"
2741 {yylhsminor.yy43.x=yymsp[-6].minor.yy43.x+yymsp[-3].minor.yy265; yylhsminor.yy43.y=yymsp[-6].minor.yy43.y+yymsp[-1].minor.yy265;}
2742 #line 2767 "pikchr.c"
2743 yymsp[-6].minor.yy43 = yylhsminor.yy43;
2744 break;
2745 case 68: /* position ::= place MINUS LP expr COMMA expr RP */
2746 #line 678 "pikchr.y"
2747 {yylhsminor.yy43.x=yymsp[-6].minor.yy43.x-yymsp[-3].minor.yy265; yylhsminor.yy43.y=yymsp[-6].minor.yy43.y-yymsp[-1].minor.yy265;}
2748 #line 2773 "pikchr.c"
2749 yymsp[-6].minor.yy43 = yylhsminor.yy43;
2750 break;
2751 case 69: /* position ::= LP position COMMA position RP */
2752 #line 679 "pikchr.y"
2753 {yymsp[-4].minor.yy43.x=yymsp[-3].minor.yy43.x; yymsp[-4].minor.yy43.y=yymsp[-1].minor.yy43.y;}
2754 #line 2779 "pikchr.c"
2755 break;
2756 case 70: /* position ::= LP position RP */
2757 #line 680 "pikchr.y"
2758 {yymsp[-2].minor.yy43=yymsp[-1].minor.yy43;}
2759 #line 2784 "pikchr.c"
2760 break;
2761 case 71: /* position ::= expr between position AND position */
2762 #line 682 "pikchr.y"
2763 {yylhsminor.yy43 = pik_position_between(yymsp[-4].minor.yy265,yymsp[-2].minor.yy43,yymsp[0].minor.yy43);}
2764 #line 2789 "pikchr.c"
2765 yymsp[-4].minor.yy43 = yylhsminor.yy43;
2766 break;
2767 case 72: /* position ::= expr LT position COMMA position GT */
2768 #line 684 "pikchr.y"
2769 {yylhsminor.yy43 = pik_position_between(yymsp[-5].minor.yy265,yymsp[-3].minor.yy43,yymsp[-1].minor.yy43);}
2770 #line 2795 "pikchr.c"
2771 yymsp[-5].minor.yy43 = yylhsminor.yy43;
2772 break;
2773 case 73: /* position ::= expr ABOVE position */
2774 #line 685 "pikchr.y"
2775 {yylhsminor.yy43=yymsp[0].minor.yy43; yylhsminor.yy43.y += yymsp[-2].minor.yy265;}
2776 #line 2801 "pikchr.c"
2777 yymsp[-2].minor.yy43 = yylhsminor.yy43;
2778 break;
2779 case 74: /* position ::= expr BELOW position */
2780 #line 686 "pikchr.y"
2781 {yylhsminor.yy43=yymsp[0].minor.yy43; yylhsminor.yy43.y -= yymsp[-2].minor.yy265;}
2782 #line 2807 "pikchr.c"
2783 yymsp[-2].minor.yy43 = yylhsminor.yy43;
2784 break;
2785 case 75: /* position ::= expr LEFT OF position */
2786 #line 687 "pikchr.y"
2787 {yylhsminor.yy43=yymsp[0].minor.yy43; yylhsminor.yy43.x -= yymsp[-3].minor.yy265;}
2788 #line 2813 "pikchr.c"
2789 yymsp[-3].minor.yy43 = yylhsminor.yy43;
2790 break;
2791 case 76: /* position ::= expr RIGHT OF position */
2792 #line 688 "pikchr.y"
2793 {yylhsminor.yy43=yymsp[0].minor.yy43; yylhsminor.yy43.x += yymsp[-3].minor.yy265;}
2794 #line 2819 "pikchr.c"
2795 yymsp[-3].minor.yy43 = yylhsminor.yy43;
2796 break;
2797 case 77: /* position ::= expr ON HEADING EDGEPT OF position */
2798 #line 690 "pikchr.y"
2799 {yylhsminor.yy43 = pik_position_at_hdg(yymsp[-5].minor.yy265,&yymsp[-2].minor.yy0,yymsp[0].minor.yy43);}
2800 #line 2825 "pikchr.c"
2801 yymsp[-5].minor.yy43 = yylhsminor.yy43;
2802 break;
2803 case 78: /* position ::= expr HEADING EDGEPT OF position */
2804 #line 692 "pikchr.y"
2805 {yylhsminor.yy43 = pik_position_at_hdg(yymsp[-4].minor.yy265,&yymsp[-2].minor.yy0,yymsp[0].minor.yy43);}
2806 #line 2831 "pikchr.c"
2807 yymsp[-4].minor.yy43 = yylhsminor.yy43;
2808 break;
2809 case 79: /* position ::= expr EDGEPT OF position */
2810 #line 694 "pikchr.y"
2811 {yylhsminor.yy43 = pik_position_at_hdg(yymsp[-3].minor.yy265,&yymsp[-2].minor.yy0,yymsp[0].minor.yy43);}
2812 #line 2837 "pikchr.c"
2813 yymsp[-3].minor.yy43 = yylhsminor.yy43;
2814 break;
2815 case 80: /* position ::= expr ON HEADING expr FROM position */
2816 #line 696 "pikchr.y"
2817 {yylhsminor.yy43 = pik_position_at_angle(yymsp[-5].minor.yy265,yymsp[-2].minor.yy265,yymsp[0].minor.yy43);}
2818 #line 2843 "pikchr.c"
2819 yymsp[-5].minor.yy43 = yylhsminor.yy43;
2820 break;
2821 case 81: /* position ::= expr HEADING expr FROM position */
2822 #line 698 "pikchr.y"
2823 {yylhsminor.yy43 = pik_position_at_angle(yymsp[-4].minor.yy265,yymsp[-2].minor.yy265,yymsp[0].minor.yy43);}
2824 #line 2849 "pikchr.c"
2825 yymsp[-4].minor.yy43 = yylhsminor.yy43;
2826 break;
2827 case 82: /* place ::= edge OF object */
2828 #line 710 "pikchr.y"
2829 {yylhsminor.yy43 = pik_place_of_elem(p,yymsp[0].minor.yy38,&yymsp[-2].minor.yy0);}
2830 #line 2855 "pikchr.c"
2831 yymsp[-2].minor.yy43 = yylhsminor.yy43;
2832 break;
2833 case 83: /* place2 ::= object */
2834 #line 711 "pikchr.y"
2835 {yylhsminor.yy43 = pik_place_of_elem(p,yymsp[0].minor.yy38,0);}
2836 #line 2861 "pikchr.c"
2837 yymsp[0].minor.yy43 = yylhsminor.yy43;
2838 break;
2839 case 84: /* place2 ::= object DOT_E edge */
2840 #line 712 "pikchr.y"
2841 {yylhsminor.yy43 = pik_place_of_elem(p,yymsp[-2].minor.yy38,&yymsp[0].minor.yy0);}
2842 #line 2867 "pikchr.c"
2843 yymsp[-2].minor.yy43 = yylhsminor.yy43;
2844 break;
2845 case 85: /* place2 ::= NTH VERTEX OF object */
2846 #line 713 "pikchr.y"
2847 {yylhsminor.yy43 = pik_nth_vertex(p,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,yymsp[0].minor.yy38);}
2848 #line 2873 "pikchr.c"
2849 yymsp[-3].minor.yy43 = yylhsminor.yy43;
2850 break;
2851 case 86: /* object ::= nth */
2852 #line 725 "pikchr.y"
2853 {yylhsminor.yy38 = pik_find_nth(p,0,&yymsp[0].minor.yy0);}
2854 #line 2879 "pikchr.c"
2855 yymsp[0].minor.yy38 = yylhsminor.yy38;
2856 break;
2857 case 87: /* object ::= nth OF|IN object */
2858 #line 726 "pikchr.y"
2859 {yylhsminor.yy38 = pik_find_nth(p,yymsp[0].minor.yy38,&yymsp[-2].minor.yy0);}
2860 #line 2885 "pikchr.c"
2861 yymsp[-2].minor.yy38 = yylhsminor.yy38;
2862 break;
2863 case 88: /* objectname ::= PLACENAME */
2864 #line 728 "pikchr.y"
2865 {yylhsminor.yy38 = pik_find_byname(p,0,&yymsp[0].minor.yy0);}
2866 #line 2891 "pikchr.c"
2867 yymsp[0].minor.yy38 = yylhsminor.yy38;
2868 break;
2869 case 89: /* objectname ::= objectname DOT_U PLACENAME */
2870 #line 730 "pikchr.y"
2871 {yylhsminor.yy38 = pik_find_byname(p,yymsp[-2].minor.yy38,&yymsp[0].minor.yy0);}
2872 #line 2897 "pikchr.c"
2873 yymsp[-2].minor.yy38 = yylhsminor.yy38;
2874 break;
2875 case 90: /* nth ::= NTH CLASSNAME */
2876 #line 732 "pikchr.y"
2877 {yylhsminor.yy0=yymsp[0].minor.yy0; yylhsminor.yy0.eCode = pik_nth_value(p,&yymsp[-1].minor.yy0); }
2878 #line 2903 "pikchr.c"
2879 yymsp[-1].minor.yy0 = yylhsminor.yy0;
2880 break;
2881 case 91: /* nth ::= NTH LAST CLASSNAME */
2882 #line 733 "pikchr.y"
2883 {yylhsminor.yy0=yymsp[0].minor.yy0; yylhsminor.yy0.eCode = -pik_nth_value(p,&yymsp[-2].minor.yy0); }
2884 #line 2909 "pikchr.c"
2885 yymsp[-2].minor.yy0 = yylhsminor.yy0;
2886 break;
2887 case 92: /* nth ::= LAST CLASSNAME */
2888 #line 734 "pikchr.y"
2889 {yymsp[-1].minor.yy0=yymsp[0].minor.yy0; yymsp[-1].minor.yy0.eCode = -1;}
2890 #line 2915 "pikchr.c"
2891 break;
2892 case 93: /* nth ::= LAST */
2893 #line 735 "pikchr.y"
2894 {yylhsminor.yy0=yymsp[0].minor.yy0; yylhsminor.yy0.eCode = -1;}
2895 #line 2920 "pikchr.c"
2896 yymsp[0].minor.yy0 = yylhsminor.yy0;
2897 break;
2898 case 94: /* nth ::= NTH LB RB */
2899 #line 736 "pikchr.y"
2900 {yylhsminor.yy0=yymsp[-1].minor.yy0; yylhsminor.yy0.eCode = pik_nth_value(p,&yymsp[-2].minor.yy0);}
2901 #line 2926 "pikchr.c"
2902 yymsp[-2].minor.yy0 = yylhsminor.yy0;
2903 break;
2904 case 95: /* nth ::= NTH LAST LB RB */
2905 #line 737 "pikchr.y"
2906 {yylhsminor.yy0=yymsp[-1].minor.yy0; yylhsminor.yy0.eCode = -pik_nth_value(p,&yymsp[-3].minor.yy0);}
2907 #line 2932 "pikchr.c"
2908 yymsp[-3].minor.yy0 = yylhsminor.yy0;
2909 break;
2910 case 96: /* nth ::= LAST LB RB */
2911 #line 738 "pikchr.y"
2912 {yymsp[-2].minor.yy0=yymsp[-1].minor.yy0; yymsp[-2].minor.yy0.eCode = -1; }
2913 #line 2938 "pikchr.c"
2914 break;
2915 case 97: /* expr ::= expr PLUS expr */
2916 #line 740 "pikchr.y"
2917 {yylhsminor.yy265=yymsp[-2].minor.yy265+yymsp[0].minor.yy265;}
2918 #line 2943 "pikchr.c"
2919 yymsp[-2].minor.yy265 = yylhsminor.yy265;
2920 break;
2921 case 98: /* expr ::= expr MINUS expr */
2922 #line 741 "pikchr.y"
2923 {yylhsminor.yy265=yymsp[-2].minor.yy265-yymsp[0].minor.yy265;}
2924 #line 2949 "pikchr.c"
2925 yymsp[-2].minor.yy265 = yylhsminor.yy265;
2926 break;
2927 case 99: /* expr ::= expr STAR expr */
2928 #line 742 "pikchr.y"
2929 {yylhsminor.yy265=yymsp[-2].minor.yy265*yymsp[0].minor.yy265;}
2930 #line 2955 "pikchr.c"
2931 yymsp[-2].minor.yy265 = yylhsminor.yy265;
2932 break;
2933 case 100: /* expr ::= expr SLASH expr */
2934 #line 743 "pikchr.y"
2935 {
2936 if( yymsp[0].minor.yy265==0.0 ){ pik_error(p, &yymsp[-1].minor.yy0, "division by zero"); yylhsminor.yy265 = 0.0; }
2937 else{ yylhsminor.yy265 = yymsp[-2].minor.yy265/yymsp[0].minor.yy265; }
2938 }
2939 #line 2964 "pikchr.c"
2940 yymsp[-2].minor.yy265 = yylhsminor.yy265;
2941 break;
2942 case 101: /* expr ::= MINUS expr */
2943 #line 747 "pikchr.y"
2944 {yymsp[-1].minor.yy265=-yymsp[0].minor.yy265;}
2945 #line 2970 "pikchr.c"
2946 break;
2947 case 102: /* expr ::= PLUS expr */
2948 #line 748 "pikchr.y"
2949 {yymsp[-1].minor.yy265=yymsp[0].minor.yy265;}
2950 #line 2975 "pikchr.c"
2951 break;
2952 case 103: /* expr ::= LP expr RP */
2953 #line 749 "pikchr.y"
2954 {yymsp[-2].minor.yy265=yymsp[-1].minor.yy265;}
2955 #line 2980 "pikchr.c"
2956 break;
2957 case 104: /* expr ::= LP FILL|COLOR|THICKNESS RP */
2958 #line 750 "pikchr.y"
2959 {yymsp[-2].minor.yy265=pik_get_var(p,&yymsp[-1].minor.yy0);}
2960 #line 2985 "pikchr.c"
2961 break;
2962 case 105: /* expr ::= NUMBER */
2963 #line 751 "pikchr.y"
2964 {yylhsminor.yy265=pik_atof(&yymsp[0].minor.yy0);}
2965 #line 2990 "pikchr.c"
2966 yymsp[0].minor.yy265 = yylhsminor.yy265;
2967 break;
2968 case 106: /* expr ::= ID */
2969 #line 752 "pikchr.y"
2970 {yylhsminor.yy265=pik_get_var(p,&yymsp[0].minor.yy0);}
2971 #line 2996 "pikchr.c"
2972 yymsp[0].minor.yy265 = yylhsminor.yy265;
2973 break;
2974 case 107: /* expr ::= FUNC1 LP expr RP */
2975 #line 753 "pikchr.y"
2976 {yylhsminor.yy265 = pik_func(p,&yymsp[-3].minor.yy0,yymsp[-1].minor.yy265,0.0);}
2977 #line 3002 "pikchr.c"
2978 yymsp[-3].minor.yy265 = yylhsminor.yy265;
2979 break;
2980 case 108: /* expr ::= FUNC2 LP expr COMMA expr RP */
2981 #line 754 "pikchr.y"
2982 {yylhsminor.yy265 = pik_func(p,&yymsp[-5].minor.yy0,yymsp[-3].minor.yy265,yymsp[-1].minor.yy265);}
2983 #line 3008 "pikchr.c"
2984 yymsp[-5].minor.yy265 = yylhsminor.yy265;
2985 break;
2986 case 109: /* expr ::= DIST LP position COMMA position RP */
2987 #line 755 "pikchr.y"
2988 {yymsp[-5].minor.yy265 = pik_dist(&yymsp[-3].minor.yy43,&yymsp[-1].minor.yy43);}
2989 #line 3014 "pikchr.c"
2990 break;
2991 case 110: /* expr ::= place2 DOT_XY X */
2992 #line 756 "pikchr.y"
2993 {yylhsminor.yy265 = yymsp[-2].minor.yy43.x;}
2994 #line 3019 "pikchr.c"
2995 yymsp[-2].minor.yy265 = yylhsminor.yy265;
2996 break;
2997 case 111: /* expr ::= place2 DOT_XY Y */
2998 #line 757 "pikchr.y"
2999 {yylhsminor.yy265 = yymsp[-2].minor.yy43.y;}
3000 #line 3025 "pikchr.c"
3001 yymsp[-2].minor.yy265 = yylhsminor.yy265;
3002 break;
3003 case 112: /* expr ::= object DOT_L numproperty */
3004 case 113: /* expr ::= object DOT_L dashproperty */ yytestcase(yyruleno==113);
3005 case 114: /* expr ::= object DOT_L colorproperty */ yytestcase(yyruleno==114);
3006 #line 758 "pikchr.y"
3007 {yylhsminor.yy265=pik_property_of(yymsp[-2].minor.yy38,&yymsp[0].minor.yy0);}
3008 #line 3033 "pikchr.c"
3009 yymsp[-2].minor.yy265 = yylhsminor.yy265;
3010 break;
3011 default:
3012 /* (115) lvalue ::= ID */ yytestcase(yyruleno==115);
3013 /* (116) lvalue ::= FILL */ yytestcase(yyruleno==116);
@@ -3101,19 +3106,19 @@
3106 ){
3107 pik_parserARG_FETCH
3108 pik_parserCTX_FETCH
3109 #define TOKEN yyminor
3110 /************ Begin %syntax_error code ****************************************/
3111 #line 519 "pikchr.y"
3112
3113 if( TOKEN.z && TOKEN.z[0] ){
3114 pik_error(p, &TOKEN, "syntax error");
3115 }else{
3116 pik_error(p, 0, "syntax error");
3117 }
3118 UNUSED_PARAMETER(yymajor);
3119 #line 3144 "pikchr.c"
3120 /************ End %syntax_error code ******************************************/
3121 pik_parserARG_STORE /* Suppress warning about unused %extra_argument variable */
3122 pik_parserCTX_STORE
3123 }
3124
@@ -3342,16 +3347,17 @@
3347 #else
3348 (void)iToken;
3349 return 0;
3350 #endif
3351 }
3352 #line 763 "pikchr.y"
3353
3354
3355
3356 /* Chart of the 148 official CSS color names with their
3357 ** corresponding RGB values thru Color Module Level 4:
3358 ** https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
3359 **
3360 ** Two new names "None" and "Off" are added with a value
3361 ** of -1.
3362 */
3363 static const struct {
@@ -3359,11 +3365,11 @@
3365 int val; /* RGB value */
3366 } aColor[] = {
3367 { "AliceBlue", 0xf0f8ff },
3368 { "AntiqueWhite", 0xfaebd7 },
3369 { "Aqua", 0x00ffff },
3370 { "Aquamarine", 0x7fffd4 },
3371 { "Azure", 0xf0ffff },
3372 { "Beige", 0xf5f5dc },
3373 { "Bisque", 0xffe4c4 },
3374 { "Black", 0x000000 },
3375 { "BlanchedAlmond", 0xffebcd },
@@ -3373,19 +3379,20 @@
3379 { "BurlyWood", 0xdeb887 },
3380 { "CadetBlue", 0x5f9ea0 },
3381 { "Chartreuse", 0x7fff00 },
3382 { "Chocolate", 0xd2691e },
3383 { "Coral", 0xff7f50 },
3384 { "CornflowerBlue", 0x6495ed },
3385 { "Cornsilk", 0xfff8dc },
3386 { "Crimson", 0xdc143c },
3387 { "Cyan", 0x00ffff },
3388 { "DarkBlue", 0x00008b },
3389 { "DarkCyan", 0x008b8b },
3390 { "DarkGoldenrod", 0xb8860b },
3391 { "DarkGray", 0xa9a9a9 },
3392 { "DarkGreen", 0x006400 },
3393 { "DarkGrey", 0xa9a9a9 },
3394 { "DarkKhaki", 0xbdb76b },
3395 { "DarkMagenta", 0x8b008b },
3396 { "DarkOliveGreen", 0x556b2f },
3397 { "DarkOrange", 0xff8c00 },
3398 { "DarkOrchid", 0x9932cc },
@@ -3392,28 +3399,31 @@
3399 { "DarkRed", 0x8b0000 },
3400 { "DarkSalmon", 0xe9967a },
3401 { "DarkSeaGreen", 0x8fbc8f },
3402 { "DarkSlateBlue", 0x483d8b },
3403 { "DarkSlateGray", 0x2f4f4f },
3404 { "DarkSlateGrey", 0x2f4f4f },
3405 { "DarkTurquoise", 0x00ced1 },
3406 { "DarkViolet", 0x9400d3 },
3407 { "DeepPink", 0xff1493 },
3408 { "DeepSkyBlue", 0x00bfff },
3409 { "DimGray", 0x696969 },
3410 { "DimGrey", 0x696969 },
3411 { "DodgerBlue", 0x1e90ff },
3412 { "Firebrick", 0xb22222 },
3413 { "FloralWhite", 0xfffaf0 },
3414 { "ForestGreen", 0x228b22 },
3415 { "Fuchsia", 0xff00ff },
3416 { "Gainsboro", 0xdcdcdc },
3417 { "GhostWhite", 0xf8f8ff },
3418 { "Gold", 0xffd700 },
3419 { "Goldenrod", 0xdaa520 },
3420 { "Gray", 0x808080 },
3421 { "Green", 0x008000 },
3422 { "GreenYellow", 0xadff2f },
3423 { "Grey", 0x808080 },
3424 { "Honeydew", 0xf0fff0 },
3425 { "HotPink", 0xff69b4 },
3426 { "IndianRed", 0xcd5c5c },
3427 { "Indigo", 0x4b0082 },
3428 { "Ivory", 0xfffff0 },
3429 { "Khaki", 0xf0e68c },
@@ -3425,26 +3435,28 @@
3435 { "LightCoral", 0xf08080 },
3436 { "LightCyan", 0xe0ffff },
3437 { "LightGoldenrodYellow", 0xfafad2 },
3438 { "LightGray", 0xd3d3d3 },
3439 { "LightGreen", 0x90ee90 },
3440 { "LightGrey", 0xd3d3d3 },
3441 { "LightPink", 0xffb6c1 },
3442 { "LightSalmon", 0xffa07a },
3443 { "LightSeaGreen", 0x20b2aa },
3444 { "LightSkyBlue", 0x87cefa },
3445 { "LightSlateGray", 0x778899 },
3446 { "LightSlateGrey", 0x778899 },
3447 { "LightSteelBlue", 0xb0c4de },
3448 { "LightYellow", 0xffffe0 },
3449 { "Lime", 0x00ff00 },
3450 { "LimeGreen", 0x32cd32 },
3451 { "Linen", 0xfaf0e6 },
3452 { "Magenta", 0xff00ff },
3453 { "Maroon", 0x800000 },
3454 { "MediumAquamarine", 0x66cdaa },
3455 { "MediumBlue", 0x0000cd },
3456 { "MediumOrchid", 0xba55d3 },
3457 { "MediumPurple", 0x9370db },
3458 { "MediumSeaGreen", 0x3cb371 },
3459 { "MediumSlateBlue", 0x7b68ee },
3460 { "MediumSpringGreen", 0x00fa9a },
3461 { "MediumTurquoise", 0x48d1cc },
3462 { "MediumVioletRed", 0xc71585 },
@@ -3460,11 +3472,11 @@
3472 { "Olive", 0x808000 },
3473 { "OliveDrab", 0x6b8e23 },
3474 { "Orange", 0xffa500 },
3475 { "OrangeRed", 0xff4500 },
3476 { "Orchid", 0xda70d6 },
3477 { "PaleGoldenrod", 0xeee8aa },
3478 { "PaleGreen", 0x98fb98 },
3479 { "PaleTurquoise", 0xafeeee },
3480 { "PaleVioletRed", 0xdb7093 },
3481 { "PapayaWhip", 0xffefd5 },
3482 { "PeachPuff", 0xffdab9 },
@@ -3471,23 +3483,25 @@
3483 { "Peru", 0xcd853f },
3484 { "Pink", 0xffc0cb },
3485 { "Plum", 0xdda0dd },
3486 { "PowderBlue", 0xb0e0e6 },
3487 { "Purple", 0x800080 },
3488 { "RebeccaPurple", 0x663399 },
3489 { "Red", 0xff0000 },
3490 { "RosyBrown", 0xbc8f8f },
3491 { "RoyalBlue", 0x4169e1 },
3492 { "SaddleBrown", 0x8b4513 },
3493 { "Salmon", 0xfa8072 },
3494 { "SandyBrown", 0xf4a460 },
3495 { "SeaGreen", 0x2e8b57 },
3496 { "Seashell", 0xfff5ee },
3497 { "Sienna", 0xa0522d },
3498 { "Silver", 0xc0c0c0 },
3499 { "SkyBlue", 0x87ceeb },
3500 { "SlateBlue", 0x6a5acd },
3501 { "SlateGray", 0x708090 },
3502 { "SlateGrey", 0x708090 },
3503 { "Snow", 0xfffafa },
3504 { "SpringGreen", 0x00ff7f },
3505 { "SteelBlue", 0x4682b4 },
3506 { "Tan", 0xd2b48c },
3507 { "Teal", 0x008080 },
@@ -4445,11 +4459,11 @@
4459 bx = f->x + e1*dx;
4460 by = f->y + e1*dy;
4461 pik_append_xy(p,"<polygon points=\"", t->x, t->y);
4462 pik_append_xy(p," ",bx-ddx, by-ddy);
4463 pik_append_xy(p," ",bx+ddx, by+ddy);
4464 pik_append_clr(p,"\" style=\"fill:",pObj->color,"\"/>\n",0);
4465 pik_chop(f,t,h/2);
4466 }
4467
4468 /*
4469 ** Compute the relative offset to an edge location from the reference for a
@@ -4552,10 +4566,45 @@
4566 (double)pPt->x, (double)pPt->y);
4567 buf[sizeof(buf)-1] = 0;
4568 pik_append(p, z, -1);
4569 pik_append(p, buf, -1);
4570 }
4571
4572 /*
4573 ** Invert the RGB color so that it is appropriate for dark mode.
4574 */
4575 static int pik_color_to_dark_mode(int x, int isBg){
4576 int r, g, b;
4577 int mn, mx;
4578 x = 0xffffff - x;
4579 r = (x>>16) & 0xff;
4580 g = (x>>8) & 0xff;
4581 b = x & 0xff;
4582 mx = r;
4583 if( g>mx ) mx = g;
4584 if( b>mx ) mx = b;
4585 mn = r;
4586 if( g<mn ) mn = g;
4587 if( b<mn ) mn = b;
4588 r = mn + (mx-r);
4589 g = mn + (mx-g);
4590 b = mn + (mx-b);
4591 if( isBg ){
4592 if( mx>127 ){
4593 r = (127*r)/mx;
4594 g = (127*g)/mx;
4595 b = (127*b)/mx;
4596 }
4597 }else{
4598 if( mn<128 && mx>mn ){
4599 r = 127 + ((r-mn)*128)/(mx-mn);
4600 g = 127 + ((g-mn)*128)/(mx-mn);
4601 b = 127 + ((b-mn)*128)/(mx-mn);
4602 }
4603 }
4604 return r*0x10000 + g*0x100 + b;
4605 }
4606
4607 /* Append a PNum value surrounded by text. Do coordinate transformations
4608 ** on the value.
4609 */
4610 static void pik_append_x(Pik *p, const char *z1, PNum v, const char *z2){
@@ -4585,16 +4634,22 @@
4634 char buf[200];
4635 snprintf(buf, sizeof(buf)-1, "%s%g%s", z1, p->rScale*v, z2);
4636 buf[sizeof(buf)-1] = 0;
4637 pik_append(p, buf, -1);
4638 }
4639 static void pik_append_clr(Pik *p,const char *z1,PNum v,const char *z2,int bg){
4640 char buf[200];
4641 int x = (int)v;
4642 int r, g, b;
4643 if( x==0 && p->fgcolor>0 && !bg ){
4644 x = p->fgcolor;
4645 }else if( p->mFlags & PIKCHR_DARK_MODE ){
4646 x = pik_color_to_dark_mode(x,bg);
4647 }
4648 r = (x>>16) & 0xff;
4649 g = (x>>8) & 0xff;
4650 b = x & 0xff;
4651 snprintf(buf, sizeof(buf)-1, "%srgb(%d,%d,%d)%s", z1, r, g, b, z2);
4652 buf[sizeof(buf)-1] = 0;
4653 pik_append(p, buf, -1);
4654 }
4655
@@ -4617,21 +4672,21 @@
4672 ** the caller wants to add some more.
4673 */
4674 static void pik_append_style(Pik *p, PObj *pObj, int bFill){
4675 pik_append(p, " style=\"", -1);
4676 if( pObj->fill>=0 && bFill ){
4677 pik_append_clr(p, "fill:", pObj->fill, ";",1);
4678 }else{
4679 pik_append(p,"fill:none;",-1);
4680 }
4681 if( pObj->sw>0.0 && pObj->color>=0.0 ){
4682 PNum sw = pObj->sw;
4683 pik_append_dis(p, "stroke-width:", sw, ";");
4684 if( pObj->nPath>2 && pObj->rad<=pObj->sw ){
4685 pik_append(p, "stroke-linejoin:round;", -1);
4686 }
4687 pik_append_clr(p, "stroke:",pObj->color,";",0);
4688 if( pObj->dotted>0.0 ){
4689 PNum v = pObj->dotted;
4690 if( sw<2.1/p->rScale ) sw = 2.1/p->rScale;
4691 pik_append_dis(p,"stroke-dasharray:",sw,"");
4692 pik_append_dis(p,",",v,";");
@@ -4882,11 +4937,11 @@
4937 }
4938 if( t->eCode & TP_BOLD ){
4939 pik_append(p, " font-weight=\"bold\"", -1);
4940 }
4941 if( pObj->color>=0.0 ){
4942 pik_append_clr(p, " fill=\"", pObj->color, "\"",0);
4943 }
4944 xtraFontScale *= p->fontScale;
4945 if( xtraFontScale<=0.99 || xtraFontScale>=1.01 ){
4946 pik_append_num(p, " font-size=\"", xtraFontScale*100.0);
4947 pik_append(p, "%\"", 2);
@@ -6827,18 +6882,26 @@
6882 PNum thickness; /* Stroke width */
6883 PNum margin; /* Extra bounding box margin */
6884 PNum w, h; /* Drawing width and height */
6885 PNum wArrow;
6886 PNum pikScale; /* Value of the "scale" variable */
6887 int miss = 0;
6888
6889 /* Set up rendering parameters */
6890 pik_compute_layout_settings(p);
6891 thickness = pik_value(p,"thickness",9,0);
6892 if( thickness<=0.01 ) thickness = 0.01;
6893 margin = pik_value(p,"margin",6,0);
6894 margin += thickness;
6895 wArrow = p->wArrow*thickness;
6896 p->fgcolor = (int)pik_value(p,"fgcolor",7,&miss);
6897 if( miss ){
6898 PToken t;
6899 t.z = "fgcolor";
6900 t.n = 7;
6901 p->fgcolor = (int)pik_lookup_color(0, &t);
6902 }
6903
6904 /* Compute a bounding box over all objects so that we can know
6905 ** how big to declare the SVG canvas */
6906 pik_bbox_init(&p->bbox);
6907 pik_bbox_add_elist(p, pList, wArrow);
@@ -7620,10 +7683,11 @@
7683 int i;
7684 int bSvgOnly = 0; /* Output SVG only. No HTML wrapper */
7685 int bDontStop = 0; /* Continue in spite of errors */
7686 int exitCode = 0; /* What to return */
7687 int mFlags = 0; /* mFlags argument to pikchr() */
7688 const char *zStyle = ""; /* Extra styling */
7689 const char *zHtmlHdr =
7690 "<!DOCTYPE html>\n"
7691 "<html lang=\"en-US\">\n"
7692 "<head>\n<title>PIKCHR Test</title>\n"
7693 "<style>\n"
@@ -7657,10 +7721,14 @@
7721 char *z = argv[i];
7722 z++;
7723 if( z[0]=='-' ) z++;
7724 if( strcmp(z,"dont-stop")==0 ){
7725 bDontStop = 1;
7726 }else
7727 if( strcmp(z,"dark-mode")==0 ){
7728 zStyle = "color:white;background-color:black;";
7729 mFlags |= PIKCHR_DARK_MODE;
7730 }else
7731 if( strcmp(z,"svg-only")==0 ){
7732 if( zHtmlHdr==0 ){
7733 fprintf(stderr, "the \"%s\" option must come first\n",argv[i]);
7734 exit(1);
@@ -7706,11 +7774,12 @@
7774 printf("<h1>File %s</h1>\n", argv[i]);
7775 if( w<0 ){
7776 printf("<p>ERROR</p>\n%s\n", zOut);
7777 }else{
7778 printf("<div id=\"svg-%d\" onclick=\"toggleHidden('svg-%d')\">\n",i,i);
7779 printf("<div style='border:3px solid lightgray;max-width:%dpx;%s'>\n",
7780 w,zStyle);
7781 printf("%s</div>\n", zOut);
7782 printf("<pre class='hidden'>");
7783 print_escape_html(zIn);
7784 printf("</pre>\n</div>\n");
7785 }
@@ -7783,6 +7852,6 @@
7852
7853
7854 #endif /* PIKCHR_TCL */
7855
7856
7857 #line 7882 "pikchr.c"
7858
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -22,16 +22,17 @@
2222
#include <ctype.h>
2323
#include "pikchrshow.h"
2424
2525
#if INTERFACE
2626
/* These are described in pikchr_process()'s docs. */
27
-#define PIKCHR_PROCESS_TH1 0x01
28
-#define PIKCHR_PROCESS_TH1_NOSVG 0x02
29
-#define PIKCHR_PROCESS_NONCE 0x04
30
-#define PIKCHR_PROCESS_ERR_PRE 0x08
31
-#define PIKCHR_PROCESS_SRC 0x10
32
-#define PIKCHR_PROCESS_DIV 0x20
27
+#define PIKCHR_PROCESS_PASSTHROUGH 0x0003 /* Pass through these flags */
28
+#define PIKCHR_PROCESS_TH1 0x0004
29
+#define PIKCHR_PROCESS_TH1_NOSVG 0x0008
30
+#define PIKCHR_PROCESS_NONCE 0x0010
31
+#define PIKCHR_PROCESS_ERR_PRE 0x0020
32
+#define PIKCHR_PROCESS_SRC 0x0040
33
+#define PIKCHR_PROCESS_DIV 0x0080
3334
#define PIKCHR_PROCESS_DIV_INDENT 0x0100
3435
#define PIKCHR_PROCESS_DIV_CENTER 0x0200
3536
#define PIKCHR_PROCESS_DIV_FLOAT_LEFT 0x0400
3637
#define PIKCHR_PROCESS_DIV_FLOAT_RIGHT 0x0800
3738
#define PIKCHR_PROCESS_DIV_TOGGLE 0x1000
@@ -159,11 +160,11 @@
159160
}else{
160161
int w = 0, h = 0;
161162
const char * zContent = blob_str(&bIn);
162163
char *zOut;
163164
zOut = pikchr(zContent, "pikchr",
164
- 0x01/*==>PIKCHR_PLAINTEXT_ERRORS*/,
165
+ 0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH),
165166
&w, &h);
166167
if( w>0 && h>0 ){
167168
const char * zClassToggle = "";
168169
const char * zClassSource = "";
169170
const char * zWrapperClass = "";
170171
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -22,16 +22,17 @@
22 #include <ctype.h>
23 #include "pikchrshow.h"
24
25 #if INTERFACE
26 /* These are described in pikchr_process()'s docs. */
27 #define PIKCHR_PROCESS_TH1 0x01
28 #define PIKCHR_PROCESS_TH1_NOSVG 0x02
29 #define PIKCHR_PROCESS_NONCE 0x04
30 #define PIKCHR_PROCESS_ERR_PRE 0x08
31 #define PIKCHR_PROCESS_SRC 0x10
32 #define PIKCHR_PROCESS_DIV 0x20
 
33 #define PIKCHR_PROCESS_DIV_INDENT 0x0100
34 #define PIKCHR_PROCESS_DIV_CENTER 0x0200
35 #define PIKCHR_PROCESS_DIV_FLOAT_LEFT 0x0400
36 #define PIKCHR_PROCESS_DIV_FLOAT_RIGHT 0x0800
37 #define PIKCHR_PROCESS_DIV_TOGGLE 0x1000
@@ -159,11 +160,11 @@
159 }else{
160 int w = 0, h = 0;
161 const char * zContent = blob_str(&bIn);
162 char *zOut;
163 zOut = pikchr(zContent, "pikchr",
164 0x01/*==>PIKCHR_PLAINTEXT_ERRORS*/,
165 &w, &h);
166 if( w>0 && h>0 ){
167 const char * zClassToggle = "";
168 const char * zClassSource = "";
169 const char * zWrapperClass = "";
170
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -22,16 +22,17 @@
22 #include <ctype.h>
23 #include "pikchrshow.h"
24
25 #if INTERFACE
26 /* These are described in pikchr_process()'s docs. */
27 #define PIKCHR_PROCESS_PASSTHROUGH 0x0003 /* Pass through these flags */
28 #define PIKCHR_PROCESS_TH1 0x0004
29 #define PIKCHR_PROCESS_TH1_NOSVG 0x0008
30 #define PIKCHR_PROCESS_NONCE 0x0010
31 #define PIKCHR_PROCESS_ERR_PRE 0x0020
32 #define PIKCHR_PROCESS_SRC 0x0040
33 #define PIKCHR_PROCESS_DIV 0x0080
34 #define PIKCHR_PROCESS_DIV_INDENT 0x0100
35 #define PIKCHR_PROCESS_DIV_CENTER 0x0200
36 #define PIKCHR_PROCESS_DIV_FLOAT_LEFT 0x0400
37 #define PIKCHR_PROCESS_DIV_FLOAT_RIGHT 0x0800
38 #define PIKCHR_PROCESS_DIV_TOGGLE 0x1000
@@ -159,11 +160,11 @@
160 }else{
161 int w = 0, h = 0;
162 const char * zContent = blob_str(&bIn);
163 char *zOut;
164 zOut = pikchr(zContent, "pikchr",
165 0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH),
166 &w, &h);
167 if( w>0 && h>0 ){
168 const char * zClassToggle = "";
169 const char * zClassSource = "";
170 const char * zWrapperClass = "";
171
--- src/security_audit.c
+++ src/security_audit.c
@@ -579,10 +579,16 @@
579579
@ Phantom artifacts are artifacts whose hash name is referenced by some
580580
@ other artifact but whose content is unknown. Some phantoms are marked
581581
@ private and those are ignored. But public phantoms cause unnecessary
582582
@ sync traffic and might represent malicious attempts to corrupt the
583583
@ repository structure.
584
+ @ </p><p>
585
+ @ To suppress unnecessary sync traffic caused by phantoms, add the RID
586
+ @ of each phantom to the "private" table. Example:
587
+ @ <blockquote><pre>
588
+ @ INSERT INTO private SELECT rid FROM blob WHERE content IS NULL;
589
+ @ </pre></blockquote>
584590
@ </p>
585591
table_of_public_phantoms();
586592
@ </li>
587593
}
588594
589595
--- src/security_audit.c
+++ src/security_audit.c
@@ -579,10 +579,16 @@
579 @ Phantom artifacts are artifacts whose hash name is referenced by some
580 @ other artifact but whose content is unknown. Some phantoms are marked
581 @ private and those are ignored. But public phantoms cause unnecessary
582 @ sync traffic and might represent malicious attempts to corrupt the
583 @ repository structure.
 
 
 
 
 
 
584 @ </p>
585 table_of_public_phantoms();
586 @ </li>
587 }
588
589
--- src/security_audit.c
+++ src/security_audit.c
@@ -579,10 +579,16 @@
579 @ Phantom artifacts are artifacts whose hash name is referenced by some
580 @ other artifact but whose content is unknown. Some phantoms are marked
581 @ private and those are ignored. But public phantoms cause unnecessary
582 @ sync traffic and might represent malicious attempts to corrupt the
583 @ repository structure.
584 @ </p><p>
585 @ To suppress unnecessary sync traffic caused by phantoms, add the RID
586 @ of each phantom to the "private" table. Example:
587 @ <blockquote><pre>
588 @ INSERT INTO private SELECT rid FROM blob WHERE content IS NULL;
589 @ </pre></blockquote>
590 @ </p>
591 table_of_public_phantoms();
592 @ </li>
593 }
594
595
+5 -4
--- src/skins.c
+++ src/skins.c
@@ -85,14 +85,15 @@
8585
*/
8686
static struct SkinDetail {
8787
const char *zName; /* Name of the detail */
8888
const char *zValue; /* Value of the detail */
8989
} aSkinDetail[] = {
90
- { "timeline-arrowheads", "1" },
91
- { "timeline-circle-nodes", "0" },
92
- { "timeline-color-graph-lines", "0" },
93
- { "white-foreground", "0" },
90
+ { "pikchr-foreground", "" },
91
+ { "timeline-arrowheads", "1" },
92
+ { "timeline-circle-nodes", "0" },
93
+ { "timeline-color-graph-lines", "0" },
94
+ { "white-foreground", "0" },
9495
};
9596
9697
/*
9798
** Invoke this routine to set the alternative skin. Return NULL if the
9899
** alternative was successfully installed. Return a string listing all
99100
--- src/skins.c
+++ src/skins.c
@@ -85,14 +85,15 @@
85 */
86 static struct SkinDetail {
87 const char *zName; /* Name of the detail */
88 const char *zValue; /* Value of the detail */
89 } aSkinDetail[] = {
90 { "timeline-arrowheads", "1" },
91 { "timeline-circle-nodes", "0" },
92 { "timeline-color-graph-lines", "0" },
93 { "white-foreground", "0" },
 
94 };
95
96 /*
97 ** Invoke this routine to set the alternative skin. Return NULL if the
98 ** alternative was successfully installed. Return a string listing all
99
--- src/skins.c
+++ src/skins.c
@@ -85,14 +85,15 @@
85 */
86 static struct SkinDetail {
87 const char *zName; /* Name of the detail */
88 const char *zValue; /* Value of the detail */
89 } aSkinDetail[] = {
90 { "pikchr-foreground", "" },
91 { "timeline-arrowheads", "1" },
92 { "timeline-circle-nodes", "0" },
93 { "timeline-color-graph-lines", "0" },
94 { "white-foreground", "0" },
95 };
96
97 /*
98 ** Invoke this routine to set the alternative skin. Return NULL if the
99 ** alternative was successfully installed. Return a string listing all
100
+10 -1
--- src/stat.c
+++ src/stat.c
@@ -335,11 +335,11 @@
335335
fossil_print("%*s%s\n", colWidth, "project-name:", z);
336336
}
337337
fsize = file_size(g.zRepositoryName, ExtFILE);
338338
fossil_print( "%*s%,lld bytes\n", colWidth, "repository-size:", fsize);
339339
if( !brief ){
340
- n = db_int(0, "SELECT count(*) FROM blob");
340
+ n = db_int(0, "SELECT count(*) FROM blob WHERE content IS NOT NULL");
341341
m = db_int(0, "SELECT count(*) FROM delta");
342342
fossil_print("%*s%,d (stored as %,d full text and %,d deltas)\n",
343343
colWidth, "artifact-count:",
344344
n, n-m, m);
345345
if( n>0 ){
@@ -376,10 +376,19 @@
376376
n = db_int(0, "SELECT count(*) FROM tag /*scan*/"
377377
" WHERE tagname GLOB 'tkt-*'");
378378
m = db_int(0, "SELECT COUNT(*) FROM event WHERE type='t'");
379379
fossil_print("%*s%,d (%,d changes)\n", colWidth, "tickets:", n, m);
380380
n = db_int(0, "SELECT COUNT(*) FROM event WHERE type='e'");
381
+ if( db_table_exists("repository","forumpost") ){
382
+ n = db_int(0, "SELECT count(*) FROM forumpost/*scan*/");
383
+ if( n>0 ){
384
+ int nThread = db_int(0, "SELECT count(*) FROM forumpost"
385
+ " WHERE froot=fpid");
386
+ fossil_print("%*s%,d (on %,d threads)\n", colWidth, "forum-posts:",
387
+ n, nThread);
388
+ }
389
+ }
381390
fossil_print("%*s%,d\n", colWidth, "events:", n);
382391
n = db_int(0, "SELECT COUNT(*) FROM event WHERE type='g'");
383392
fossil_print("%*s%,d\n", colWidth, "tag-changes:", n);
384393
z = db_text(0, "SELECT datetime(mtime) || ' - about ' ||"
385394
" CAST(julianday('now') - mtime AS INTEGER)"
386395
--- src/stat.c
+++ src/stat.c
@@ -335,11 +335,11 @@
335 fossil_print("%*s%s\n", colWidth, "project-name:", z);
336 }
337 fsize = file_size(g.zRepositoryName, ExtFILE);
338 fossil_print( "%*s%,lld bytes\n", colWidth, "repository-size:", fsize);
339 if( !brief ){
340 n = db_int(0, "SELECT count(*) FROM blob");
341 m = db_int(0, "SELECT count(*) FROM delta");
342 fossil_print("%*s%,d (stored as %,d full text and %,d deltas)\n",
343 colWidth, "artifact-count:",
344 n, n-m, m);
345 if( n>0 ){
@@ -376,10 +376,19 @@
376 n = db_int(0, "SELECT count(*) FROM tag /*scan*/"
377 " WHERE tagname GLOB 'tkt-*'");
378 m = db_int(0, "SELECT COUNT(*) FROM event WHERE type='t'");
379 fossil_print("%*s%,d (%,d changes)\n", colWidth, "tickets:", n, m);
380 n = db_int(0, "SELECT COUNT(*) FROM event WHERE type='e'");
 
 
 
 
 
 
 
 
 
381 fossil_print("%*s%,d\n", colWidth, "events:", n);
382 n = db_int(0, "SELECT COUNT(*) FROM event WHERE type='g'");
383 fossil_print("%*s%,d\n", colWidth, "tag-changes:", n);
384 z = db_text(0, "SELECT datetime(mtime) || ' - about ' ||"
385 " CAST(julianday('now') - mtime AS INTEGER)"
386
--- src/stat.c
+++ src/stat.c
@@ -335,11 +335,11 @@
335 fossil_print("%*s%s\n", colWidth, "project-name:", z);
336 }
337 fsize = file_size(g.zRepositoryName, ExtFILE);
338 fossil_print( "%*s%,lld bytes\n", colWidth, "repository-size:", fsize);
339 if( !brief ){
340 n = db_int(0, "SELECT count(*) FROM blob WHERE content IS NOT NULL");
341 m = db_int(0, "SELECT count(*) FROM delta");
342 fossil_print("%*s%,d (stored as %,d full text and %,d deltas)\n",
343 colWidth, "artifact-count:",
344 n, n-m, m);
345 if( n>0 ){
@@ -376,10 +376,19 @@
376 n = db_int(0, "SELECT count(*) FROM tag /*scan*/"
377 " WHERE tagname GLOB 'tkt-*'");
378 m = db_int(0, "SELECT COUNT(*) FROM event WHERE type='t'");
379 fossil_print("%*s%,d (%,d changes)\n", colWidth, "tickets:", n, m);
380 n = db_int(0, "SELECT COUNT(*) FROM event WHERE type='e'");
381 if( db_table_exists("repository","forumpost") ){
382 n = db_int(0, "SELECT count(*) FROM forumpost/*scan*/");
383 if( n>0 ){
384 int nThread = db_int(0, "SELECT count(*) FROM forumpost"
385 " WHERE froot=fpid");
386 fossil_print("%*s%,d (on %,d threads)\n", colWidth, "forum-posts:",
387 n, nThread);
388 }
389 }
390 fossil_print("%*s%,d\n", colWidth, "events:", n);
391 n = db_int(0, "SELECT COUNT(*) FROM event WHERE type='g'");
392 fossil_print("%*s%,d\n", colWidth, "tag-changes:", n);
393 z = db_text(0, "SELECT datetime(mtime) || ' - about ' ||"
394 " CAST(julianday('now') - mtime AS INTEGER)"
395
+58 -36
--- www/caps/index.md
+++ www/caps/index.md
@@ -256,54 +256,76 @@
256256
All of the above applies to [login groups][lg] as well.
257257
258258
259259
## <a name="webonly"></a>Caps Affect Web Interfaces Only
260260
261
-User caps only affect Fossil’s [UI pages][wp], remote operations over
262
-`http[s]://` URLs, and [the JSON API][japi].
263
-
264
-User caps *do not* affect operations done on a local repo opened via a
265
-`file://` URL or a file system path. This should strike you as sensible:
266
-only local file permissions matter when operating on a local SQLite DB
267
-file. The same is true when working on a clone done over such a path,
268
-except that there are then two sets of file system permission checks:
269
-once to modify the working check-out’s repo clone DB file, then again on
270
-[sync][sync] with the parent DB file. The Fossil capability checks are
271
-effectively defeated because your user has [**Setup**][s] capability on
272
-both sides of the sync.
273
-
274
-What may surprise you is that user caps *also do not affect SSH!* When
275
-you make a change to such a repository, the change first goes to the
276
-local clone, where file system permissions are all that matter, but then
277
-upon sync, the situation is effectively the same as when the parent repo
278
-is on the local file system. If you can log into the remote system over
279
-SSH and that user has the necessary file system permissions on that
280
-remote repo DB file, it is the same situation as for `file://` URLs.
281
-
282
-All Fossil syncs are done over HTTP, even for `file://` and `ssh://`
283
-URLs:
261
+Fossil’s user capability system only affects accesses over `http[s]://`
262
+URLs. This includes clone, sync/push/pull, the [UI pages][wp], and [the
263
+JSON API][japi]. For everything else, the user caps aren’t consulted at
264
+all.
265
+
266
+The only checks made when working directly with a local repository are
267
+the operating system’s file system permissions. This should strike you
268
+as sensible, since if you have local file access to the repository, you
269
+can do anything you want to that repo DB including adding a
270
+[**Setup**][s] user for yourself, after which Fossil’s user capability
271
+system is effectively bypassed. This is why the `fossil ui` command
272
+gives you Setup permissions within Fossil UI: it can’t usefully prevent
273
+you from doing anything through the UI since only the local file system
274
+permissions actually matter.
275
+
276
+What may be more surprising to you is that this is also true when
277
+working on a *clone* done over a local file path, except that there are
278
+then two sets of file system permission checks: once to modify the
279
+working check-out’s repo clone DB file, then again on [sync][sync] with
280
+the parent DB file. The Fossil capability checks are effectively
281
+defeated because your user has [**Setup**][s] capability on both sides
282
+of the sync. Be aware that those file checks do still matter, however:
283
+Fossil requires write access to a repo DB while cloning from it, so you
284
+can’t clone from a read-only repo DB file over a local file path.
285
+
286
+Even more surprising may be the fact that user caps do not affect
287
+cloning and syncing over SSH! When you make a change to such a
288
+repository, the change first goes to the local clone where file system
289
+permissions are all that matter, but then upon sync, the situation is
290
+effectively the same as when the parent repo is on the local file
291
+system. The reason behind this is that if you can log into the remote
292
+system over SSH and that user has the necessary file system permissions
293
+on that remote repo DB file to allow clone and sync operations, then
294
+we’re back in the same situation as with local files: there’s no point
295
+trying to enforce the Fossil user capabilities when you can just modify
296
+the remote DB directly, so the operation proceeds unimpeded.
297
+
298
+Where this gets confusing is that *all* Fossil syncs are done over the
299
+HTTP protocol, including those done over `file://` and `ssh://` URLs,
300
+not just those done over `http[s]://` URLs:
284301
285302
* For `ssh://` URLs, Fossil pipes the HTTP conversation through a
286303
local SSH client to a remote instance of Fossil running the
287
- [`test-http`](/help?name=test-http) command to recieve the tunneled
288
- HTTP connection without cap checks. The SSH client defaults to “`ssh
289
- -e none -T`” on most platforms, except on Windows where it defaults
290
- to “`plink -ssh -T`”. You can override this with [the `ssh-command`
304
+ [`test-http`](/help?name=test-http) command to receive the tunneled
305
+ HTTP connection. The reason Fossil’s user capability system is
306
+ bypassed in this case is that [`test-http` gives full capabilities
307
+ to its users][sxcap].
308
+
309
+ The SSH client command defaults to “`ssh -e none -T`” on most
310
+ platforms except Windows where it defaults to “`plink -ssh -T`”.
311
+ You can override this with [the `ssh-command`
291312
setting](/help?name=ssh-command).
292313
293
-* For `file://` URLs, the “sending” Fossil instance writes its side of
314
+* For `file://` URLs — as opposed to plain local file paths —
315
+ the “sending” Fossil instance writes its side of
294316
the HTTP conversation out to a temporary file in the same directory
295317
as the local repo clone and then calls itself on the “receiving”
296318
repository to read that same HTTP transcript file back in to apply
297
- those changes to that repository. Presumably Fossil doesn’t do this
298
- with a pipe to ease portability to Windows.
299
-
300
-Because both mechanisms work on local repos, the checks for capabilities
301
-like [**Read**][o] and [**Write**][i] within the HTTP conversation for
302
-such URLs can never return “false,” because you are the [**Setup**][s]
303
-user on both sides of the conversation. Such checks only have a useful
304
-effect when done over an `http[s]://` URL.
319
+ those changes to that repository. Presumably Fossil does this
320
+ instead of using a pipe to ease portability to Windows.
321
+
322
+Checks for capabilities like [**Read**][o] and [**Write**][i] within the
323
+HTTP conversation between two Fossil instances only have a useful effect
324
+when done over an `http[s]://` URL.
325
+
326
+[sxcap]: https://fossil-scm.org/home/file?ci=8813ae91a699ac73&name=src%2Fmain.c&ln=2632-2637
305327
306328
307329
## <a name="pubpg"></a>Public Pages
308330
309331
In Admin → Access, there is an option for giving a list of [globs][glob]
310332
--- www/caps/index.md
+++ www/caps/index.md
@@ -256,54 +256,76 @@
256 All of the above applies to [login groups][lg] as well.
257
258
259 ## <a name="webonly"></a>Caps Affect Web Interfaces Only
260
261 User caps only affect Fossil’s [UI pages][wp], remote operations over
262 `http[s]://` URLs, and [the JSON API][japi].
263
264 User caps *do not* affect operations done on a local repo opened via a
265 `file://` URL or a file system path. This should strike you as sensible:
266 only local file permissions matter when operating on a local SQLite DB
267 file. The same is true when working on a clone done over such a path,
268 except that there are then two sets of file system permission checks:
269 once to modify the working check-out’s repo clone DB file, then again on
270 [sync][sync] with the parent DB file. The Fossil capability checks are
271 effectively defeated because your user has [**Setup**][s] capability on
272 both sides of the sync.
273
274 What may surprise you is that user caps *also do not affect SSH!* When
275 you make a change to such a repository, the change first goes to the
276 local clone, where file system permissions are all that matter, but then
277 upon sync, the situation is effectively the same as when the parent repo
278 is on the local file system. If you can log into the remote system over
279 SSH and that user has the necessary file system permissions on that
280 remote repo DB file, it is the same situation as for `file://` URLs.
281
282 All Fossil syncs are done over HTTP, even for `file://` and `ssh://`
283 URLs:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
285 * For `ssh://` URLs, Fossil pipes the HTTP conversation through a
286 local SSH client to a remote instance of Fossil running the
287 [`test-http`](/help?name=test-http) command to recieve the tunneled
288 HTTP connection without cap checks. The SSH client defaults to “`ssh
289 -e none -T`” on most platforms, except on Windows where it defaults
290 to “`plink -ssh -T`”. You can override this with [the `ssh-command`
 
 
 
 
291 setting](/help?name=ssh-command).
292
293 * For `file://` URLs, the “sending” Fossil instance writes its side of
 
294 the HTTP conversation out to a temporary file in the same directory
295 as the local repo clone and then calls itself on the “receiving”
296 repository to read that same HTTP transcript file back in to apply
297 those changes to that repository. Presumably Fossil doesn’t do this
298 with a pipe to ease portability to Windows.
299
300 Because both mechanisms work on local repos, the checks for capabilities
301 like [**Read**][o] and [**Write**][i] within the HTTP conversation for
302 such URLs can never return “false,” because you are the [**Setup**][s]
303 user on both sides of the conversation. Such checks only have a useful
304 effect when done over an `http[s]://` URL.
305
306
307 ## <a name="pubpg"></a>Public Pages
308
309 In Admin → Access, there is an option for giving a list of [globs][glob]
310
--- www/caps/index.md
+++ www/caps/index.md
@@ -256,54 +256,76 @@
256 All of the above applies to [login groups][lg] as well.
257
258
259 ## <a name="webonly"></a>Caps Affect Web Interfaces Only
260
261 Fossil’s user capability system only affects accesses over `http[s]://`
262 URLs. This includes clone, sync/push/pull, the [UI pages][wp], and [the
263 JSON API][japi]. For everything else, the user caps aren’t consulted at
264 all.
265
266 The only checks made when working directly with a local repository are
267 the operating system’s file system permissions. This should strike you
268 as sensible, since if you have local file access to the repository, you
269 can do anything you want to that repo DB including adding a
270 [**Setup**][s] user for yourself, after which Fossil’s user capability
271 system is effectively bypassed. This is why the `fossil ui` command
272 gives you Setup permissions within Fossil UI: it can’t usefully prevent
273 you from doing anything through the UI since only the local file system
274 permissions actually matter.
275
276 What may be more surprising to you is that this is also true when
277 working on a *clone* done over a local file path, except that there are
278 then two sets of file system permission checks: once to modify the
279 working check-out’s repo clone DB file, then again on [sync][sync] with
280 the parent DB file. The Fossil capability checks are effectively
281 defeated because your user has [**Setup**][s] capability on both sides
282 of the sync. Be aware that those file checks do still matter, however:
283 Fossil requires write access to a repo DB while cloning from it, so you
284 can’t clone from a read-only repo DB file over a local file path.
285
286 Even more surprising may be the fact that user caps do not affect
287 cloning and syncing over SSH! When you make a change to such a
288 repository, the change first goes to the local clone where file system
289 permissions are all that matter, but then upon sync, the situation is
290 effectively the same as when the parent repo is on the local file
291 system. The reason behind this is that if you can log into the remote
292 system over SSH and that user has the necessary file system permissions
293 on that remote repo DB file to allow clone and sync operations, then
294 we’re back in the same situation as with local files: there’s no point
295 trying to enforce the Fossil user capabilities when you can just modify
296 the remote DB directly, so the operation proceeds unimpeded.
297
298 Where this gets confusing is that *all* Fossil syncs are done over the
299 HTTP protocol, including those done over `file://` and `ssh://` URLs,
300 not just those done over `http[s]://` URLs:
301
302 * For `ssh://` URLs, Fossil pipes the HTTP conversation through a
303 local SSH client to a remote instance of Fossil running the
304 [`test-http`](/help?name=test-http) command to receive the tunneled
305 HTTP connection. The reason Fossil’s user capability system is
306 bypassed in this case is that [`test-http` gives full capabilities
307 to its users][sxcap].
308
309 The SSH client command defaults to “`ssh -e none -T`” on most
310 platforms except Windows where it defaults to “`plink -ssh -T`”.
311 You can override this with [the `ssh-command`
312 setting](/help?name=ssh-command).
313
314 * For `file://` URLs — as opposed to plain local file paths —
315 the “sending” Fossil instance writes its side of
316 the HTTP conversation out to a temporary file in the same directory
317 as the local repo clone and then calls itself on the “receiving”
318 repository to read that same HTTP transcript file back in to apply
319 those changes to that repository. Presumably Fossil does this
320 instead of using a pipe to ease portability to Windows.
321
322 Checks for capabilities like [**Read**][o] and [**Write**][i] within the
323 HTTP conversation between two Fossil instances only have a useful effect
324 when done over an `http[s]://` URL.
325
326 [sxcap]: https://fossil-scm.org/home/file?ci=8813ae91a699ac73&name=src%2Fmain.c&ln=2632-2637
327
328
329 ## <a name="pubpg"></a>Public Pages
330
331 In Admin → Access, there is an option for giving a list of [globs][glob]
332
--- www/customskin.md
+++ www/customskin.md
@@ -169,22 +169,30 @@
169169
<p>The details.txt file is short list of settings that control
170170
the look and feel, mostly of the timeline. The default
171171
details.txt file looks like this:
172172
173173
<blockquote><pre>
174
+pikchr-foreground: ""
174175
timeline-arrowheads: 1
175176
timeline-circle-nodes: 1
176177
timeline-color-graph-lines: 1
177178
white-foreground: 0
178179
</pre></blockquote>
179180
180
-The first three setings in details.txt control the appearance
181
+The three "timeline-" settings in details.txt control the appearance
181182
of certain aspects of the timeline graph. The number on the
182183
right is a boolean - "1" to activate the feature and "0" to
183184
disable it. The "white-foreground:" setting should be set to
184185
"1" if the page color has light-color text on a darker background,
185
-and "0" if the page has dark text on a light-colored background.</dd>
186
+and "0" if the page has dark text on a light-colored background.
187
+<p>
188
+If the "pikchr-foreground" setting (only available in Fossil 2.14 and
189
+later) is defined and is not an empty string then it specifies a
190
+foreground color to use for [pikchr diagrams](./pikchr.md). The
191
+default pikchr foreground color is black, or white if the
192
+"white-foreground" boolean is set.
193
+</dd>
186194
187195
<dt><b>footer.txt</b> and <b>header.txt</b></dt><dd>
188196
189197
<p>The footer.txt and header.txt files contain the Content Footer
190198
and Content Header respectively. Of these, the Content Header is
191199
--- www/customskin.md
+++ www/customskin.md
@@ -169,22 +169,30 @@
169 <p>The details.txt file is short list of settings that control
170 the look and feel, mostly of the timeline. The default
171 details.txt file looks like this:
172
173 <blockquote><pre>
 
174 timeline-arrowheads: 1
175 timeline-circle-nodes: 1
176 timeline-color-graph-lines: 1
177 white-foreground: 0
178 </pre></blockquote>
179
180 The first three setings in details.txt control the appearance
181 of certain aspects of the timeline graph. The number on the
182 right is a boolean - "1" to activate the feature and "0" to
183 disable it. The "white-foreground:" setting should be set to
184 "1" if the page color has light-color text on a darker background,
185 and "0" if the page has dark text on a light-colored background.</dd>
 
 
 
 
 
 
 
186
187 <dt><b>footer.txt</b> and <b>header.txt</b></dt><dd>
188
189 <p>The footer.txt and header.txt files contain the Content Footer
190 and Content Header respectively. Of these, the Content Header is
191
--- www/customskin.md
+++ www/customskin.md
@@ -169,22 +169,30 @@
169 <p>The details.txt file is short list of settings that control
170 the look and feel, mostly of the timeline. The default
171 details.txt file looks like this:
172
173 <blockquote><pre>
174 pikchr-foreground: ""
175 timeline-arrowheads: 1
176 timeline-circle-nodes: 1
177 timeline-color-graph-lines: 1
178 white-foreground: 0
179 </pre></blockquote>
180
181 The three "timeline-" settings in details.txt control the appearance
182 of certain aspects of the timeline graph. The number on the
183 right is a boolean - "1" to activate the feature and "0" to
184 disable it. The "white-foreground:" setting should be set to
185 "1" if the page color has light-color text on a darker background,
186 and "0" if the page has dark text on a light-colored background.
187 <p>
188 If the "pikchr-foreground" setting (only available in Fossil 2.14 and
189 later) is defined and is not an empty string then it specifies a
190 foreground color to use for [pikchr diagrams](./pikchr.md). The
191 default pikchr foreground color is black, or white if the
192 "white-foreground" boolean is set.
193 </dd>
194
195 <dt><b>footer.txt</b> and <b>header.txt</b></dt><dd>
196
197 <p>The footer.txt and header.txt files contain the Content Footer
198 and Content Header respectively. Of these, the Content Header is
199
--- www/delta_format.wiki
+++ www/delta_format.wiki
@@ -95,11 +95,11 @@
9595
9696
<a name="trailer"></a><h2>2.2 Trailer</h2>
9797
<verbatim type="pikchr">
9898
leftmargin = 0.1
9999
box height 50% "Checksum"
100
- box same "\":\""
100
+ box same "\";\""
101101
</verbatim>
102102
103103
<p>The trailer consists of a single number followed by a semicolon (ASCII
104104
0x3b). This number is a checksum of the target and can be used by a
105105
decoder to verify that the delta applied correctly, reconstructing the
106106
--- www/delta_format.wiki
+++ www/delta_format.wiki
@@ -95,11 +95,11 @@
95
96 <a name="trailer"></a><h2>2.2 Trailer</h2>
97 <verbatim type="pikchr">
98 leftmargin = 0.1
99 box height 50% "Checksum"
100 box same "\":\""
101 </verbatim>
102
103 <p>The trailer consists of a single number followed by a semicolon (ASCII
104 0x3b). This number is a checksum of the target and can be used by a
105 decoder to verify that the delta applied correctly, reconstructing the
106
--- www/delta_format.wiki
+++ www/delta_format.wiki
@@ -95,11 +95,11 @@
95
96 <a name="trailer"></a><h2>2.2 Trailer</h2>
97 <verbatim type="pikchr">
98 leftmargin = 0.1
99 box height 50% "Checksum"
100 box same "\";\""
101 </verbatim>
102
103 <p>The trailer consists of a single number followed by a semicolon (ASCII
104 0x3b). This number is a checksum of the target and can be used by a
105 decoder to verify that the delta applied correctly, reconstructing the
106
+7 -6
--- www/gitusers.md
+++ www/gitusers.md
@@ -795,18 +795,19 @@
795795
1. It’s a bit cryptic. Leave off the refname or punctuation, and it
796796
means something else. You cannot simplify the cryptic incantation in
797797
the typical use case.
798798
799799
2. A date string in Git without a time will be interpreted as
800
- “[at localtime on that date][gapxd],” so the command means something
801
- different from one second to the next! If there are multiple commits
802
- on that date, that command can give different results depending on
803
- the time of day you run it.
800
+ “[at the local wall clock time on the given date][gapxd],” so the
801
+ command means something different from one second to the next. This
802
+ can be a problem if there are multiple commits on that date, because
803
+ the command will give diffferent results depending on the time of
804
+ day you run it.
804805
805806
3. It gives misleading output if there is no close match for the date
806
- in target commit in the local [reflog]. On a fresh clone, the reflog
807
- is empty, and even on a well-established clone, Git [automatically
807
+ in the local [reflog]. It starts out empty after a fresh clone, and
808
+ while it does build up as you use that clone, Git [automatically
808809
prunes][gle] the reflog to 90 days of history by default. This means
809810
the command above can give different results from one machine to the
810811
next, or even from one day to the next on the same clone.
811812
812813
The command won’t fail outright if the reflog can’t resolve the
813814
--- www/gitusers.md
+++ www/gitusers.md
@@ -795,18 +795,19 @@
795 1. It’s a bit cryptic. Leave off the refname or punctuation, and it
796 means something else. You cannot simplify the cryptic incantation in
797 the typical use case.
798
799 2. A date string in Git without a time will be interpreted as
800 “[at localtime on that date][gapxd],” so the command means something
801 different from one second to the next! If there are multiple commits
802 on that date, that command can give different results depending on
803 the time of day you run it.
 
804
805 3. It gives misleading output if there is no close match for the date
806 in target commit in the local [reflog]. On a fresh clone, the reflog
807 is empty, and even on a well-established clone, Git [automatically
808 prunes][gle] the reflog to 90 days of history by default. This means
809 the command above can give different results from one machine to the
810 next, or even from one day to the next on the same clone.
811
812 The command won’t fail outright if the reflog can’t resolve the
813
--- www/gitusers.md
+++ www/gitusers.md
@@ -795,18 +795,19 @@
795 1. It’s a bit cryptic. Leave off the refname or punctuation, and it
796 means something else. You cannot simplify the cryptic incantation in
797 the typical use case.
798
799 2. A date string in Git without a time will be interpreted as
800 “[at the local wall clock time on the given date][gapxd],” so the
801 command means something different from one second to the next. This
802 can be a problem if there are multiple commits on that date, because
803 the command will give diffferent results depending on the time of
804 day you run it.
805
806 3. It gives misleading output if there is no close match for the date
807 in the local [reflog]. It starts out empty after a fresh clone, and
808 while it does build up as you use that clone, Git [automatically
809 prunes][gle] the reflog to 90 days of history by default. This means
810 the command above can give different results from one machine to the
811 next, or even from one day to the next on the same clone.
812
813 The command won’t fail outright if the reflog can’t resolve the
814
--- www/inout.wiki
+++ www/inout.wiki
@@ -24,10 +24,20 @@
2424
is currently the only VCS interchange format that Fossil understands. But
2525
future versions of Fossil might be enhanced to understand other VCS
2626
interchange formats, and so for compatibility, use of the
2727
--git option is recommended.
2828
29
+<a name="fx_git"></a>
30
+Note that in new imports, Fossil defaults to using the email component of the
31
+Git <em>committer</em> (or <em>author</em> if <code>--use-author</code> is
32
+passed) to attribute check-ins in the imported repository. Alternatively, the
33
+[/help?cmd=import | <code>--attribute</code>] option can be passed to have all
34
+commits by a given committer attributed to a desired username. This will create
35
+and populate the new <code>fx_git</code> table in the repository database to
36
+maintain a record of correspondent usernames and email addresses that can be
37
+used in subsequent exports or incremental imports.
38
+
2939
<h2>Fossil → Git</h2>
3040
3141
To convert a Fossil repository into a Git repository, run commands like
3242
this:
3343
3444
--- www/inout.wiki
+++ www/inout.wiki
@@ -24,10 +24,20 @@
24 is currently the only VCS interchange format that Fossil understands. But
25 future versions of Fossil might be enhanced to understand other VCS
26 interchange formats, and so for compatibility, use of the
27 --git option is recommended.
28
 
 
 
 
 
 
 
 
 
 
29 <h2>Fossil → Git</h2>
30
31 To convert a Fossil repository into a Git repository, run commands like
32 this:
33
34
--- www/inout.wiki
+++ www/inout.wiki
@@ -24,10 +24,20 @@
24 is currently the only VCS interchange format that Fossil understands. But
25 future versions of Fossil might be enhanced to understand other VCS
26 interchange formats, and so for compatibility, use of the
27 --git option is recommended.
28
29 <a name="fx_git"></a>
30 Note that in new imports, Fossil defaults to using the email component of the
31 Git <em>committer</em> (or <em>author</em> if <code>--use-author</code> is
32 passed) to attribute check-ins in the imported repository. Alternatively, the
33 [/help?cmd=import | <code>--attribute</code>] option can be passed to have all
34 commits by a given committer attributed to a desired username. This will create
35 and populate the new <code>fx_git</code> table in the repository database to
36 maintain a record of correspondent usernames and email addresses that can be
37 used in subsequent exports or incremental imports.
38
39 <h2>Fossil → Git</h2>
40
41 To convert a Fossil repository into a Git repository, run commands like
42 this:
43
44
--- www/mirrortogithub.md
+++ www/mirrortogithub.md
@@ -124,10 +124,27 @@
124124
[long list of restrictions](https://git-scm.com/docs/git-check-ref-format)
125125
on tag and branch names in Git. If any of your Fossil tag or branch names
126126
violate these rules, then the names are translated prior to being exported
127127
to Git. The translation usually involves converting the offending characters
128128
into underscores.
129
+
130
+ * If your Fossil user contact info is not set and this repository was not
131
+ initially [imported from Git](./inout.wiki), `fossil git export` will
132
+ construct a generic `[email protected]` for the Git *committer* and *author*
133
+ email fields of each commit. However, Fossil will first attempt to parse an
134
+ email address from your user contact info, which can be set through a
135
+ Fossil [UI][ui] browser window or with the [`user contact`][usercmd]
136
+ subcommand on the command line. Alternatively, if this repository was
137
+ previously imported from Git using the [`--attribute`][attr] option, the
138
+ [`fx_git`][fxgit] table will be queried for correspondent email addresses.
139
+ Only if neither of these methods produce a user specified email will the
140
+ abovementioned generic address be used.
141
+
142
+[attr]: /help?cmd=import
143
+[fxgit]: ./inout.wiki#fx_git
144
+[ui]: /help?cmd=ui
145
+[usercmd]: /help?cmd=user
129146
130147
<a name='ex1'></a>
131148
## Example GitHub Mirrors
132149
133150
As of this writing (2019-03-16) Fossil’s own repository is mirrored
134151
--- www/mirrortogithub.md
+++ www/mirrortogithub.md
@@ -124,10 +124,27 @@
124 [long list of restrictions](https://git-scm.com/docs/git-check-ref-format)
125 on tag and branch names in Git. If any of your Fossil tag or branch names
126 violate these rules, then the names are translated prior to being exported
127 to Git. The translation usually involves converting the offending characters
128 into underscores.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
130 <a name='ex1'></a>
131 ## Example GitHub Mirrors
132
133 As of this writing (2019-03-16) Fossil’s own repository is mirrored
134
--- www/mirrortogithub.md
+++ www/mirrortogithub.md
@@ -124,10 +124,27 @@
124 [long list of restrictions](https://git-scm.com/docs/git-check-ref-format)
125 on tag and branch names in Git. If any of your Fossil tag or branch names
126 violate these rules, then the names are translated prior to being exported
127 to Git. The translation usually involves converting the offending characters
128 into underscores.
129
130 * If your Fossil user contact info is not set and this repository was not
131 initially [imported from Git](./inout.wiki), `fossil git export` will
132 construct a generic `[email protected]` for the Git *committer* and *author*
133 email fields of each commit. However, Fossil will first attempt to parse an
134 email address from your user contact info, which can be set through a
135 Fossil [UI][ui] browser window or with the [`user contact`][usercmd]
136 subcommand on the command line. Alternatively, if this repository was
137 previously imported from Git using the [`--attribute`][attr] option, the
138 [`fx_git`][fxgit] table will be queried for correspondent email addresses.
139 Only if neither of these methods produce a user specified email will the
140 abovementioned generic address be used.
141
142 [attr]: /help?cmd=import
143 [fxgit]: ./inout.wiki#fx_git
144 [ui]: /help?cmd=ui
145 [usercmd]: /help?cmd=user
146
147 <a name='ex1'></a>
148 ## Example GitHub Mirrors
149
150 As of this writing (2019-03-16) Fossil’s own repository is mirrored
151
--- www/mkindex.tcl
+++ www/mkindex.tcl
@@ -100,11 +100,10 @@
100100
Of Fossil}
101101
tech_overview.wiki {SQLite Databases Used By Fossil}
102102
th1.md {The TH1 Scripting Language}
103103
tickets.wiki {The Fossil Ticket System}
104104
theory1.wiki {Thoughts On The Design Of The Fossil DVCS}
105
- tls-nginx.md {Proxying Fossil via HTTPS with nginx}
106105
unvers.wiki {Unversioned Files}
107106
webpage-ex.md {Webpage Examples}
108107
webui.wiki {The Fossil Web Interface}
109108
whyusefossil.wiki {Why You Should Use Fossil}
110109
whyusefossil.wiki {Benefits Of Version Control}
111110
--- www/mkindex.tcl
+++ www/mkindex.tcl
@@ -100,11 +100,10 @@
100 Of Fossil}
101 tech_overview.wiki {SQLite Databases Used By Fossil}
102 th1.md {The TH1 Scripting Language}
103 tickets.wiki {The Fossil Ticket System}
104 theory1.wiki {Thoughts On The Design Of The Fossil DVCS}
105 tls-nginx.md {Proxying Fossil via HTTPS with nginx}
106 unvers.wiki {Unversioned Files}
107 webpage-ex.md {Webpage Examples}
108 webui.wiki {The Fossil Web Interface}
109 whyusefossil.wiki {Why You Should Use Fossil}
110 whyusefossil.wiki {Benefits Of Version Control}
111
--- www/mkindex.tcl
+++ www/mkindex.tcl
@@ -100,11 +100,10 @@
100 Of Fossil}
101 tech_overview.wiki {SQLite Databases Used By Fossil}
102 th1.md {The TH1 Scripting Language}
103 tickets.wiki {The Fossil Ticket System}
104 theory1.wiki {Thoughts On The Design Of The Fossil DVCS}
 
105 unvers.wiki {Unversioned Files}
106 webpage-ex.md {Webpage Examples}
107 webui.wiki {The Fossil Web Interface}
108 whyusefossil.wiki {Why You Should Use Fossil}
109 whyusefossil.wiki {Benefits Of Version Control}
110
--- www/permutedindex.html
+++ www/permutedindex.html
@@ -176,11 +176,10 @@
176176
<li><a href="server/"><b>How To Configure A Fossil Server</b></a></li>
177177
<li><a href="newrepo.wiki"><b>How To Create A New Fossil Repository</b></a></li>
178178
<li><a href="mirrortogithub.md"><b>How To Mirror A Fossil Repository On GitHub</b></a></li>
179179
<li><a href="encryptedrepos.wiki"><b>How To Use Encrypted Repositories</b></a></li>
180180
<li><a href="hacker-howto.wiki">How-To &mdash; Hacker</a></li>
181
-<li><a href="tls-nginx.md">HTTPS with nginx &mdash; Proxying Fossil via</a></li>
182181
<li><a href="fossil-from-msvc.wiki">IDE &mdash; Integrating Fossil in the Microsoft Express 2010</a></li>
183182
<li><a href="hashes.md">Identification &mdash; Hashes: Fossil Artifact</a></li>
184183
<li><a href="image-format-vs-repo-size.md"><b>Image Format vs Fossil Repo Size</b></a></li>
185184
<li><a href="tech_overview.wiki">Implementation Of Fossil &mdash; A Technical Overview Of The Design And</a></li>
186185
<li><a href="inout.wiki"><b>Import And Export To And From Git</b></a></li>
@@ -207,11 +206,10 @@
207206
<li><a href="mirrorlimitations.md">Mirrors &mdash; Limitations On Git</a></li>
208207
<li><a href="globs.md">Name Glob Patterns &mdash; File</a></li>
209208
<li><a href="checkin_names.wiki">Names &mdash; Check-in And Version</a></li>
210209
<li><a href="adding_code.wiki">New Features To Fossil &mdash; Adding</a></li>
211210
<li><a href="newrepo.wiki">New Fossil Repository &mdash; How To Create A</a></li>
212
-<li><a href="tls-nginx.md">nginx &mdash; Proxying Fossil via HTTPS with</a></li>
213211
<li><a href="alerts.md">Notifications &mdash; Email Alerts And</a></li>
214212
<li><a href="foss-cklist.wiki">Open-Source Projects &mdash; Checklist For Successful</a></li>
215213
<li><a href="pop.wiki">Operation &mdash; Principles Of</a></li>
216214
<li><a href="cgi.wiki">Options &mdash; CGI Script Configuration</a></li>
217215
<li><a href="env-opts.md">Options &mdash; Environment Variables and Global</a></li>
@@ -235,11 +233,10 @@
235233
<li><a href="embeddeddoc.wiki">Project Documentation &mdash; Embedded</a></li>
236234
<li><a href="foss-cklist.wiki">Projects &mdash; Checklist For Successful Open-Source</a></li>
237235
<li><a href="childprojects.wiki">Projects &mdash; Child</a></li>
238236
<li><a href="fossil_prompt.wiki">Prompt &mdash; Fossilized Bash</a></li>
239237
<li><a href="sync.wiki">Protocol &mdash; The Fossil Sync</a></li>
240
-<li><a href="tls-nginx.md"><b>Proxying Fossil via HTTPS with nginx</b></a></li>
241238
<li><a href="history.md">Purpose And History Of Fossil &mdash; The</a></li>
242239
<li><a href="faq.wiki">Questions &mdash; Frequently Asked</a></li>
243240
<li><a href="qandc.wiki"><b>Questions And Criticisms</b></a></li>
244241
<li><a href="quickstart.wiki">Quick Start Guide &mdash; Fossil</a></li>
245242
<li><a href="quotes.wiki"><b>Quotes: What People Are Saying About Fossil, Git, and DVCSes in General</b></a></li>
@@ -327,11 +324,10 @@
327324
<li><a href="ssl.wiki"><b>Using SSL with Fossil</b></a></li>
328325
<li><a href="env-opts.md">Variables and Global Options &mdash; Environment</a></li>
329326
<li><a href="whyusefossil.wiki">Version Control &mdash; Benefits Of</a></li>
330327
<li><a href="checkin_names.wiki">Version Names &mdash; Check-in And</a></li>
331328
<li><a href="fossil-v-git.wiki">Versus Git &mdash; Fossil</a></li>
332
-<li><a href="tls-nginx.md">via HTTPS with nginx &mdash; Proxying Fossil</a></li>
333329
<li><a href="image-format-vs-repo-size.md">vs Fossil Repo Size &mdash; Image Format</a></li>
334330
<li><a href="grep.md">vs POSIX grep &mdash; Fossil grep</a></li>
335331
<li><a href="webui.wiki">Web Interface &mdash; The Fossil</a></li>
336332
<li><a href="customskin.md">Web Pages &mdash; Theming: Customizing The Appearance of</a></li>
337333
<li><a href="webpage-ex.md"><b>Webpage Examples</b></a></li>
338334
--- www/permutedindex.html
+++ www/permutedindex.html
@@ -176,11 +176,10 @@
176 <li><a href="server/"><b>How To Configure A Fossil Server</b></a></li>
177 <li><a href="newrepo.wiki"><b>How To Create A New Fossil Repository</b></a></li>
178 <li><a href="mirrortogithub.md"><b>How To Mirror A Fossil Repository On GitHub</b></a></li>
179 <li><a href="encryptedrepos.wiki"><b>How To Use Encrypted Repositories</b></a></li>
180 <li><a href="hacker-howto.wiki">How-To &mdash; Hacker</a></li>
181 <li><a href="tls-nginx.md">HTTPS with nginx &mdash; Proxying Fossil via</a></li>
182 <li><a href="fossil-from-msvc.wiki">IDE &mdash; Integrating Fossil in the Microsoft Express 2010</a></li>
183 <li><a href="hashes.md">Identification &mdash; Hashes: Fossil Artifact</a></li>
184 <li><a href="image-format-vs-repo-size.md"><b>Image Format vs Fossil Repo Size</b></a></li>
185 <li><a href="tech_overview.wiki">Implementation Of Fossil &mdash; A Technical Overview Of The Design And</a></li>
186 <li><a href="inout.wiki"><b>Import And Export To And From Git</b></a></li>
@@ -207,11 +206,10 @@
207 <li><a href="mirrorlimitations.md">Mirrors &mdash; Limitations On Git</a></li>
208 <li><a href="globs.md">Name Glob Patterns &mdash; File</a></li>
209 <li><a href="checkin_names.wiki">Names &mdash; Check-in And Version</a></li>
210 <li><a href="adding_code.wiki">New Features To Fossil &mdash; Adding</a></li>
211 <li><a href="newrepo.wiki">New Fossil Repository &mdash; How To Create A</a></li>
212 <li><a href="tls-nginx.md">nginx &mdash; Proxying Fossil via HTTPS with</a></li>
213 <li><a href="alerts.md">Notifications &mdash; Email Alerts And</a></li>
214 <li><a href="foss-cklist.wiki">Open-Source Projects &mdash; Checklist For Successful</a></li>
215 <li><a href="pop.wiki">Operation &mdash; Principles Of</a></li>
216 <li><a href="cgi.wiki">Options &mdash; CGI Script Configuration</a></li>
217 <li><a href="env-opts.md">Options &mdash; Environment Variables and Global</a></li>
@@ -235,11 +233,10 @@
235 <li><a href="embeddeddoc.wiki">Project Documentation &mdash; Embedded</a></li>
236 <li><a href="foss-cklist.wiki">Projects &mdash; Checklist For Successful Open-Source</a></li>
237 <li><a href="childprojects.wiki">Projects &mdash; Child</a></li>
238 <li><a href="fossil_prompt.wiki">Prompt &mdash; Fossilized Bash</a></li>
239 <li><a href="sync.wiki">Protocol &mdash; The Fossil Sync</a></li>
240 <li><a href="tls-nginx.md"><b>Proxying Fossil via HTTPS with nginx</b></a></li>
241 <li><a href="history.md">Purpose And History Of Fossil &mdash; The</a></li>
242 <li><a href="faq.wiki">Questions &mdash; Frequently Asked</a></li>
243 <li><a href="qandc.wiki"><b>Questions And Criticisms</b></a></li>
244 <li><a href="quickstart.wiki">Quick Start Guide &mdash; Fossil</a></li>
245 <li><a href="quotes.wiki"><b>Quotes: What People Are Saying About Fossil, Git, and DVCSes in General</b></a></li>
@@ -327,11 +324,10 @@
327 <li><a href="ssl.wiki"><b>Using SSL with Fossil</b></a></li>
328 <li><a href="env-opts.md">Variables and Global Options &mdash; Environment</a></li>
329 <li><a href="whyusefossil.wiki">Version Control &mdash; Benefits Of</a></li>
330 <li><a href="checkin_names.wiki">Version Names &mdash; Check-in And</a></li>
331 <li><a href="fossil-v-git.wiki">Versus Git &mdash; Fossil</a></li>
332 <li><a href="tls-nginx.md">via HTTPS with nginx &mdash; Proxying Fossil</a></li>
333 <li><a href="image-format-vs-repo-size.md">vs Fossil Repo Size &mdash; Image Format</a></li>
334 <li><a href="grep.md">vs POSIX grep &mdash; Fossil grep</a></li>
335 <li><a href="webui.wiki">Web Interface &mdash; The Fossil</a></li>
336 <li><a href="customskin.md">Web Pages &mdash; Theming: Customizing The Appearance of</a></li>
337 <li><a href="webpage-ex.md"><b>Webpage Examples</b></a></li>
338
--- www/permutedindex.html
+++ www/permutedindex.html
@@ -176,11 +176,10 @@
176 <li><a href="server/"><b>How To Configure A Fossil Server</b></a></li>
177 <li><a href="newrepo.wiki"><b>How To Create A New Fossil Repository</b></a></li>
178 <li><a href="mirrortogithub.md"><b>How To Mirror A Fossil Repository On GitHub</b></a></li>
179 <li><a href="encryptedrepos.wiki"><b>How To Use Encrypted Repositories</b></a></li>
180 <li><a href="hacker-howto.wiki">How-To &mdash; Hacker</a></li>
 
181 <li><a href="fossil-from-msvc.wiki">IDE &mdash; Integrating Fossil in the Microsoft Express 2010</a></li>
182 <li><a href="hashes.md">Identification &mdash; Hashes: Fossil Artifact</a></li>
183 <li><a href="image-format-vs-repo-size.md"><b>Image Format vs Fossil Repo Size</b></a></li>
184 <li><a href="tech_overview.wiki">Implementation Of Fossil &mdash; A Technical Overview Of The Design And</a></li>
185 <li><a href="inout.wiki"><b>Import And Export To And From Git</b></a></li>
@@ -207,11 +206,10 @@
206 <li><a href="mirrorlimitations.md">Mirrors &mdash; Limitations On Git</a></li>
207 <li><a href="globs.md">Name Glob Patterns &mdash; File</a></li>
208 <li><a href="checkin_names.wiki">Names &mdash; Check-in And Version</a></li>
209 <li><a href="adding_code.wiki">New Features To Fossil &mdash; Adding</a></li>
210 <li><a href="newrepo.wiki">New Fossil Repository &mdash; How To Create A</a></li>
 
211 <li><a href="alerts.md">Notifications &mdash; Email Alerts And</a></li>
212 <li><a href="foss-cklist.wiki">Open-Source Projects &mdash; Checklist For Successful</a></li>
213 <li><a href="pop.wiki">Operation &mdash; Principles Of</a></li>
214 <li><a href="cgi.wiki">Options &mdash; CGI Script Configuration</a></li>
215 <li><a href="env-opts.md">Options &mdash; Environment Variables and Global</a></li>
@@ -235,11 +233,10 @@
233 <li><a href="embeddeddoc.wiki">Project Documentation &mdash; Embedded</a></li>
234 <li><a href="foss-cklist.wiki">Projects &mdash; Checklist For Successful Open-Source</a></li>
235 <li><a href="childprojects.wiki">Projects &mdash; Child</a></li>
236 <li><a href="fossil_prompt.wiki">Prompt &mdash; Fossilized Bash</a></li>
237 <li><a href="sync.wiki">Protocol &mdash; The Fossil Sync</a></li>
 
238 <li><a href="history.md">Purpose And History Of Fossil &mdash; The</a></li>
239 <li><a href="faq.wiki">Questions &mdash; Frequently Asked</a></li>
240 <li><a href="qandc.wiki"><b>Questions And Criticisms</b></a></li>
241 <li><a href="quickstart.wiki">Quick Start Guide &mdash; Fossil</a></li>
242 <li><a href="quotes.wiki"><b>Quotes: What People Are Saying About Fossil, Git, and DVCSes in General</b></a></li>
@@ -327,11 +324,10 @@
324 <li><a href="ssl.wiki"><b>Using SSL with Fossil</b></a></li>
325 <li><a href="env-opts.md">Variables and Global Options &mdash; Environment</a></li>
326 <li><a href="whyusefossil.wiki">Version Control &mdash; Benefits Of</a></li>
327 <li><a href="checkin_names.wiki">Version Names &mdash; Check-in And</a></li>
328 <li><a href="fossil-v-git.wiki">Versus Git &mdash; Fossil</a></li>
 
329 <li><a href="image-format-vs-repo-size.md">vs Fossil Repo Size &mdash; Image Format</a></li>
330 <li><a href="grep.md">vs POSIX grep &mdash; Fossil grep</a></li>
331 <li><a href="webui.wiki">Web Interface &mdash; The Fossil</a></li>
332 <li><a href="customskin.md">Web Pages &mdash; Theming: Customizing The Appearance of</a></li>
333 <li><a href="webpage-ex.md"><b>Webpage Examples</b></a></li>
334
--- www/server/debian/nginx.md
+++ www/server/debian/nginx.md
@@ -4,20 +4,19 @@
44
instructions][scgii], which may suffice for your purposes if your needs
55
are simple.
66
77
Here, we add more detailed information on nginx itself, plus details
88
about running it on Debian type OSes. We focus on Debian 10 (Buster) and
9
-Ubuntu 18.04 here, which are common Tier 1 OS offerings for [virtual
10
-private servers][vps]. This material may not work for older OSes. It is
9
+Ubuntu 20.04 here, which are common Tier 1 OS offerings for [virtual
10
+private servers][vps] at the time of writing. This material may not work for older OSes. It is
1111
known in particular to not work as given for Debian 9 and older!
1212
13
-If you want to add TLS to this configuration, that is covered [in a
14
-separate document][tls] which was written with the assumption that
15
-you’ve read this first.
13
+We also cover adding TLS to the basic configuration, because several
14
+details depend on the host OS and web stack details. Besides, TLS is
15
+widely considered part of the baseline configuration these days.
1616
1717
[scgii]: ../any/scgi.md
18
-[tls]: ../../tls-nginx.md
1918
[vps]: https://en.wikipedia.org/wiki/Virtual_private_server
2019
2120
2221
## <a name="benefits"></a>Benefits
2322
@@ -35,21 +34,21 @@
3534
To give you some idea of the sort of thing you can readily
3635
accomplish with nginx, your author runs a single public web server
3736
that provides transparent name-based virtual hosting for four
3837
separate domains:
3938
40
- * One is entirely static, not involving any dynamic content or
41
- Fossil integration at all.
39
+ * <p>One is entirely static, not involving any dynamic content or
40
+ Fossil integration at all.</p>
4241
43
- * Another is served almost entirely by Fossil, with a few select
42
+ * <p>Another is served almost entirely by Fossil, with a few select
4443
static content exceptions punched past Fossil, which are handled
45
- entirely via nginx.
44
+ entirely via nginx.</p>
4645
47
- * The other two domains are aliases for one another — e.g.
46
+ * <p>The other two domains are aliases for one another — e.g.
4847
`example.com` and `example.net` — with most of the content being
49
- static. This pair of domains has three different Fossil repo
50
- proxies attached to various sections of the URI hierarchy.
48
+ static. This pair of domains has several unrelated Fossil repo
49
+ proxies attached to various sections of the URI hierarchy.</p>
5150
5251
By using nginx, I was able to do all of the above with minimal
5352
repetition between the site configurations.
5453
5554
* **Integration** — Because nginx is so popular, it integrates with
@@ -65,37 +64,35 @@
6564
## <a name="modes"></a>Fossil Service Modes
6665
6766
Fossil provides four major ways to access a repository it’s serving
6867
remotely, three of which are straightforward to use with nginx:
6968
70
-* **HTTP** — Fossil has a built-in HTTP server: [`fossil
71
- server`](../any/none.md). While this method is efficient and it’s
69
+* **HTTP** — Fossil has [a built-in HTTP server](../any/none.md).
70
+ While this method is efficient and it’s
7271
possible to use nginx to proxy access to another HTTP server, we
7372
don’t see any particularly good reason to make nginx reinterpret
7473
Fossil’s own implementation of HTTP when we have a better option.
7574
(But see [below](#http).)
75
+
76
+* **SSH** — This method exists primarily to avoid the need for HTTPS,
77
+ but we *want* HTTPS. (We’ll get to that [below](#tls).)
78
+ There is probably a way to get nginx to proxy Fossil to HTTPS via
79
+ SSH, but it would be pointlessly complicated.
7680
7781
* **CGI** — This method is simple but inefficient, because it launches
7882
a separate Fossil instance on every HTTP hit.
79
-
8083
Since Fossil is a relatively small self-contained program, and it’s
8184
designed to start up quickly, this method can work well in a
8285
surprisingly large number of cases.
83
-
8486
Nevertheless, we will avoid this option in this document because
8587
we’re already buying into a certain amount of complexity here in
8688
order to gain power. There’s no sense in throwing away any of that
8789
hard-won performance on CGI overhead.
8890
8991
* **SCGI** — The [SCGI protocol][scgip] provides the simplicity of CGI
9092
without its performance problems.
9193
92
-* **SSH** — This method exists primarily to avoid the need for HTTPS,
93
- but we *want* HTTPS. (We’ll get to that in [another document][tls].)
94
- There is probably a way to get nginx to proxy Fossil to HTTPS via
95
- SSH, but it would be pointlessly complicated.
96
-
9794
SCGI it is, then.
9895
9996
[scgip]: https://en.wikipedia.org/wiki/Simple_Common_Gateway_Interface
10097
10198
@@ -103,10 +100,14 @@
103100
104101
The first step is to install some non-default packages we’ll need. SSH into
105102
your server, then say:
106103
107104
$ sudo apt install fossil nginx
105
+
106
+You can leave “`fossil`” out of that if you’re building Fossil from
107
+source to get a more up-to-date version than is shipped with the host
108
+OS.
108109
109110
110111
## <a name="scgi"></a>Running Fossil in SCGI Mode
111112
112113
For the following nginx configuration to work, it needs to contact a
@@ -190,20 +191,20 @@
190191
repetition across `server { }` blocks when setting up multiple domains
191192
on a single server.
192193
193194
The configuration for `foo.net` is similar.
194195
195
-See [the nginx docs](http://nginx.org/en/docs/) for more ideas.
196
+See [the nginx docs](https://nginx.org/en/docs/) for more ideas.
196197
197198
198199
## <a name="http"></a>Proxying HTTP Anyway
199200
200201
[Above](#modes), we argued that proxying SCGI is a better option than
201202
making nginx reinterpret Fossil’s own implementation of HTTP. If you
202203
want Fossil to speak HTTP, just [set Fossil up as a standalone
203204
server](../any/none.md). And if you want nginx to [provide TLS
204
-encryption for Fossil][tls], proxying HTTP instead of SCGI provides no
205
+encryption for Fossil](#tls), proxying HTTP instead of SCGI provides no
205206
benefit.
206207
207208
However, it is still worth showing the proper method of proxying
208209
Fossil’s HTTP server through nginx if only to make reading nginx
209210
documentation on other sites easier:
@@ -212,10 +213,434 @@
212213
rewrite ^/code(/.*) $1 break;
213214
proxy_pass http://127.0.0.1:12345;
214215
}
215216
216217
The most common thing people get wrong when hand-rolling a configuration
217
-like this is to get the slashes wrong. Fossil is senstitive to this. For
218
+like this is to get the slashes wrong. Fossil is sensitive to this. For
218219
instance, Fossil will not collapse double slashes down to a single
219220
slash, as some other HTTP servers will.
220221
222
+
223
+## <a name="fail2ban"></a> Integrating `fail2ban`
224
+
225
+You can have `fail2ban` recognize attacks and automatically block them,
226
+but the stock configuration doesn’t work with our Fossil setup above, so
227
+we have to do a bit of local adjustment.
228
+
229
+First, install it:
230
+
231
+ sudo apt install fail2ban
232
+
233
+Out of the box, you get SSH monitoring only. There are nginx monitors
234
+included with the package, but they don’t look in the right places for
235
+the right things. We’d like it to react to Fossil `/login` failures, for
236
+example. Put the following into
237
+`/etc/fail2ban/filter.d/nginx-fossil-login.conf`:
238
+
239
+ [Definition]
240
+ failregex = ^<HOST> - .*POST .*/login HTTP/..." 401
241
+
242
+That teaches `fail2ban` how to recognize the errors logged by Fossil
243
+[as of 2.14](/info/39d7eb0e22). (Earlier versions of Fossil returned
244
+HTTP status code 200 for this, so you couldn’t distinguish a successful
245
+login from a failure.)
246
+
247
+Then in `/etc/fail2ban/jail.local`, add this section:
248
+
249
+ [nginx-fossil-login]
250
+ enabled = true
251
+ logpath = /var/log/nginx/*-https-access.log
252
+
253
+The last line is the key: it tells `fail2ban` where we’ve put all of our
254
+per-repo access logs in the nginx config above.
255
+
256
+There’s a [lot more you can do][dof2b], but that gets us out of scope of
257
+this guide.
258
+
259
+
260
+[dof2b]: https://www.digitalocean.com/community/tutorials/how-to-protect-an-nginx-server-with-fail2ban-on-ubuntu-14-04
261
+
262
+
263
+## <a name="tls"></a> Adding TLS (HTTPS) Support
264
+
265
+One of the [many ways](../../ssl.wiki) to provide TLS-encrypted HTTP access
266
+(a.k.a. HTTPS) to Fossil is to run it behind a web proxy that supports
267
+TLS. One such option is nginx on Debian, so we show the details of that
268
+here.
269
+
270
+You can extend this guide to other operating systems by following the
271
+instructions found via [the front Certbot web page][cb] instead, telling
272
+it what OS and web stack you’re using. Chances are good that they’ve got
273
+a good guide for you already.
274
+
275
+
276
+### <a id="leew"></a> Configuring Let’s Encrypt, the Easy Way
277
+
278
+If your web serving needs are simple, [Certbot][cb] can configure nginx
279
+for you and keep its certificates up to date. Simply follow Certbot’s
280
+[nginx on Ubuntu 20.04 LTS guide][cbnu].
281
+
282
+Unfortunately, the setup above was beyond Certbot’s ability to cope the
283
+last time we tried it. The use of per-subdomain files in particular
284
+confused Certbot, so we had to [arrange these details manually](#lehw),
285
+else the Let’s Encrypt [ACME] exchange failed in the necessary domain
286
+validation steps.
287
+
288
+At this point, if your configuration needs are simple, needing only a
289
+single Internet domain and a single Fossil repo, you might wish to try
290
+to reduce the above configuration to a more typical single-file nginx
291
+config, which Certbot might then cope with out of the box.
292
+
293
+
294
+
295
+### <a id="lehw"></a> Configuring Let’s Encrypt, the Hard Way
296
+
297
+The primary motivation for this section is that it documents the manual
298
+Certbot configuration on my public Fossil-based site. I’m addressing
299
+the “me” years hence who needs to upgrade to Ubuntu 22.04 or 24.04 LTS
300
+and has forgotten all of this stuff. 😉
301
+
302
+
303
+#### Step 1: Shifting into Manual
304
+
305
+The first thing we’ll do is install Certbot in the normal way, but we’ll
306
+turn off all of the Certbot automation and won’t follow through with use
307
+of the `--nginx` plugin:
308
+
309
+ $ sudo snap install --classic certbot
310
+ $ sudo systemctl disable certbot.timer
311
+
312
+Next, edit `/etc/letsencrypt/renewal/example.com.conf` to disable the
313
+nginx plugins. You’re looking for two lines setting the “install” and
314
+“auth” plugins to “nginx”. You can comment them out or remove them
315
+entirely.
316
+
317
+
318
+#### Step 2: Configuring nginx
319
+
320
+This is a straightforward extension to the HTTP-only configuration
321
+[above](#config):
322
+
323
+ server {
324
+ server_name .foo.net;
325
+
326
+ include local/tls-common;
327
+
328
+ charset utf-8;
329
+
330
+ access_log /var/log/nginx/foo.net-https-access.log;
331
+ error_log /var/log/nginx/foo.net-https-error.log;
332
+
333
+ # Bypass Fossil for the static Doxygen docs
334
+ location /doc/html {
335
+ root /var/www/foo.net;
336
+
337
+ location ~* \.(html|ico|css|js|gif|jpg|png)$ {
338
+ expires 7d;
339
+ add_header Vary Accept-Encoding;
340
+ access_log off;
341
+ }
342
+ }
343
+
344
+ # Redirect everything else to the Fossil instance
345
+ location / {
346
+ include scgi_params;
347
+ scgi_pass 127.0.0.1:12345;
348
+ scgi_param HTTPS "on";
349
+ scgi_param SCRIPT_NAME "";
350
+ }
351
+ }
352
+ server {
353
+ server_name .foo.net;
354
+ root /var/www/foo.net;
355
+ include local/http-certbot-only;
356
+ access_log /var/log/nginx/foo.net-http-access.log;
357
+ error_log /var/log/nginx/foo.net-http-error.log;
358
+ }
359
+
360
+One big difference between this and the HTTP-only case is
361
+that we need two `server { }` blocks: one for HTTPS service, and
362
+one for HTTP-only service.
363
+
364
+
365
+##### HTTP over TLS (HTTPS) Service
366
+
367
+The first `server { }` block includes this file, `local/tls-common`:
368
+
369
+ listen 443 ssl;
370
+
371
+ ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
372
+ ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
373
+
374
+ ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
375
+
376
+ ssl_stapling on;
377
+ ssl_stapling_verify on;
378
+
379
+ ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
380
+ ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-CBC-SHA:ECDHE-ECDSA-AES256-CBC-SHA:ECDHE-ECDSA-AES128-CBC-SHA256:ECDHE-ECDSA-AES256-CBC-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-CBC-SHA:ECDHE-RSA-AES256-CBC-SHA:ECDHE-RSA-AES128-CBC-SHA256:ECDHE-RSA-AES256-CBC-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-CBC-SHA:DHE-RSA-AES256-CBC-SHA:DHE-RSA-AES128-CBC-SHA256:DHE-RSA-AES256-CBC-SHA256";
381
+ ssl_session_cache shared:le_nginx_SSL:1m;
382
+ ssl_prefer_server_ciphers on;
383
+ ssl_session_timeout 1440m;
384
+
385
+These are the common TLS configuration parameters used by all domains
386
+hosted by this server.
387
+
388
+The first line tells nginx to accept TLS-encrypted HTTP connections on
389
+the standard HTTPS port. It is the same as `listen 443; ssl on;` in
390
+older versions of nginx.
391
+
392
+Since all of those domains share a single TLS certificate, we reference
393
+the same `example.com/*.pem` files written out by Certbot with the
394
+`ssl_certificate*` lines.
395
+
396
+The `ssl_dhparam` directive isn’t strictly required, but without it, the
397
+server becomes vulnerable to the [Logjam attack][lja] because some of
398
+the cryptography steps are precomputed, making the attacker’s job much
399
+easier. The parameter file this directive references should be
400
+generated automatically by the Let’s Encrypt package upon installation,
401
+making those parameters unique to your server and thus unguessable. If
402
+the file doesn’t exist on your system, you can create it manually, so:
403
+
404
+ $ sudo openssl dhparam -out /etc/letsencrypt/dhparams.pem 2048
405
+
406
+Beware, this can take a long time. On a shared Linux host I tried it on
407
+running OpenSSL 1.1.0g, it took about 21 seconds, but on a fast, idle
408
+iMac running LibreSSL 2.6.5, it took 8 minutes and 4 seconds!
409
+
410
+The next section is also optional. It enables [OCSP stapling][ocsp], a
411
+protocol that improves the speed and security of the TLS connection
412
+negotiation.
413
+
414
+The next section containing the `ssl_protocols` and `ssl_ciphers` lines
415
+restricts the TLS implementation to only those protocols and ciphers
416
+that are currently believed to be safe and secure. This section is the
417
+one most prone to bit-rot: as new attacks on TLS and its associated
418
+technologies are discovered, this configuration is likely to need to
419
+change. Even if we fully succeed in keeping this document up-to-date in
420
+the face of the evolving security landscape, we’re recommending static
421
+configurations for your server: it will thus be up to you to track
422
+changes in this document and others to merge the changes into your local
423
+static configuration.
424
+
425
+Running a TLS certificate checker against your site occasionally is a
426
+good idea. The most thorough service I’m aware of is the [Qualys SSL
427
+Labs Test][qslt], which gives the site I’m basing this guide on an “A+”
428
+rating at the time of this writing. The long `ssl_ciphers` line above is
429
+based on [their advice][qslc]: the default nginx configuration tells
430
+OpenSSL to use whatever ciphersuites it considers “high security,” but
431
+some of those have come to be considered “weak” in the time between that
432
+judgement and the time of this writing. By explicitly giving the list of
433
+ciphersuites we want OpenSSL to use within nginx, we can remove those
434
+that become considered weak in the future.
435
+
436
+<a id=”hsts”></a>There are a few things you can do to get an even better
437
+grade, such as to enable [HSTS][hsts]:
438
+
439
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
440
+
441
+This prevents a particular variety of [man in the middle attack][mitm]
442
+where our HTTP-to-HTTPS permanent redirect is intercepted, allowing the
443
+attacker to prevent the automatic upgrade of the connection to a secure
444
+TLS-encrypted one. I didn’t enable that in the configuration above
445
+because it is something a site administrator should enable only after
446
+the configuration is tested and stable, and then only after due
447
+consideration. There are ways to lock your users out of your site by
448
+jumping to HSTS hastily. When you’re ready, there are [guides you can
449
+follow][nest] elsewhere online.
450
+
451
+
452
+##### HTTP-Only Service
453
+
454
+While we’d prefer not to offer HTTP service at all, we need to do so for
455
+two reasons:
456
+
457
+* The temporary reason is that until we get Let’s Encrypt certificates
458
+ minted and configured properly, we can’t use HTTPS yet at all.
459
+
460
+* The ongoing reason is that the Certbot [ACME][acme] HTTP-01
461
+ challenge used by the Let’s Encrypt service only runs over HTTP. This is
462
+ not only because it has to work before HTTPS is first configured,
463
+ but also because it might need to work after a certificate is
464
+ accidentally allowed to lapse to get that server back into a state
465
+ where it can speak HTTPS safely again.
466
+
467
+So, from the second `service { }` block, we include this file to set up
468
+the minimal HTTP service we require, `local/http-certbot-only`:
469
+
470
+ listen 80;
471
+ listen [::]:80;
472
+
473
+ # This is expressed as a rewrite rule instead of an "if" because
474
+ # http://wiki.nginx.org/IfIsEvil
475
+ #rewrite ^(/.well-known/acme-challenge/.*) $1 break;
476
+
477
+ # Force everything else to HTTPS with a permanent redirect.
478
+ #return 301 https://$host$request_uri;
479
+
480
+As written above, this configuration does nothing other than to tell
481
+nginx that it’s allowed to serve content via HTTP on port 80 as well.
482
+We’ll uncomment the `rewrite` and `return` directives below, when we’re
483
+ready to begin testing.
484
+
485
+Notice that most of the nginx directives given [above](#config) moved up
486
+into the TLS `server { }` block, because we eventually want this site to
487
+be as close to HTTPS-only as we can get it.
488
+
489
+
490
+#### Step 3: Dry Run
491
+
492
+We want to first request a dry run, because Let’s Encrypt puts some
493
+rather low limits on how often you’re allowed to request an actual
494
+certificate. You want to be sure everything’s working before you do
495
+that. You’ll run a command something like this:
496
+
497
+ $ sudo certbot certonly --webroot --dry-run \
498
+ --webroot-path /var/www/example.com \
499
+ -d example.com -d www.example.com \
500
+ -d example.net -d www.example.net \
501
+ --webroot-path /var/www/foo.net \
502
+ -d foo.net -d www.foo.net
503
+
504
+There are two key options here.
505
+
506
+First, we’re telling Certbot to use its `--webroot` plugin instead of
507
+the automated `--nginx` plugin. With this plugin, Certbot writes the
508
+[ACME][acme] HTTP-01 challenge files to the static web document root
509
+directory behind each domain. For this example, we’ve got two web
510
+roots, one of which holds documents for two different second-level
511
+domains (`example.com` and `example.net`) with `www` at the third level
512
+being optional. This is a common sort of configuration these days, but
513
+you needn’t feel that you must slavishly imitate it. The other web root
514
+is for an entirely different domain, also with `www` being optional.
515
+Since all of these domains are served by a single nginx instance, we
516
+need to give all of this in a single command, because we want to mint a
517
+single certificate that authenticates all of these domains.
518
+
519
+The second key option is `--dry-run`, which tells Certbot not to do
520
+anything permanent. We’re just seeing if everything works as expected,
521
+at this point.
522
+
523
+
524
+##### Troubleshooting the Dry Run
525
+
526
+If that didn’t work, try creating a manual test:
527
+
528
+ $ mkdir -p /var/www/example.com/.well-known/acme-challenge
529
+ $ echo hi > /var/www/example.com/.well-known/acme-challenge/test
530
+
531
+Then try to pull that file over HTTP — not HTTPS! — as
532
+`http://example.com/.well-known/acme-challenge/test`. I’ve found that
533
+using Firefox or Safari is better for this sort of thing than Chrome,
534
+because Chrome is more aggressive about automatically forwarding URLs to
535
+HTTPS even if you requested “`http`”.
536
+
537
+In extremis, you can do the test manually:
538
+
539
+ $ curl -i http://example.com/.well-known/acme-challenge/test
540
+ HTTP/1.1 200 OK
541
+ Server: nginx/1.14.0 (Ubuntu)
542
+ Date: Sat, 19 Jan 2019 19:43:58 GMT
543
+ Content-Type: application/octet-stream
544
+ Content-Length: 3
545
+ Last-Modified: Sat, 19 Jan 2019 18:21:54 GMT
546
+ Connection: keep-alive
547
+ ETag: "5c436ac2-4"
548
+ Accept-Ranges: bytes
549
+
550
+ hi
551
+
552
+The key bits you’re looking for here are the “200 OK” response code at
553
+the start and the “hi” line at the end. (Or whatever you wrote in to the
554
+test file.)
555
+
556
+If you get a 301 redirect to an `https://` URI, you either haven’t
557
+uncommented the `rewrite` line for HTTP-only service for this directory,
558
+or there’s some other problem with the “redirect to HTTPS” config.
559
+
560
+If you get a 404 or other error response, you need to look into your web
561
+server logs to find out what’s going wrong.
562
+
563
+If you’re still running into trouble, the log file written by Certbot
564
+can be helpful. It tells you where it’s writing the ACME files early in
565
+each run.
566
+
567
+
568
+
569
+#### Step 4: Getting Your First Certificate
570
+
571
+Once the dry run is working, you can drop the `--dry-run` option and
572
+re-run the long command above. (The one with all the `--webroot*`
573
+flags.) This should now succeed, and it will save all of those flag
574
+values to your Let’s Encrypt configuration file, so you don’t need to
575
+keep giving them.
576
+
577
+
578
+
579
+#### Step 5: Test It
580
+
581
+Edit the `local/http-certbot-only` file and uncomment the `redirect` and
582
+`return` directives, then restart your nginx server and make sure it now
583
+forces everything to HTTPS like it should:
584
+
585
+ $ sudo systemctl restart nginx
586
+
587
+Test ideas:
588
+
589
+* Visit both Fossil and non-Fossil URLs
590
+
591
+* Log into the repo, log out, and log back in
592
+
593
+* Clone via `http`: ensure that it redirects to `https`, and that
594
+ subsequent `fossil sync` commands go directly to `https` due to the
595
+ 301 permanent redirect.
596
+
597
+This forced redirect is why we don’t need the Fossil Admin &rarr; Access
598
+"Redirect to HTTPS on the Login page" setting to be enabled. Not only
599
+is it unnecessary with this HTTPS redirect at the front-end proxy level,
600
+it would actually [cause an infinite redirect loop if
601
+enabled](./ssl.wiki#rloop).
602
+
603
+
604
+
605
+#### Step 6: Switch to HTTPS Sync
606
+
607
+Fossil remembers permanent HTTP-to-HTTPS redirects on sync since version
608
+2.9, so all you need to do to switch your syncs to HTTPS is:
609
+
610
+ $ fossil sync -R /path/to/repo.fossil
611
+
612
+
613
+#### Step 7: Renewing Automatically
614
+
615
+Now that the configuration is solid, you can renew the LE cert with the
616
+`certbot` command from above without the `--dry-run` flag plus a restart
617
+of nginx:
618
+
619
+ sudo certbot certonly --webroot \
620
+ --webroot-path /var/www/example.com \
621
+ -d example.com -d www.example.com \
622
+ -d example.net -d www.example.net \
623
+ --webroot-path /var/www/foo.net \
624
+ -d foo.net -d www.foo.net
625
+ sudo systemctl restart nginx
626
+
627
+I put those commands in a script in the `PATH`, then arrange to call that
628
+periodically. Let’s Encrypt doesn’t let you renew the certificate very
629
+often unless forced, and when forced there’s a maximum renewal counter.
630
+Nevertheless, some people recommend running this daily and just letting
631
+it fail until the server lets you renew. Others arrange to run it no
632
+more often than it’s known to work without complaint. Suit yourself.
633
+
634
+
635
+[acme]: https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment
636
+[cb]: https://certbot.eff.org/
637
+[cbnu]: https://certbot.eff.org/lets-encrypt/ubuntufocal-nginx
638
+[hsts]: https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security
639
+[lja]: https://en.wikipedia.org/wiki/Logjam_(computer_security)
640
+[mitm]: https://en.wikipedia.org/wiki/Man-in-the-middle_attack
641
+[nest]: https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/
642
+[ocsp]: https://en.wikipedia.org/wiki/OCSP_stapling
643
+[qslc]: https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices
644
+[qslt]: https://www.ssllabs.com/ssltest/
645
+
221646
*[Return to the top-level Fossil server article.](../)*
222647
--- www/server/debian/nginx.md
+++ www/server/debian/nginx.md
@@ -4,20 +4,19 @@
4 instructions][scgii], which may suffice for your purposes if your needs
5 are simple.
6
7 Here, we add more detailed information on nginx itself, plus details
8 about running it on Debian type OSes. We focus on Debian 10 (Buster) and
9 Ubuntu 18.04 here, which are common Tier 1 OS offerings for [virtual
10 private servers][vps]. This material may not work for older OSes. It is
11 known in particular to not work as given for Debian 9 and older!
12
13 If you want to add TLS to this configuration, that is covered [in a
14 separate document][tls] which was written with the assumption that
15 you’ve read this first.
16
17 [scgii]: ../any/scgi.md
18 [tls]: ../../tls-nginx.md
19 [vps]: https://en.wikipedia.org/wiki/Virtual_private_server
20
21
22 ## <a name="benefits"></a>Benefits
23
@@ -35,21 +34,21 @@
35 To give you some idea of the sort of thing you can readily
36 accomplish with nginx, your author runs a single public web server
37 that provides transparent name-based virtual hosting for four
38 separate domains:
39
40 * One is entirely static, not involving any dynamic content or
41 Fossil integration at all.
42
43 * Another is served almost entirely by Fossil, with a few select
44 static content exceptions punched past Fossil, which are handled
45 entirely via nginx.
46
47 * The other two domains are aliases for one another — e.g.
48 `example.com` and `example.net` — with most of the content being
49 static. This pair of domains has three different Fossil repo
50 proxies attached to various sections of the URI hierarchy.
51
52 By using nginx, I was able to do all of the above with minimal
53 repetition between the site configurations.
54
55 * **Integration** — Because nginx is so popular, it integrates with
@@ -65,37 +64,35 @@
65 ## <a name="modes"></a>Fossil Service Modes
66
67 Fossil provides four major ways to access a repository it’s serving
68 remotely, three of which are straightforward to use with nginx:
69
70 * **HTTP** — Fossil has a built-in HTTP server: [`fossil
71 server`](../any/none.md). While this method is efficient and it’s
72 possible to use nginx to proxy access to another HTTP server, we
73 don’t see any particularly good reason to make nginx reinterpret
74 Fossil’s own implementation of HTTP when we have a better option.
75 (But see [below](#http).)
 
 
 
 
 
76
77 * **CGI** — This method is simple but inefficient, because it launches
78 a separate Fossil instance on every HTTP hit.
79
80 Since Fossil is a relatively small self-contained program, and it’s
81 designed to start up quickly, this method can work well in a
82 surprisingly large number of cases.
83
84 Nevertheless, we will avoid this option in this document because
85 we’re already buying into a certain amount of complexity here in
86 order to gain power. There’s no sense in throwing away any of that
87 hard-won performance on CGI overhead.
88
89 * **SCGI** — The [SCGI protocol][scgip] provides the simplicity of CGI
90 without its performance problems.
91
92 * **SSH** — This method exists primarily to avoid the need for HTTPS,
93 but we *want* HTTPS. (We’ll get to that in [another document][tls].)
94 There is probably a way to get nginx to proxy Fossil to HTTPS via
95 SSH, but it would be pointlessly complicated.
96
97 SCGI it is, then.
98
99 [scgip]: https://en.wikipedia.org/wiki/Simple_Common_Gateway_Interface
100
101
@@ -103,10 +100,14 @@
103
104 The first step is to install some non-default packages we’ll need. SSH into
105 your server, then say:
106
107 $ sudo apt install fossil nginx
 
 
 
 
108
109
110 ## <a name="scgi"></a>Running Fossil in SCGI Mode
111
112 For the following nginx configuration to work, it needs to contact a
@@ -190,20 +191,20 @@
190 repetition across `server { }` blocks when setting up multiple domains
191 on a single server.
192
193 The configuration for `foo.net` is similar.
194
195 See [the nginx docs](http://nginx.org/en/docs/) for more ideas.
196
197
198 ## <a name="http"></a>Proxying HTTP Anyway
199
200 [Above](#modes), we argued that proxying SCGI is a better option than
201 making nginx reinterpret Fossil’s own implementation of HTTP. If you
202 want Fossil to speak HTTP, just [set Fossil up as a standalone
203 server](../any/none.md). And if you want nginx to [provide TLS
204 encryption for Fossil][tls], proxying HTTP instead of SCGI provides no
205 benefit.
206
207 However, it is still worth showing the proper method of proxying
208 Fossil’s HTTP server through nginx if only to make reading nginx
209 documentation on other sites easier:
@@ -212,10 +213,434 @@
212 rewrite ^/code(/.*) $1 break;
213 proxy_pass http://127.0.0.1:12345;
214 }
215
216 The most common thing people get wrong when hand-rolling a configuration
217 like this is to get the slashes wrong. Fossil is senstitive to this. For
218 instance, Fossil will not collapse double slashes down to a single
219 slash, as some other HTTP servers will.
220
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221 *[Return to the top-level Fossil server article.](../)*
222
--- www/server/debian/nginx.md
+++ www/server/debian/nginx.md
@@ -4,20 +4,19 @@
4 instructions][scgii], which may suffice for your purposes if your needs
5 are simple.
6
7 Here, we add more detailed information on nginx itself, plus details
8 about running it on Debian type OSes. We focus on Debian 10 (Buster) and
9 Ubuntu 20.04 here, which are common Tier 1 OS offerings for [virtual
10 private servers][vps] at the time of writing. This material may not work for older OSes. It is
11 known in particular to not work as given for Debian 9 and older!
12
13 We also cover adding TLS to the basic configuration, because several
14 details depend on the host OS and web stack details. Besides, TLS is
15 widely considered part of the baseline configuration these days.
16
17 [scgii]: ../any/scgi.md
 
18 [vps]: https://en.wikipedia.org/wiki/Virtual_private_server
19
20
21 ## <a name="benefits"></a>Benefits
22
@@ -35,21 +34,21 @@
34 To give you some idea of the sort of thing you can readily
35 accomplish with nginx, your author runs a single public web server
36 that provides transparent name-based virtual hosting for four
37 separate domains:
38
39 * <p>One is entirely static, not involving any dynamic content or
40 Fossil integration at all.</p>
41
42 * <p>Another is served almost entirely by Fossil, with a few select
43 static content exceptions punched past Fossil, which are handled
44 entirely via nginx.</p>
45
46 * <p>The other two domains are aliases for one another — e.g.
47 `example.com` and `example.net` — with most of the content being
48 static. This pair of domains has several unrelated Fossil repo
49 proxies attached to various sections of the URI hierarchy.</p>
50
51 By using nginx, I was able to do all of the above with minimal
52 repetition between the site configurations.
53
54 * **Integration** — Because nginx is so popular, it integrates with
@@ -65,37 +64,35 @@
64 ## <a name="modes"></a>Fossil Service Modes
65
66 Fossil provides four major ways to access a repository it’s serving
67 remotely, three of which are straightforward to use with nginx:
68
69 * **HTTP** — Fossil has [a built-in HTTP server](../any/none.md).
70 While this method is efficient and it’s
71 possible to use nginx to proxy access to another HTTP server, we
72 don’t see any particularly good reason to make nginx reinterpret
73 Fossil’s own implementation of HTTP when we have a better option.
74 (But see [below](#http).)
75
76 * **SSH** — This method exists primarily to avoid the need for HTTPS,
77 but we *want* HTTPS. (We’ll get to that [below](#tls).)
78 There is probably a way to get nginx to proxy Fossil to HTTPS via
79 SSH, but it would be pointlessly complicated.
80
81 * **CGI** — This method is simple but inefficient, because it launches
82 a separate Fossil instance on every HTTP hit.
 
83 Since Fossil is a relatively small self-contained program, and it’s
84 designed to start up quickly, this method can work well in a
85 surprisingly large number of cases.
 
86 Nevertheless, we will avoid this option in this document because
87 we’re already buying into a certain amount of complexity here in
88 order to gain power. There’s no sense in throwing away any of that
89 hard-won performance on CGI overhead.
90
91 * **SCGI** — The [SCGI protocol][scgip] provides the simplicity of CGI
92 without its performance problems.
93
 
 
 
 
 
94 SCGI it is, then.
95
96 [scgip]: https://en.wikipedia.org/wiki/Simple_Common_Gateway_Interface
97
98
@@ -103,10 +100,14 @@
100
101 The first step is to install some non-default packages we’ll need. SSH into
102 your server, then say:
103
104 $ sudo apt install fossil nginx
105
106 You can leave “`fossil`” out of that if you’re building Fossil from
107 source to get a more up-to-date version than is shipped with the host
108 OS.
109
110
111 ## <a name="scgi"></a>Running Fossil in SCGI Mode
112
113 For the following nginx configuration to work, it needs to contact a
@@ -190,20 +191,20 @@
191 repetition across `server { }` blocks when setting up multiple domains
192 on a single server.
193
194 The configuration for `foo.net` is similar.
195
196 See [the nginx docs](https://nginx.org/en/docs/) for more ideas.
197
198
199 ## <a name="http"></a>Proxying HTTP Anyway
200
201 [Above](#modes), we argued that proxying SCGI is a better option than
202 making nginx reinterpret Fossil’s own implementation of HTTP. If you
203 want Fossil to speak HTTP, just [set Fossil up as a standalone
204 server](../any/none.md). And if you want nginx to [provide TLS
205 encryption for Fossil](#tls), proxying HTTP instead of SCGI provides no
206 benefit.
207
208 However, it is still worth showing the proper method of proxying
209 Fossil’s HTTP server through nginx if only to make reading nginx
210 documentation on other sites easier:
@@ -212,10 +213,434 @@
213 rewrite ^/code(/.*) $1 break;
214 proxy_pass http://127.0.0.1:12345;
215 }
216
217 The most common thing people get wrong when hand-rolling a configuration
218 like this is to get the slashes wrong. Fossil is sensitive to this. For
219 instance, Fossil will not collapse double slashes down to a single
220 slash, as some other HTTP servers will.
221
222
223 ## <a name="fail2ban"></a> Integrating `fail2ban`
224
225 You can have `fail2ban` recognize attacks and automatically block them,
226 but the stock configuration doesn’t work with our Fossil setup above, so
227 we have to do a bit of local adjustment.
228
229 First, install it:
230
231 sudo apt install fail2ban
232
233 Out of the box, you get SSH monitoring only. There are nginx monitors
234 included with the package, but they don’t look in the right places for
235 the right things. We’d like it to react to Fossil `/login` failures, for
236 example. Put the following into
237 `/etc/fail2ban/filter.d/nginx-fossil-login.conf`:
238
239 [Definition]
240 failregex = ^<HOST> - .*POST .*/login HTTP/..." 401
241
242 That teaches `fail2ban` how to recognize the errors logged by Fossil
243 [as of 2.14](/info/39d7eb0e22). (Earlier versions of Fossil returned
244 HTTP status code 200 for this, so you couldn’t distinguish a successful
245 login from a failure.)
246
247 Then in `/etc/fail2ban/jail.local`, add this section:
248
249 [nginx-fossil-login]
250 enabled = true
251 logpath = /var/log/nginx/*-https-access.log
252
253 The last line is the key: it tells `fail2ban` where we’ve put all of our
254 per-repo access logs in the nginx config above.
255
256 There’s a [lot more you can do][dof2b], but that gets us out of scope of
257 this guide.
258
259
260 [dof2b]: https://www.digitalocean.com/community/tutorials/how-to-protect-an-nginx-server-with-fail2ban-on-ubuntu-14-04
261
262
263 ## <a name="tls"></a> Adding TLS (HTTPS) Support
264
265 One of the [many ways](../../ssl.wiki) to provide TLS-encrypted HTTP access
266 (a.k.a. HTTPS) to Fossil is to run it behind a web proxy that supports
267 TLS. One such option is nginx on Debian, so we show the details of that
268 here.
269
270 You can extend this guide to other operating systems by following the
271 instructions found via [the front Certbot web page][cb] instead, telling
272 it what OS and web stack you’re using. Chances are good that they’ve got
273 a good guide for you already.
274
275
276 ### <a id="leew"></a> Configuring Let’s Encrypt, the Easy Way
277
278 If your web serving needs are simple, [Certbot][cb] can configure nginx
279 for you and keep its certificates up to date. Simply follow Certbot’s
280 [nginx on Ubuntu 20.04 LTS guide][cbnu].
281
282 Unfortunately, the setup above was beyond Certbot’s ability to cope the
283 last time we tried it. The use of per-subdomain files in particular
284 confused Certbot, so we had to [arrange these details manually](#lehw),
285 else the Let’s Encrypt [ACME] exchange failed in the necessary domain
286 validation steps.
287
288 At this point, if your configuration needs are simple, needing only a
289 single Internet domain and a single Fossil repo, you might wish to try
290 to reduce the above configuration to a more typical single-file nginx
291 config, which Certbot might then cope with out of the box.
292
293
294
295 ### <a id="lehw"></a> Configuring Let’s Encrypt, the Hard Way
296
297 The primary motivation for this section is that it documents the manual
298 Certbot configuration on my public Fossil-based site. I’m addressing
299 the “me” years hence who needs to upgrade to Ubuntu 22.04 or 24.04 LTS
300 and has forgotten all of this stuff. 😉
301
302
303 #### Step 1: Shifting into Manual
304
305 The first thing we’ll do is install Certbot in the normal way, but we’ll
306 turn off all of the Certbot automation and won’t follow through with use
307 of the `--nginx` plugin:
308
309 $ sudo snap install --classic certbot
310 $ sudo systemctl disable certbot.timer
311
312 Next, edit `/etc/letsencrypt/renewal/example.com.conf` to disable the
313 nginx plugins. You’re looking for two lines setting the “install” and
314 “auth” plugins to “nginx”. You can comment them out or remove them
315 entirely.
316
317
318 #### Step 2: Configuring nginx
319
320 This is a straightforward extension to the HTTP-only configuration
321 [above](#config):
322
323 server {
324 server_name .foo.net;
325
326 include local/tls-common;
327
328 charset utf-8;
329
330 access_log /var/log/nginx/foo.net-https-access.log;
331 error_log /var/log/nginx/foo.net-https-error.log;
332
333 # Bypass Fossil for the static Doxygen docs
334 location /doc/html {
335 root /var/www/foo.net;
336
337 location ~* \.(html|ico|css|js|gif|jpg|png)$ {
338 expires 7d;
339 add_header Vary Accept-Encoding;
340 access_log off;
341 }
342 }
343
344 # Redirect everything else to the Fossil instance
345 location / {
346 include scgi_params;
347 scgi_pass 127.0.0.1:12345;
348 scgi_param HTTPS "on";
349 scgi_param SCRIPT_NAME "";
350 }
351 }
352 server {
353 server_name .foo.net;
354 root /var/www/foo.net;
355 include local/http-certbot-only;
356 access_log /var/log/nginx/foo.net-http-access.log;
357 error_log /var/log/nginx/foo.net-http-error.log;
358 }
359
360 One big difference between this and the HTTP-only case is
361 that we need two `server { }` blocks: one for HTTPS service, and
362 one for HTTP-only service.
363
364
365 ##### HTTP over TLS (HTTPS) Service
366
367 The first `server { }` block includes this file, `local/tls-common`:
368
369 listen 443 ssl;
370
371 ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
372 ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
373
374 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
375
376 ssl_stapling on;
377 ssl_stapling_verify on;
378
379 ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
380 ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-CBC-SHA:ECDHE-ECDSA-AES256-CBC-SHA:ECDHE-ECDSA-AES128-CBC-SHA256:ECDHE-ECDSA-AES256-CBC-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-CBC-SHA:ECDHE-RSA-AES256-CBC-SHA:ECDHE-RSA-AES128-CBC-SHA256:ECDHE-RSA-AES256-CBC-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-CBC-SHA:DHE-RSA-AES256-CBC-SHA:DHE-RSA-AES128-CBC-SHA256:DHE-RSA-AES256-CBC-SHA256";
381 ssl_session_cache shared:le_nginx_SSL:1m;
382 ssl_prefer_server_ciphers on;
383 ssl_session_timeout 1440m;
384
385 These are the common TLS configuration parameters used by all domains
386 hosted by this server.
387
388 The first line tells nginx to accept TLS-encrypted HTTP connections on
389 the standard HTTPS port. It is the same as `listen 443; ssl on;` in
390 older versions of nginx.
391
392 Since all of those domains share a single TLS certificate, we reference
393 the same `example.com/*.pem` files written out by Certbot with the
394 `ssl_certificate*` lines.
395
396 The `ssl_dhparam` directive isn’t strictly required, but without it, the
397 server becomes vulnerable to the [Logjam attack][lja] because some of
398 the cryptography steps are precomputed, making the attacker’s job much
399 easier. The parameter file this directive references should be
400 generated automatically by the Let’s Encrypt package upon installation,
401 making those parameters unique to your server and thus unguessable. If
402 the file doesn’t exist on your system, you can create it manually, so:
403
404 $ sudo openssl dhparam -out /etc/letsencrypt/dhparams.pem 2048
405
406 Beware, this can take a long time. On a shared Linux host I tried it on
407 running OpenSSL 1.1.0g, it took about 21 seconds, but on a fast, idle
408 iMac running LibreSSL 2.6.5, it took 8 minutes and 4 seconds!
409
410 The next section is also optional. It enables [OCSP stapling][ocsp], a
411 protocol that improves the speed and security of the TLS connection
412 negotiation.
413
414 The next section containing the `ssl_protocols` and `ssl_ciphers` lines
415 restricts the TLS implementation to only those protocols and ciphers
416 that are currently believed to be safe and secure. This section is the
417 one most prone to bit-rot: as new attacks on TLS and its associated
418 technologies are discovered, this configuration is likely to need to
419 change. Even if we fully succeed in keeping this document up-to-date in
420 the face of the evolving security landscape, we’re recommending static
421 configurations for your server: it will thus be up to you to track
422 changes in this document and others to merge the changes into your local
423 static configuration.
424
425 Running a TLS certificate checker against your site occasionally is a
426 good idea. The most thorough service I’m aware of is the [Qualys SSL
427 Labs Test][qslt], which gives the site I’m basing this guide on an “A+”
428 rating at the time of this writing. The long `ssl_ciphers` line above is
429 based on [their advice][qslc]: the default nginx configuration tells
430 OpenSSL to use whatever ciphersuites it considers “high security,” but
431 some of those have come to be considered “weak” in the time between that
432 judgement and the time of this writing. By explicitly giving the list of
433 ciphersuites we want OpenSSL to use within nginx, we can remove those
434 that become considered weak in the future.
435
436 <a id=”hsts”></a>There are a few things you can do to get an even better
437 grade, such as to enable [HSTS][hsts]:
438
439 add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
440
441 This prevents a particular variety of [man in the middle attack][mitm]
442 where our HTTP-to-HTTPS permanent redirect is intercepted, allowing the
443 attacker to prevent the automatic upgrade of the connection to a secure
444 TLS-encrypted one. I didn’t enable that in the configuration above
445 because it is something a site administrator should enable only after
446 the configuration is tested and stable, and then only after due
447 consideration. There are ways to lock your users out of your site by
448 jumping to HSTS hastily. When you’re ready, there are [guides you can
449 follow][nest] elsewhere online.
450
451
452 ##### HTTP-Only Service
453
454 While we’d prefer not to offer HTTP service at all, we need to do so for
455 two reasons:
456
457 * The temporary reason is that until we get Let’s Encrypt certificates
458 minted and configured properly, we can’t use HTTPS yet at all.
459
460 * The ongoing reason is that the Certbot [ACME][acme] HTTP-01
461 challenge used by the Let’s Encrypt service only runs over HTTP. This is
462 not only because it has to work before HTTPS is first configured,
463 but also because it might need to work after a certificate is
464 accidentally allowed to lapse to get that server back into a state
465 where it can speak HTTPS safely again.
466
467 So, from the second `service { }` block, we include this file to set up
468 the minimal HTTP service we require, `local/http-certbot-only`:
469
470 listen 80;
471 listen [::]:80;
472
473 # This is expressed as a rewrite rule instead of an "if" because
474 # http://wiki.nginx.org/IfIsEvil
475 #rewrite ^(/.well-known/acme-challenge/.*) $1 break;
476
477 # Force everything else to HTTPS with a permanent redirect.
478 #return 301 https://$host$request_uri;
479
480 As written above, this configuration does nothing other than to tell
481 nginx that it’s allowed to serve content via HTTP on port 80 as well.
482 We’ll uncomment the `rewrite` and `return` directives below, when we’re
483 ready to begin testing.
484
485 Notice that most of the nginx directives given [above](#config) moved up
486 into the TLS `server { }` block, because we eventually want this site to
487 be as close to HTTPS-only as we can get it.
488
489
490 #### Step 3: Dry Run
491
492 We want to first request a dry run, because Let’s Encrypt puts some
493 rather low limits on how often you’re allowed to request an actual
494 certificate. You want to be sure everything’s working before you do
495 that. You’ll run a command something like this:
496
497 $ sudo certbot certonly --webroot --dry-run \
498 --webroot-path /var/www/example.com \
499 -d example.com -d www.example.com \
500 -d example.net -d www.example.net \
501 --webroot-path /var/www/foo.net \
502 -d foo.net -d www.foo.net
503
504 There are two key options here.
505
506 First, we’re telling Certbot to use its `--webroot` plugin instead of
507 the automated `--nginx` plugin. With this plugin, Certbot writes the
508 [ACME][acme] HTTP-01 challenge files to the static web document root
509 directory behind each domain. For this example, we’ve got two web
510 roots, one of which holds documents for two different second-level
511 domains (`example.com` and `example.net`) with `www` at the third level
512 being optional. This is a common sort of configuration these days, but
513 you needn’t feel that you must slavishly imitate it. The other web root
514 is for an entirely different domain, also with `www` being optional.
515 Since all of these domains are served by a single nginx instance, we
516 need to give all of this in a single command, because we want to mint a
517 single certificate that authenticates all of these domains.
518
519 The second key option is `--dry-run`, which tells Certbot not to do
520 anything permanent. We’re just seeing if everything works as expected,
521 at this point.
522
523
524 ##### Troubleshooting the Dry Run
525
526 If that didn’t work, try creating a manual test:
527
528 $ mkdir -p /var/www/example.com/.well-known/acme-challenge
529 $ echo hi > /var/www/example.com/.well-known/acme-challenge/test
530
531 Then try to pull that file over HTTP — not HTTPS! — as
532 `http://example.com/.well-known/acme-challenge/test`. I’ve found that
533 using Firefox or Safari is better for this sort of thing than Chrome,
534 because Chrome is more aggressive about automatically forwarding URLs to
535 HTTPS even if you requested “`http`”.
536
537 In extremis, you can do the test manually:
538
539 $ curl -i http://example.com/.well-known/acme-challenge/test
540 HTTP/1.1 200 OK
541 Server: nginx/1.14.0 (Ubuntu)
542 Date: Sat, 19 Jan 2019 19:43:58 GMT
543 Content-Type: application/octet-stream
544 Content-Length: 3
545 Last-Modified: Sat, 19 Jan 2019 18:21:54 GMT
546 Connection: keep-alive
547 ETag: "5c436ac2-4"
548 Accept-Ranges: bytes
549
550 hi
551
552 The key bits you’re looking for here are the “200 OK” response code at
553 the start and the “hi” line at the end. (Or whatever you wrote in to the
554 test file.)
555
556 If you get a 301 redirect to an `https://` URI, you either haven’t
557 uncommented the `rewrite` line for HTTP-only service for this directory,
558 or there’s some other problem with the “redirect to HTTPS” config.
559
560 If you get a 404 or other error response, you need to look into your web
561 server logs to find out what’s going wrong.
562
563 If you’re still running into trouble, the log file written by Certbot
564 can be helpful. It tells you where it’s writing the ACME files early in
565 each run.
566
567
568
569 #### Step 4: Getting Your First Certificate
570
571 Once the dry run is working, you can drop the `--dry-run` option and
572 re-run the long command above. (The one with all the `--webroot*`
573 flags.) This should now succeed, and it will save all of those flag
574 values to your Let’s Encrypt configuration file, so you don’t need to
575 keep giving them.
576
577
578
579 #### Step 5: Test It
580
581 Edit the `local/http-certbot-only` file and uncomment the `redirect` and
582 `return` directives, then restart your nginx server and make sure it now
583 forces everything to HTTPS like it should:
584
585 $ sudo systemctl restart nginx
586
587 Test ideas:
588
589 * Visit both Fossil and non-Fossil URLs
590
591 * Log into the repo, log out, and log back in
592
593 * Clone via `http`: ensure that it redirects to `https`, and that
594 subsequent `fossil sync` commands go directly to `https` due to the
595 301 permanent redirect.
596
597 This forced redirect is why we don’t need the Fossil Admin &rarr; Access
598 "Redirect to HTTPS on the Login page" setting to be enabled. Not only
599 is it unnecessary with this HTTPS redirect at the front-end proxy level,
600 it would actually [cause an infinite redirect loop if
601 enabled](./ssl.wiki#rloop).
602
603
604
605 #### Step 6: Switch to HTTPS Sync
606
607 Fossil remembers permanent HTTP-to-HTTPS redirects on sync since version
608 2.9, so all you need to do to switch your syncs to HTTPS is:
609
610 $ fossil sync -R /path/to/repo.fossil
611
612
613 #### Step 7: Renewing Automatically
614
615 Now that the configuration is solid, you can renew the LE cert with the
616 `certbot` command from above without the `--dry-run` flag plus a restart
617 of nginx:
618
619 sudo certbot certonly --webroot \
620 --webroot-path /var/www/example.com \
621 -d example.com -d www.example.com \
622 -d example.net -d www.example.net \
623 --webroot-path /var/www/foo.net \
624 -d foo.net -d www.foo.net
625 sudo systemctl restart nginx
626
627 I put those commands in a script in the `PATH`, then arrange to call that
628 periodically. Let’s Encrypt doesn’t let you renew the certificate very
629 often unless forced, and when forced there’s a maximum renewal counter.
630 Nevertheless, some people recommend running this daily and just letting
631 it fail until the server lets you renew. Others arrange to run it no
632 more often than it’s known to work without complaint. Suit yourself.
633
634
635 [acme]: https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment
636 [cb]: https://certbot.eff.org/
637 [cbnu]: https://certbot.eff.org/lets-encrypt/ubuntufocal-nginx
638 [hsts]: https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security
639 [lja]: https://en.wikipedia.org/wiki/Logjam_(computer_security)
640 [mitm]: https://en.wikipedia.org/wiki/Man-in-the-middle_attack
641 [nest]: https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/
642 [ocsp]: https://en.wikipedia.org/wiki/OCSP_stapling
643 [qslc]: https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices
644 [qslt]: https://www.ssllabs.com/ssltest/
645
646 *[Return to the top-level Fossil server article.](../)*
647
+8 -2
--- www/ssl.wiki
+++ www/ssl.wiki
@@ -235,11 +235,11 @@
235235
elsewhere in this repository that cover your options for [./server/
236236
| serving Fossil repositories]. A few of the most useful of these are:
237237
238238
* <a id="stunnel" href="./server/any/stunnel.md">Serving via stunnel</a>
239239
* <a id="althttpd" href="./server/any/althttpd.md">Serving via stunnel + althttpd</a>
240
- * <a id="nginx" href="./server/any/scgi.md">Serving via SCGI (nginx)</a>
240
+ * <a id="nginx" href="./server/debian/nginx.md#tls">Serving via SCGI with nginx on Debian</a>
241241
242242
243243
<h2 id="enforcing">Enforcing TLS Access</h2>
244244
245245
To use TLS encryption in cloning and syncing to a remote Fossil
@@ -246,10 +246,16 @@
246246
repository, be sure to use the <tt>https:</tt> URI scheme in
247247
<tt>clone</tt> and <tt>sync</tt> commands. If your server is configured
248248
to serve the repository via both HTTP and HTTPS, it's easy to
249249
accidentally use unencrypted HTTP if you forget the all-important 's'.
250250
251
+As of Fossil 2.9, using an <tt>http://</tt> URI with <tt>fossil
252
+clone</tt> or <tt>sync</tt> on a site that forwards to HTTPS will cause
253
+Fossil to remember the secure URL. However, there's a
254
+[https://en.wikipedia.org/wiki/Trust_on_first_use | TOFU problem] with
255
+this: it's still better to use <tt>https://</tt> from the start.
256
+
251257
As of Fossil 2.8, there is a setting in the Fossil UI under Admin &rarr;
252258
Access called "Redirect to HTTPS," which is set to "Off" by default.
253259
Changing this only affects web UI access to the Fossil repository. It
254260
doesn't affect clones and syncs done via the <tt>http</tt> URI scheme.
255261
@@ -280,11 +286,11 @@
280286
fix the setting, and then upload it to the repository server
281287
again.</p>
282288
283289
It's best to enforce TLS-only access at the front-end proxy level
284290
anyway. It not only avoids the problem entirely, it can be significantly
285
-more secure. The [./tls-nginx.md|nginx TLS proxy guide] shows one way
291
+more secure. The [server/debian/nginx.md#tls | nginx-on-Debian proxy guide] shows one way
286292
to achieve this.</p>
287293
288294
289295
<h2>Terminology Note</h2>
290296
291297
--- www/ssl.wiki
+++ www/ssl.wiki
@@ -235,11 +235,11 @@
235 elsewhere in this repository that cover your options for [./server/
236 | serving Fossil repositories]. A few of the most useful of these are:
237
238 * <a id="stunnel" href="./server/any/stunnel.md">Serving via stunnel</a>
239 * <a id="althttpd" href="./server/any/althttpd.md">Serving via stunnel + althttpd</a>
240 * <a id="nginx" href="./server/any/scgi.md">Serving via SCGI (nginx)</a>
241
242
243 <h2 id="enforcing">Enforcing TLS Access</h2>
244
245 To use TLS encryption in cloning and syncing to a remote Fossil
@@ -246,10 +246,16 @@
246 repository, be sure to use the <tt>https:</tt> URI scheme in
247 <tt>clone</tt> and <tt>sync</tt> commands. If your server is configured
248 to serve the repository via both HTTP and HTTPS, it's easy to
249 accidentally use unencrypted HTTP if you forget the all-important 's'.
250
 
 
 
 
 
 
251 As of Fossil 2.8, there is a setting in the Fossil UI under Admin &rarr;
252 Access called "Redirect to HTTPS," which is set to "Off" by default.
253 Changing this only affects web UI access to the Fossil repository. It
254 doesn't affect clones and syncs done via the <tt>http</tt> URI scheme.
255
@@ -280,11 +286,11 @@
280 fix the setting, and then upload it to the repository server
281 again.</p>
282
283 It's best to enforce TLS-only access at the front-end proxy level
284 anyway. It not only avoids the problem entirely, it can be significantly
285 more secure. The [./tls-nginx.md|nginx TLS proxy guide] shows one way
286 to achieve this.</p>
287
288
289 <h2>Terminology Note</h2>
290
291
--- www/ssl.wiki
+++ www/ssl.wiki
@@ -235,11 +235,11 @@
235 elsewhere in this repository that cover your options for [./server/
236 | serving Fossil repositories]. A few of the most useful of these are:
237
238 * <a id="stunnel" href="./server/any/stunnel.md">Serving via stunnel</a>
239 * <a id="althttpd" href="./server/any/althttpd.md">Serving via stunnel + althttpd</a>
240 * <a id="nginx" href="./server/debian/nginx.md#tls">Serving via SCGI with nginx on Debian</a>
241
242
243 <h2 id="enforcing">Enforcing TLS Access</h2>
244
245 To use TLS encryption in cloning and syncing to a remote Fossil
@@ -246,10 +246,16 @@
246 repository, be sure to use the <tt>https:</tt> URI scheme in
247 <tt>clone</tt> and <tt>sync</tt> commands. If your server is configured
248 to serve the repository via both HTTP and HTTPS, it's easy to
249 accidentally use unencrypted HTTP if you forget the all-important 's'.
250
251 As of Fossil 2.9, using an <tt>http://</tt> URI with <tt>fossil
252 clone</tt> or <tt>sync</tt> on a site that forwards to HTTPS will cause
253 Fossil to remember the secure URL. However, there's a
254 [https://en.wikipedia.org/wiki/Trust_on_first_use | TOFU problem] with
255 this: it's still better to use <tt>https://</tt> from the start.
256
257 As of Fossil 2.8, there is a setting in the Fossil UI under Admin &rarr;
258 Access called "Redirect to HTTPS," which is set to "Off" by default.
259 Changing this only affects web UI access to the Fossil repository. It
260 doesn't affect clones and syncs done via the <tt>http</tt> URI scheme.
261
@@ -280,11 +286,11 @@
286 fix the setting, and then upload it to the repository server
287 again.</p>
288
289 It's best to enforce TLS-only access at the front-end proxy level
290 anyway. It not only avoids the problem entirely, it can be significantly
291 more secure. The [server/debian/nginx.md#tls | nginx-on-Debian proxy guide] shows one way
292 to achieve this.</p>
293
294
295 <h2>Terminology Note</h2>
296
297
+1 -427
--- www/tls-nginx.md
+++ www/tls-nginx.md
@@ -1,430 +1,3 @@
11
# Proxying Fossil via HTTPS with nginx
22
3
-One of the [many ways](./ssl.wiki) to provide TLS-encrypted HTTP access
4
-(a.k.a. HTTPS) to Fossil is to run it behind a web proxy that supports
5
-TLS. This document explains how to use the powerful [nginx web
6
-server](http://nginx.org/) to do that.
7
-
8
-This document is an extension of the [Serving via nginx on Debian][nod]
9
-document. Please read that first, then come back here to extend its
10
-configuration with TLS.
11
-
12
-[nod]: ./server/debian/nginx.md
13
-
14
-
15
-## Install Certbot
16
-
17
-The [nginx-on-Debian document][nod] had you install a few non-default
18
-packages to the system, but there’s one more you need for this guide:
19
-
20
- $ sudo apt install certbot
21
-
22
-You can extend this guide to other operating systems by following the
23
-instructions found via [the front Certbot web page][cb] instead, telling
24
-it what OS and web stack you’re using. Chances are good that they’ve got
25
-a good guide for you already.
26
-
27
-
28
-# Configuring Let’s Encrypt, the Easy Way
29
-
30
-If your web serving needs are simple, [Certbot][cb] can configure nginx
31
-for you and keep its certificates up to date. Simply follow Certbot’s
32
-[nginx on Ubuntu 18.04 LTS guide][cbnu]. We’d recommend one small
33
-change: to use the version of Certbot in the Ubuntu package repository
34
-rather than download it from the Certbot site.
35
-
36
-You should be able to use the nginx configuration given in our [Serving
37
-via nginx on Debian][nod] guide with little to no change. The main thing
38
-to watch out for is that the TCP port number in the nginx configuration
39
-needs to match the value you gave when starting Fossil. If you followed
40
-that guide’s advice, it will be 9000. Another option is to use [the
41
-`fslsrv` script](/file/tools/fslsrv), in which case the TCP port number
42
-will be 12345 or higher.
43
-
44
-
45
-# Configuring Let’s Encrypt, the Hard Way
46
-
47
-If you’re finding that you can’t get certificates to be issued or
48
-renewed using the Easy Way instructions, the problem is usually that
49
-your nginx configuration is too complicated for Certbot’s `--nginx`
50
-plugin to understand. It attempts to rewrite your nginx configuration
51
-files on the fly to achieve the renewal, and if it doesn’t put its
52
-directives in the right locations, the domain verification can fail.
53
-
54
-Let’s Encrypt uses the [Automated Certificate Management
55
-Environment][acme] protocol (ACME) to determine whether a given client
56
-actually has control over the domain(s) for which it wants a certificate
57
-minted. Let’s Encrypt will not blithely let you mint certificates for
58
-`google.com` and `paypal.com` just because you ask for it!
59
-
60
-Your author’s configuration, glossed [in the HTTP-only guide][nod],
61
-is complicated enough that
62
-the current version of Certbot (0.28 at the time of this writing) can’t
63
-cope with it. That’s the primary motivation for me to write this guide:
64
-I’m addressing the “me” years hence who needs to upgrade to Ubuntu 20.04
65
-or 22.04 LTS and has forgotten all of this stuff. 😉
66
-
67
-
68
-## Step 1: Shifting into Manual
69
-
70
-The first thing to do is to turn off all of the Certbot automation,
71
-because it’ll only get in our way. First, disable the Certbot package’s
72
-automatic background updater:
73
-
74
- $ sudo systemctl disable certbot.timer
75
-
76
-Next, edit `/etc/letsencrypt/renewal/example.com.conf` to disable the
77
-nginx plugins. You’re looking for two lines setting the “install” and
78
-“auth” plugins to “nginx”. You can comment them out or remove them
79
-entirely.
80
-
81
-
82
-## Step 2: Configuring nginx
83
-
84
-This is a straightforward extension to [the HTTP-only
85
-configuration](./server/debian/nginx.md#config):
86
-
87
- server {
88
- server_name .foo.net;
89
-
90
- include local/tls-common;
91
-
92
- charset utf-8;
93
-
94
- access_log /var/log/nginx/foo.net-https-access.log;
95
- error_log /var/log/nginx/foo.net-https-error.log;
96
-
97
- # Bypass Fossil for the static Doxygen docs
98
- location /doc/html {
99
- root /var/www/foo.net;
100
-
101
- location ~* \.(html|ico|css|js|gif|jpg|png)$ {
102
- expires 7d;
103
- add_header Vary Accept-Encoding;
104
- access_log off;
105
- }
106
- }
107
-
108
- # Redirect everything else to the Fossil instance
109
- location / {
110
- include scgi_params;
111
- scgi_pass 127.0.0.1:12345;
112
- scgi_param HTTPS "on";
113
- scgi_param SCRIPT_NAME "";
114
- }
115
- }
116
- server {
117
- server_name .foo.net;
118
- root /var/www/foo.net;
119
- include local/http-certbot-only;
120
- access_log /var/log/nginx/foo.net-http-access.log;
121
- error_log /var/log/nginx/foo.net-http-error.log;
122
- }
123
-
124
-One big difference between this and the HTTP-only case is
125
-that we need two `server { }` blocks: one for HTTPS service, and
126
-one for HTTP-only service.
127
-
128
-
129
-### HTTP over TLS (HTTPS) Service
130
-
131
-The first `server { }` block includes this file, `local/tls-common`:
132
-
133
- listen 443 ssl;
134
-
135
- ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
136
- ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
137
-
138
- ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
139
-
140
- ssl_stapling on;
141
- ssl_stapling_verify on;
142
-
143
- ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
144
- ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256”;
145
- ssl_session_cache shared:le_nginx_SSL:1m;
146
- ssl_prefer_server_ciphers on;
147
- ssl_session_timeout 1440m;
148
-
149
-These are the common TLS configuration parameters used by all domains
150
-hosted by this server.
151
-
152
-The first line tells nginx to accept TLS-encrypted HTTP connections on
153
-the standard HTTPS port. It is the same as `listen 443; ssl on;` in
154
-older versions of nginx.
155
-
156
-Since all of those domains share a single TLS certificate, we reference
157
-the same `example.com/*.pem` files written out by Certbot with the
158
-`ssl_certificate*` lines.
159
-
160
-The `ssl_dhparam` directive isn’t strictly required, but without it, the
161
-server becomes vulnerable to the [Logjam attack][lja] because some of
162
-the cryptography steps are precomputed, making the attacker’s job much
163
-easier. The parameter file this directive references should be
164
-generated automatically by the Let’s Encrypt package upon installation,
165
-making those parameters unique to your server and thus unguessable. If
166
-the file doesn’t exist on your system, you can create it manually, so:
167
-
168
- $ sudo openssl dhparam -out /etc/letsencrypt/dhparams.pem 2048
169
-
170
-Beware, this can take a long time. On a shared Linux host I tried it on
171
-running OpenSSL 1.1.0g, it took about 21 seconds, but on a fast, idle
172
-iMac running LibreSSL 2.6.5, it took 8 minutes and 4 seconds!
173
-
174
-The next section is also optional. It enables [OCSP stapling][ocsp], a
175
-protocol that improves the speed and security of the TLS connection
176
-negotiation.
177
-
178
-The next section containing the `ssl_protocols` and `ssl_ciphers` lines
179
-restricts the TLS implementation to only those protocols and ciphers
180
-that are currently believed to be safe and secure. This section is the
181
-one most prone to bit-rot: as new attacks on TLS and its associated
182
-technologies are discovered, this configuration is likely to need to
183
-change. Even if we fully succeed in [keeping this document
184
-up-to-date](#evolution), the nature of this guide is to recommend static
185
-configurations for your server. You will have to keep an eye on this
186
-sort of thing and evolve your local configuration as the world changes
187
-around it.
188
-
189
-Running a TLS certificate checker against your site occasionally is a
190
-good idea. The most thorough service I’m aware of is the [Qualys SSL
191
-Labs Test][qslt], which gives the site I’m basing this guide on an “A”
192
-rating at the time of this writing. The long `ssl_ciphers` line above is
193
-based on [their advice][qslc]: the default nginx configuration tells
194
-OpenSSL to use whatever ciphersuites it considers “high security,” but
195
-some of those have come to be considered “weak” in the time between that
196
-judgement and the time of this writing. By explicitly giving the list of
197
-ciphersuites we want OpenSSL to use within nginx, we can remove those
198
-that become considered weak in the future.
199
-
200
-<a id=”hsts”></a>There are a few things you can do to get an even better
201
-grade, such as to enable [HSTS][hsts], which prevents a particular
202
-variety of [man in the middle attack][mitm] where our HTTP-to-HTTPS
203
-permanent redirect is intercepted, allowing the attacker to prevent the
204
-automatic upgrade of the connection to a secure TLS-encrypted one. I
205
-didn’t enable that in the configuration above, because it is something a
206
-site administrator should enable only after the configuration is tested
207
-and stable, and then only after due consideration. There are ways to
208
-lock your users out of your site by jumping to HSTS hastily. When you’re
209
-ready, there are [guides you can follow][nest] elsewhere online.
210
-
211
-
212
-### HTTP-Only Service
213
-
214
-While we’d prefer not to offer HTTP service at all, we need to do so for
215
-two reasons:
216
-
217
-* The temporary reason is that until we get Let’s Encrypt certificates
218
- minted and configured properly, we can’t use HTTPS yet at all.
219
-
220
-* The ongoing reason is that the Certbot [ACME][acme] HTTP-01
221
- challenge used by the Let’s Encrypt service only runs over HTTP. This is
222
- not only because it has to work before HTTPS is first configured,
223
- but also because it might need to work after a certificate is
224
- accidentally allowed to lapse, to get that server back into a state
225
- where it can speak HTTPS safely again.
226
-
227
-So, from the second `service { }` block, we include this file to set up
228
-the minimal HTTP service we require, `local/http-certbot-only`:
229
-
230
- listen 80;
231
- listen [::]:80;
232
-
233
- # This is expressed as a rewrite rule instead of an "if" because
234
- # http://wiki.nginx.org/IfIsEvil
235
- #rewrite ^(/.well-known/acme-challenge/.*) $1 break;
236
-
237
- # Force everything else to HTTPS with a permanent redirect.
238
- #return 301 https://$host$request_uri;
239
-
240
-As written above, this configuration does nothing other than to tell
241
-nginx that it’s allowed to serve content via HTTP on port 80 as well.
242
-We’ll uncomment the `rewrite` and `return` directives below, when we’re
243
-ready to begin testing.
244
-
245
-Notice that this configuration is very different from that in the
246
-[HTTP-only nginx on Debian][nod] guide. Most of that guide’s nginx
247
-directives moved up into the TLS `server { }` block, because we
248
-eventually want this site to be as close to HTTPS-only as we can get it.
249
-
250
-
251
-## Step 3: Dry Run
252
-
253
-We want to first request a dry run, because Let’s Encrypt puts some
254
-rather low limits on how often you’re allowed to request an actual
255
-certificate. You want to be sure everything’s working before you do
256
-that. You’ll run a command something like this:
257
-
258
- $ sudo certbot certonly --webroot --dry-run \
259
- --webroot-path /var/www/example.com \
260
- -d example.com -d www.example.com \
261
- -d example.net -d www.example.net \
262
- --webroot-path /var/www/foo.net \
263
- -d foo.net -d www.foo.net
264
-
265
-There are two key options here.
266
-
267
-First, we’re telling Certbot to use its `--webroot` plugin instead of
268
-the automated `--nginx` plugin. With this plugin, Certbot writes the
269
-[ACME][acme] HTTP-01 challenge files to the static web document root
270
-directory behind each domain. For this example, we’ve got two web
271
-roots, one of which holds documents for two different second-level
272
-domains (`example.com` and `example.net`) with `www` at the third level
273
-being optional. This is a common sort of configuration these days, but
274
-you needn’t feel that you must slavishly imitate it; the other web root
275
-is for an entirely different domain, also with `www` being optional.
276
-Since all of these domains are served by a single nginx instance, we
277
-need to give all of this in a single command, because we want to mint a
278
-single certificate that authenticates all of these domains.
279
-
280
-The second key option is `--dry-run`, which tells Certbot not to do
281
-anything permanent. We’re just seeing if everything works as expected,
282
-at this point.
283
-
284
-
285
-### Troubleshooting the Dry Run
286
-
287
-If that didn’t work, try creating a manual test:
288
-
289
- $ mkdir -p /var/www/example.com/.well-known/acme-challenge
290
- $ echo hi > /var/www/example.com/.well-known/acme-challenge/test
291
-
292
-Then try to pull that file over HTTP — not HTTPS! — as
293
-`http://example.com/.well-known/acme-challenge/test`. I’ve found that
294
-using Firefox or Safari is better for this sort of thing than Chrome,
295
-because Chrome is more aggressive about automatically forwarding URLs to
296
-HTTPS even if you requested “`http`”.
297
-
298
-In extremis, you can do the test manually:
299
-
300
- $ telnet foo.net 80
301
- GET /.well-known/acme-challenge/test HTTP/1.1
302
- Host: example.com
303
-
304
- HTTP/1.1 200 OK
305
- Server: nginx/1.14.0 (Ubuntu)
306
- Date: Sat, 19 Jan 2019 19:43:58 GMT
307
- Content-Type: application/octet-stream
308
- Content-Length: 3
309
- Last-Modified: Sat, 19 Jan 2019 18:21:54 GMT
310
- Connection: keep-alive
311
- ETag: "5c436ac2-4"
312
- Accept-Ranges: bytes
313
-
314
- hi
315
-
316
-You type the first two lines at the remote system, plus the doubled
317
-“Enter” to create the blank line, and you get something back that
318
-hopefully looks like the rest of the text above.
319
-
320
-The key bits you’re looking for here are the “hi” line at the end — the
321
-document content you created above — and the “200 OK” response code. If
322
-you get a 404 or other error response, you need to look into your web
323
-server logs to find out what’s going wrong.
324
-
325
-Note that it’s important to do this test with HTTP/1.1 when debugging a
326
-name-based virtual hosting configuration like this. Unless you test only
327
-with the primary domain name alias for the server, this test will fail.
328
-Using the example configuration above, you can only use the
329
-easier-to-type HTTP/1.0 protocol to test the `foo.net` alias.
330
-
331
-If you’re still running into trouble, the log file written by Certbot
332
-can be helpful. It tells you where it’s writing it early in each run.
333
-
334
-
335
-
336
-## Step 4: Getting Your First Certificate
337
-
338
-Once the dry run is working, you can drop the `--dry-run` option and
339
-re-run the long command above. (The one with all the `--webroot*`
340
-flags.) This should now succeed, and it will save all of those flag
341
-values to your Let’s Encrypt configuration file, so you don’t need to
342
-keep giving them.
343
-
344
-
345
-
346
-## Step 5: Test It
347
-
348
-Edit the `local/http-certbot-only` file and uncomment the `redirect` and
349
-`return` directives, then restart your nginx server and make sure it now
350
-forces everything to HTTPS like it should:
351
-
352
- $ sudo systemctl restart nginx
353
-
354
-Test ideas:
355
-
356
-* Visit both Fossil and non-Fossil URLs
357
-
358
-* Log into the repo, log out, and log back in
359
-
360
-* Clone via `http`: ensure that it redirects to `https`, and that
361
- subsequent `fossil sync` commands go directly to `https` due to the
362
- 301 permanent redirect.
363
-
364
-This forced redirect is why we don’t need the Fossil Admin &rarr; Access
365
-"Redirect to HTTPS on the Login page" setting to be enabled. Not only
366
-is it unnecessary with this HTTPS redirect at the front-end proxy level,
367
-it would actually [cause an infinite redirect loop if
368
-enabled](./ssl.wiki#rloop).
369
-
370
-
371
-
372
-## Step 6: Re-Point Fossil at Your Repositories
373
-
374
-As of Fossil 2.9, the permanent HTTP-to-HTTPS redirect we enabled above
375
-causes Fossil to remember the new URL automatically the first time it’s
376
-redirected to it. All you need to do to switch your syncs to HTTPS is:
377
-
378
- $ cd ~/path/to/checkout
379
- $ fossil sync
380
-
381
-
382
-## Step 7: Renewing Automatically
383
-
384
-Now that the configuration is solid, you can renew the LE cert with the
385
-`certbot` command from above without the `--dry-run` flag plus a restart
386
-of nginx:
387
-
388
- sudo certbot certonly --webroot \
389
- --webroot-path /var/www/example.com \
390
- -d example.com -d www.example.com \
391
- -d example.net -d www.example.net \
392
- --webroot-path /var/www/foo.net \
393
- -d foo.net -d www.foo.net
394
- sudo systemctl restart nginx
395
-
396
-I put those commands in a script in the `PATH`, then arrange to call that
397
-periodically. Let’s Encrypt doesn’t let you renew the certificate very
398
-often unless forced, and when forced there’s a maximum renewal counter.
399
-Nevertheless, some people recommend running this daily and just letting
400
-it fail until the server lets you renew. Others arrange to run it no
401
-more often than it’s known to work without complaint. Suit yourself.
402
-
403
-
------------
404
-
405
-<a id=”evolution”></a>
406
-**Document Evolution**
407
-
408
-Large parts of this article have been rewritten several times now due to
409
-shifting technology in the TLS and proxying spheres.
410
-
411
-There is no particularly good reason to expect that this sort of thing
412
-will not continue to happen, so we consider this to be a living
413
-document. If you do not have commit access on the `fossil-scm.org`
414
-repository to update this document as the world changes around it, you
415
-can discuss this document [on the forum][fd]. This document’s author
416
-keeps an eye on the forum and expects to keep this document updated with
417
-ideas that appear in that thread.
418
-
419
-[acme]: https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment
420
-[cb]: https://certbot.eff.org/
421
-[cbnu]: https://certbot.eff.org/lets-encrypt/ubuntubionic-nginx
422
-[fd]: https://fossil-scm.org/forum/forumpost/ae6a4ee157
423
-[hsts]: https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security
424
-[lja]: https://en.wikipedia.org/wiki/Logjam_(computer_security)
425
-[mitm]: https://en.wikipedia.org/wiki/Man-in-the-middle_attack
426
-[nest]: https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/
427
-[ocsp]: https://en.wikipedia.org/wiki/OCSP_stapling
428
-[qslc]: https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices
429
-[qslt]: https://www.ssllabs.com/ssltest/
3
+This document has [moved](./server/debian/nginx.md#tls).
4304
--- www/tls-nginx.md
+++ www/tls-nginx.md
@@ -1,430 +1,3 @@
1 # Proxying Fossil via HTTPS with nginx
2
3 One of the [many ways](./ssl.wiki) to provide TLS-encrypted HTTP access
4 (a.k.a. HTTPS) to Fossil is to run it behind a web proxy that supports
5 TLS. This document explains how to use the powerful [nginx web
6 server](http://nginx.org/) to do that.
7
8 This document is an extension of the [Serving via nginx on Debian][nod]
9 document. Please read that first, then come back here to extend its
10 configuration with TLS.
11
12 [nod]: ./server/debian/nginx.md
13
14
15 ## Install Certbot
16
17 The [nginx-on-Debian document][nod] had you install a few non-default
18 packages to the system, but there’s one more you need for this guide:
19
20 $ sudo apt install certbot
21
22 You can extend this guide to other operating systems by following the
23 instructions found via [the front Certbot web page][cb] instead, telling
24 it what OS and web stack you’re using. Chances are good that they’ve got
25 a good guide for you already.
26
27
28 # Configuring Let’s Encrypt, the Easy Way
29
30 If your web serving needs are simple, [Certbot][cb] can configure nginx
31 for you and keep its certificates up to date. Simply follow Certbot’s
32 [nginx on Ubuntu 18.04 LTS guide][cbnu]. We’d recommend one small
33 change: to use the version of Certbot in the Ubuntu package repository
34 rather than download it from the Certbot site.
35
36 You should be able to use the nginx configuration given in our [Serving
37 via nginx on Debian][nod] guide with little to no change. The main thing
38 to watch out for is that the TCP port number in the nginx configuration
39 needs to match the value you gave when starting Fossil. If you followed
40 that guide’s advice, it will be 9000. Another option is to use [the
41 `fslsrv` script](/file/tools/fslsrv), in which case the TCP port number
42 will be 12345 or higher.
43
44
45 # Configuring Let’s Encrypt, the Hard Way
46
47 If you’re finding that you can’t get certificates to be issued or
48 renewed using the Easy Way instructions, the problem is usually that
49 your nginx configuration is too complicated for Certbot’s `--nginx`
50 plugin to understand. It attempts to rewrite your nginx configuration
51 files on the fly to achieve the renewal, and if it doesn’t put its
52 directives in the right locations, the domain verification can fail.
53
54 Let’s Encrypt uses the [Automated Certificate Management
55 Environment][acme] protocol (ACME) to determine whether a given client
56 actually has control over the domain(s) for which it wants a certificate
57 minted. Let’s Encrypt will not blithely let you mint certificates for
58 `google.com` and `paypal.com` just because you ask for it!
59
60 Your author’s configuration, glossed [in the HTTP-only guide][nod],
61 is complicated enough that
62 the current version of Certbot (0.28 at the time of this writing) can’t
63 cope with it. That’s the primary motivation for me to write this guide:
64 I’m addressing the “me” years hence who needs to upgrade to Ubuntu 20.04
65 or 22.04 LTS and has forgotten all of this stuff. 😉
66
67
68 ## Step 1: Shifting into Manual
69
70 The first thing to do is to turn off all of the Certbot automation,
71 because it’ll only get in our way. First, disable the Certbot package’s
72 automatic background updater:
73
74 $ sudo systemctl disable certbot.timer
75
76 Next, edit `/etc/letsencrypt/renewal/example.com.conf` to disable the
77 nginx plugins. You’re looking for two lines setting the “install” and
78 “auth” plugins to “nginx”. You can comment them out or remove them
79 entirely.
80
81
82 ## Step 2: Configuring nginx
83
84 This is a straightforward extension to [the HTTP-only
85 configuration](./server/debian/nginx.md#config):
86
87 server {
88 server_name .foo.net;
89
90 include local/tls-common;
91
92 charset utf-8;
93
94 access_log /var/log/nginx/foo.net-https-access.log;
95 error_log /var/log/nginx/foo.net-https-error.log;
96
97 # Bypass Fossil for the static Doxygen docs
98 location /doc/html {
99 root /var/www/foo.net;
100
101 location ~* \.(html|ico|css|js|gif|jpg|png)$ {
102 expires 7d;
103 add_header Vary Accept-Encoding;
104 access_log off;
105 }
106 }
107
108 # Redirect everything else to the Fossil instance
109 location / {
110 include scgi_params;
111 scgi_pass 127.0.0.1:12345;
112 scgi_param HTTPS "on";
113 scgi_param SCRIPT_NAME "";
114 }
115 }
116 server {
117 server_name .foo.net;
118 root /var/www/foo.net;
119 include local/http-certbot-only;
120 access_log /var/log/nginx/foo.net-http-access.log;
121 error_log /var/log/nginx/foo.net-http-error.log;
122 }
123
124 One big difference between this and the HTTP-only case is
125 that we need two `server { }` blocks: one for HTTPS service, and
126 one for HTTP-only service.
127
128
129 ### HTTP over TLS (HTTPS) Service
130
131 The first `server { }` block includes this file, `local/tls-common`:
132
133 listen 443 ssl;
134
135 ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
136 ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
137
138 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
139
140 ssl_stapling on;
141 ssl_stapling_verify on;
142
143 ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
144 ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256”;
145 ssl_session_cache shared:le_nginx_SSL:1m;
146 ssl_prefer_server_ciphers on;
147 ssl_session_timeout 1440m;
148
149 These are the common TLS configuration parameters used by all domains
150 hosted by this server.
151
152 The first line tells nginx to accept TLS-encrypted HTTP connections on
153 the standard HTTPS port. It is the same as `listen 443; ssl on;` in
154 older versions of nginx.
155
156 Since all of those domains share a single TLS certificate, we reference
157 the same `example.com/*.pem` files written out by Certbot with the
158 `ssl_certificate*` lines.
159
160 The `ssl_dhparam` directive isn’t strictly required, but without it, the
161 server becomes vulnerable to the [Logjam attack][lja] because some of
162 the cryptography steps are precomputed, making the attacker’s job much
163 easier. The parameter file this directive references should be
164 generated automatically by the Let’s Encrypt package upon installation,
165 making those parameters unique to your server and thus unguessable. If
166 the file doesn’t exist on your system, you can create it manually, so:
167
168 $ sudo openssl dhparam -out /etc/letsencrypt/dhparams.pem 2048
169
170 Beware, this can take a long time. On a shared Linux host I tried it on
171 running OpenSSL 1.1.0g, it took about 21 seconds, but on a fast, idle
172 iMac running LibreSSL 2.6.5, it took 8 minutes and 4 seconds!
173
174 The next section is also optional. It enables [OCSP stapling][ocsp], a
175 protocol that improves the speed and security of the TLS connection
176 negotiation.
177
178 The next section containing the `ssl_protocols` and `ssl_ciphers` lines
179 restricts the TLS implementation to only those protocols and ciphers
180 that are currently believed to be safe and secure. This section is the
181 one most prone to bit-rot: as new attacks on TLS and its associated
182 technologies are discovered, this configuration is likely to need to
183 change. Even if we fully succeed in [keeping this document
184 up-to-date](#evolution), the nature of this guide is to recommend static
185 configurations for your server. You will have to keep an eye on this
186 sort of thing and evolve your local configuration as the world changes
187 around it.
188
189 Running a TLS certificate checker against your site occasionally is a
190 good idea. The most thorough service I’m aware of is the [Qualys SSL
191 Labs Test][qslt], which gives the site I’m basing this guide on an “A”
192 rating at the time of this writing. The long `ssl_ciphers` line above is
193 based on [their advice][qslc]: the default nginx configuration tells
194 OpenSSL to use whatever ciphersuites it considers “high security,” but
195 some of those have come to be considered “weak” in the time between that
196 judgement and the time of this writing. By explicitly giving the list of
197 ciphersuites we want OpenSSL to use within nginx, we can remove those
198 that become considered weak in the future.
199
200 <a id=”hsts”></a>There are a few things you can do to get an even better
201 grade, such as to enable [HSTS][hsts], which prevents a particular
202 variety of [man in the middle attack][mitm] where our HTTP-to-HTTPS
203 permanent redirect is intercepted, allowing the attacker to prevent the
204 automatic upgrade of the connection to a secure TLS-encrypted one. I
205 didn’t enable that in the configuration above, because it is something a
206 site administrator should enable only after the configuration is tested
207 and stable, and then only after due consideration. There are ways to
208 lock your users out of your site by jumping to HSTS hastily. When you’re
209 ready, there are [guides you can follow][nest] elsewhere online.
210
211
212 ### HTTP-Only Service
213
214 While we’d prefer not to offer HTTP service at all, we need to do so for
215 two reasons:
216
217 * The temporary reason is that until we get Let’s Encrypt certificates
218 minted and configured properly, we can’t use HTTPS yet at all.
219
220 * The ongoing reason is that the Certbot [ACME][acme] HTTP-01
221 challenge used by the Let’s Encrypt service only runs over HTTP. This is
222 not only because it has to work before HTTPS is first configured,
223 but also because it might need to work after a certificate is
224 accidentally allowed to lapse, to get that server back into a state
225 where it can speak HTTPS safely again.
226
227 So, from the second `service { }` block, we include this file to set up
228 the minimal HTTP service we require, `local/http-certbot-only`:
229
230 listen 80;
231 listen [::]:80;
232
233 # This is expressed as a rewrite rule instead of an "if" because
234 # http://wiki.nginx.org/IfIsEvil
235 #rewrite ^(/.well-known/acme-challenge/.*) $1 break;
236
237 # Force everything else to HTTPS with a permanent redirect.
238 #return 301 https://$host$request_uri;
239
240 As written above, this configuration does nothing other than to tell
241 nginx that it’s allowed to serve content via HTTP on port 80 as well.
242 We’ll uncomment the `rewrite` and `return` directives below, when we’re
243 ready to begin testing.
244
245 Notice that this configuration is very different from that in the
246 [HTTP-only nginx on Debian][nod] guide. Most of that guide’s nginx
247 directives moved up into the TLS `server { }` block, because we
248 eventually want this site to be as close to HTTPS-only as we can get it.
249
250
251 ## Step 3: Dry Run
252
253 We want to first request a dry run, because Let’s Encrypt puts some
254 rather low limits on how often you’re allowed to request an actual
255 certificate. You want to be sure everything’s working before you do
256 that. You’ll run a command something like this:
257
258 $ sudo certbot certonly --webroot --dry-run \
259 --webroot-path /var/www/example.com \
260 -d example.com -d www.example.com \
261 -d example.net -d www.example.net \
262 --webroot-path /var/www/foo.net \
263 -d foo.net -d www.foo.net
264
265 There are two key options here.
266
267 First, we’re telling Certbot to use its `--webroot` plugin instead of
268 the automated `--nginx` plugin. With this plugin, Certbot writes the
269 [ACME][acme] HTTP-01 challenge files to the static web document root
270 directory behind each domain. For this example, we’ve got two web
271 roots, one of which holds documents for two different second-level
272 domains (`example.com` and `example.net`) with `www` at the third level
273 being optional. This is a common sort of configuration these days, but
274 you needn’t feel that you must slavishly imitate it; the other web root
275 is for an entirely different domain, also with `www` being optional.
276 Since all of these domains are served by a single nginx instance, we
277 need to give all of this in a single command, because we want to mint a
278 single certificate that authenticates all of these domains.
279
280 The second key option is `--dry-run`, which tells Certbot not to do
281 anything permanent. We’re just seeing if everything works as expected,
282 at this point.
283
284
285 ### Troubleshooting the Dry Run
286
287 If that didn’t work, try creating a manual test:
288
289 $ mkdir -p /var/www/example.com/.well-known/acme-challenge
290 $ echo hi > /var/www/example.com/.well-known/acme-challenge/test
291
292 Then try to pull that file over HTTP — not HTTPS! — as
293 `http://example.com/.well-known/acme-challenge/test`. I’ve found that
294 using Firefox or Safari is better for this sort of thing than Chrome,
295 because Chrome is more aggressive about automatically forwarding URLs to
296 HTTPS even if you requested “`http`”.
297
298 In extremis, you can do the test manually:
299
300 $ telnet foo.net 80
301 GET /.well-known/acme-challenge/test HTTP/1.1
302 Host: example.com
303
304 HTTP/1.1 200 OK
305 Server: nginx/1.14.0 (Ubuntu)
306 Date: Sat, 19 Jan 2019 19:43:58 GMT
307 Content-Type: application/octet-stream
308 Content-Length: 3
309 Last-Modified: Sat, 19 Jan 2019 18:21:54 GMT
310 Connection: keep-alive
311 ETag: "5c436ac2-4"
312 Accept-Ranges: bytes
313
314 hi
315
316 You type the first two lines at the remote system, plus the doubled
317 “Enter” to create the blank line, and you get something back that
318 hopefully looks like the rest of the text above.
319
320 The key bits you’re looking for here are the “hi” line at the end — the
321 document content you created above — and the “200 OK” response code. If
322 you get a 404 or other error response, you need to look into your web
323 server logs to find out what’s going wrong.
324
325 Note that it’s important to do this test with HTTP/1.1 when debugging a
326 name-based virtual hosting configuration like this. Unless you test only
327 with the primary domain name alias for the server, this test will fail.
328 Using the example configuration above, you can only use the
329 easier-to-type HTTP/1.0 protocol to test the `foo.net` alias.
330
331 If you’re still running into trouble, the log file written by Certbot
332 can be helpful. It tells you where it’s writing it early in each run.
333
334
335
336 ## Step 4: Getting Your First Certificate
337
338 Once the dry run is working, you can drop the `--dry-run` option and
339 re-run the long command above. (The one with all the `--webroot*`
340 flags.) This should now succeed, and it will save all of those flag
341 values to your Let’s Encrypt configuration file, so you don’t need to
342 keep giving them.
343
344
345
346 ## Step 5: Test It
347
348 Edit the `local/http-certbot-only` file and uncomment the `redirect` and
349 `return` directives, then restart your nginx server and make sure it now
350 forces everything to HTTPS like it should:
351
352 $ sudo systemctl restart nginx
353
354 Test ideas:
355
356 * Visit both Fossil and non-Fossil URLs
357
358 * Log into the repo, log out, and log back in
359
360 * Clone via `http`: ensure that it redirects to `https`, and that
361 subsequent `fossil sync` commands go directly to `https` due to the
362 301 permanent redirect.
363
364 This forced redirect is why we don’t need the Fossil Admin &rarr; Access
365 "Redirect to HTTPS on the Login page" setting to be enabled. Not only
366 is it unnecessary with this HTTPS redirect at the front-end proxy level,
367 it would actually [cause an infinite redirect loop if
368 enabled](./ssl.wiki#rloop).
369
370
371
372 ## Step 6: Re-Point Fossil at Your Repositories
373
374 As of Fossil 2.9, the permanent HTTP-to-HTTPS redirect we enabled above
375 causes Fossil to remember the new URL automatically the first time it’s
376 redirected to it. All you need to do to switch your syncs to HTTPS is:
377
378 $ cd ~/path/to/checkout
379 $ fossil sync
380
381
382 ## Step 7: Renewing Automatically
383
384 Now that the configuration is solid, you can renew the LE cert with the
385 `certbot` command from above without the `--dry-run` flag plus a restart
386 of nginx:
387
388 sudo certbot certonly --webroot \
389 --webroot-path /var/www/example.com \
390 -d example.com -d www.example.com \
391 -d example.net -d www.example.net \
392 --webroot-path /var/www/foo.net \
393 -d foo.net -d www.foo.net
394 sudo systemctl restart nginx
395
396 I put those commands in a script in the `PATH`, then arrange to call that
397 periodically. Let’s Encrypt doesn’t let you renew the certificate very
398 often unless forced, and when forced there’s a maximum renewal counter.
399 Nevertheless, some people recommend running this daily and just letting
400 it fail until the server lets you renew. Others arrange to run it no
401 more often than it’s known to work without complaint. Suit yourself.
402
403
------------
404
405 <a id=”evolution”></a>
406 **Document Evolution**
407
408 Large parts of this article have been rewritten several times now due to
409 shifting technology in the TLS and proxying spheres.
410
411 There is no particularly good reason to expect that this sort of thing
412 will not continue to happen, so we consider this to be a living
413 document. If you do not have commit access on the `fossil-scm.org`
414 repository to update this document as the world changes around it, you
415 can discuss this document [on the forum][fd]. This document’s author
416 keeps an eye on the forum and expects to keep this document updated with
417 ideas that appear in that thread.
418
419 [acme]: https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment
420 [cb]: https://certbot.eff.org/
421 [cbnu]: https://certbot.eff.org/lets-encrypt/ubuntubionic-nginx
422 [fd]: https://fossil-scm.org/forum/forumpost/ae6a4ee157
423 [hsts]: https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security
424 [lja]: https://en.wikipedia.org/wiki/Logjam_(computer_security)
425 [mitm]: https://en.wikipedia.org/wiki/Man-in-the-middle_attack
426 [nest]: https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/
427 [ocsp]: https://en.wikipedia.org/wiki/OCSP_stapling
428 [qslc]: https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices
429 [qslt]: https://www.ssllabs.com/ssltest/
430
--- www/tls-nginx.md
+++ www/tls-nginx.md
@@ -1,430 +1,3 @@
1 # Proxying Fossil via HTTPS with nginx
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
------------
3 This document has [moved](./server/debian/nginx.md#tls).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button