[0001]
[0002]
[0003]
[0004]
[0005]
[0006]
[0007]
[0008]
[0009]
[0010]
[0011]
[0012]
[0013]
[0014]
[0015]
[0016]
[0017]
[0018]
[0019]
[0020]
[0021]
[0022]
[0023]
[0024]
[0025]
[0026]
[0027]
[0028]
[0029]
[0030]
[0031]
[0032]
[0033]
[0034]
[0035]
[0036]
[0037]
[0038]
[0039]
[0040]
[0041]
[0042]
[0043]
[0044]
[0045]
[0046]
[0047]
[0048]
[0049]
[0050]
[0051]
[0052]
[0053]
[0054]
[0055]
[0056]
[0057]
[0058]
[0059]
[0060]
[0061]
[0062]
[0063]
[0064]
[0065]
[0066]
[0067]
[0068]
[0069]
[0070]
[0071]
[0072]
[0073]
[0074]
[0075]
[0076]
[0077]
[0078]
[0079]
[0080]
[0081]
[0082]
[0083]
[0084]
[0085]
[0086]
[0087]
[0088]
[0089]
[0090]
[0091]
[0092]
[0093]
[0094]
[0095]
[0096]
[0097]
[0098]
[0099]
[0100]
[0101]
[0102]
[0103]
[0104]
[0105]
[0106]
[0107]
[0108]
[0109]
[0110]
[0111]
[0112]
[0113]
[0114]
[0115]
[0116]
[0117]
[0118]
[0119]
[0120]
[0121]
[0122]
[0123]
[0124]
[0125]
[0126]
[0127]
[0128]
[0129]
[0130]
[0131]
[0132]
[0133]
[0134]
[0135]
[0136]
[0137]
[0138]
[0139]
[0140]
[0141]
[0142]
[0143]
[0144]
[0145]
[0146]
[0147]
[0148]
[0149]
[0150]
[0151]
[0152]
[0153]
[0154]
[0155]
[0156]
[0157]
[0158]
[0159]
[0160]
[0161]
[0162]
[0163]
[0164]
[0165]
[0166]
[0167]
[0168]
[0169]
[0170]
[0171]
[0172]
[0173]
[0174]
[0175]
[0176]
[0177]
[0178]
[0179]
[0180]
[0181]
[0182]
[0183]
[0184]
[0185]
[0186]
[0187]
[0188]
[0189]
[0190]
[0191]
[0192]
[0193]
[0194]
[0195]
[0196]
[0197]
[0198]
[0199]
[0200]
[0201]
[0202]
[0203]
[0204]
[0205]
[0206]
[0207]
[0208]
[0209]
[0210]
[0211]
[0212]
[0213]
[0214]
[0215]
[0216]
[0217]
[0218]
[0219]
[0220]
[0221]
[0222]
[0223]
[0224]
[0225]
[0226]
[0227]
[0228]
[0229]
[0230]
[0231]
[0232]
[0233]
[0234]
[0235]
[0236]
[0237]
[0238]
[0239]
[0240]
[0241]
[0242]
[0243]
[0244]
[0245]
[0246]
[0247]
[0248]
[0249]
[0250]
[0251]
[0252]
[0253]
[0254]
[0255]
[0256]
[0257]
[0258]
[0259]
[0260]
[0261]
[0262]
[0263]
[0264]
[0265]
[0266]
[0267]
[0268]
[0269]
[0270]
[0271]
[0272]
[0273]
[0274]
[0275]
[0276]
[0277]
[0278]
[0279]
[0280]
[0281]
[0282]
[0283]
[0284]
[0285]
[0286]
[0287]
[0288]
[0289]
[0290]
[0291]
[0292]
[0293]
[0294]
[0295]
[0296]
[0297]
[0298]
[0299]
[0300]
[0301]
[0302]
[0303]
[0304]
[0305]
[0306]
[0307]
[0308]
[0309]
[0310]
[0311]
[0312]
[0313]
[0314]
[0315]
[0316]
[0317]
[0318]
[0319]
[0320]
[0321]
[0322]
[0323]
[0324]
[0325]
[0326]
[0327]
[0328]
[0329]
[0330]
[0331]
[0332]
[0333]
[0334]
[0335]
[0336]
[0337]
[0338]
[0339]
[0340]
[0341]
[0342]
[0343]
[0344]
[0345]
[0346]
[0347]
[0348]
[0349]
[0350]
[0351]
[0352]
[0353]
[0354]
[0355]
[0356]
[0357]
[0358]
[0359]
[0360]
[0361]
[0362]
[0363]
[0364]
[0365]
[0366]
[0367]
[0368]
[0369]
[0370]
[0371]
[0372]
[0373]
[0374]
[0375]
[0376]
[0377]
[0378]
[0379]
[0380]
[0381]
[0382]
[0383]
[0384]
[0385]
[0386]
[0387]
[0388]
[0389]
[0390]
[0391]
[0392]
[0393]
[0394]
[0395]
[0396]
[0397]
[0398]
[0399]
[0400]
[0401]
[0402]
[0403]
[0404]
[0405]
[0406]
[0407]
[0408]
[0409]
[0410]
[0411]
[0412]
[0413]
[0414]
[0415]
[0416]
[0417]
[0418]
[0419]
[0420]
[0421]
[0422]
[0423]
[0424]
[0425]
[0426]
[0427]
[0428]
[0429]
[0430]
[0431]
[0432]
[0433]
[0434]
[0435]
[0436]
[0437]
[0438]
[0439]
[0440]
[0441]
[0442]
[0443]
[0444]
[0445]
[0446]
[0447]
[0448]
[0449]
[0450]
[0451]
[0452]
[0453]
[0454]
[0455]
[0456]
[0457]
[0458]
[0459]
[0460]
[0461]
[0462]
[0463]
[0464]
[0465]
[0466]
[0467]
[0468]
[0469]
[0470]
[0471]
[0472]
[0473]
[0474]
[0475]
[0476]
[0477]
[0478]
[0479]
[0480]
[0481]
[0482]
[0483]
[0484]
[0485]
[0486]
[0487]
[0488]
[0489]
[0490]
[0491]
[0492]
[0493]
[0494]
[0495]
[0496]
[0497]
[0498]
[0499]
[0500]
[0501]
[0502]
[0503]
[0504]
[0505]
[0506]
[0507]
[0508]
[0509]
[0510]
[0511]
[0512]
[0513]
[0514]
[0515]
[0516]
[0517]
[0518]
[0519]
[0520]
[0521]
[0522]
[0523]
[0524]
[0525]
[0526]
[0527]
[0528]
[0529]
[0530]
[0531]
[0532]
[0533]
[0534]
[0535]
[0536]
[0537]
[0538]
[0539]
[0540]
[0541]
[0542]
[0543]
[0544]
[0545]
[0546]
[0547]
[0548]
[0549]
[0550]
[0551]
[0552]
[0553]
[0554]
[0555]
[0556]
[0557]
[0558]
[0559]
[0560]
[0561]
[0562]
[0563]
[0564]
[0565]
[0566]
[0567]
[0568]
[0569]
[0570]
[0571]
[0572]
[0573]
[0574]
[0575]
[0576]
[0577]
[0578]
[0579]
[0580]
[0581]
[0582]
[0583]
[0584]
[0585]
[0586]
[0587]
[0588]
[0589]
[0590]
[0591]
[0592]
[0593]
[0594]
[0595]
[0596]
[0597]
[0598]
[0599]
[0600]
[0601]
[0602]
[0603]
[0604]
[0605]
[0606]
[0607]
[0608]
[0609]
[0610]
[0611]
[0612]
[0613]
[0614]
[0615]
[0616]
[0617]
[0618]
[0619]
[0620]
[0621]
[0622]
[0623]
[0624]
[0625]
[0626]
[0627]
[0628]
[0629]
[0630]
[0631]
[0632]
[0633]
[0634]
[0635]
[0636]
[0637]
[0638]
[0639]
[0640]
[0641]
[0642]
[0643]
[0644]
[0645]
[0646]
[0647]
[0648]
[0649]
[0650]
[0651]
[0652]
[0653]
[0654]
[0655]
[0656]
[0657]
[0658]
[0659]
[0660]
[0661]
[0662]
[0663]
[0664]
[0665]
[0666]
[0667]
[0668]
[0669]
[0670]
[0671]
[0672]
[0673]
[0674]
[0675]
[0676]
[0677]
[0678]
[0679]
[0680]
[0681]
[0682]
[0683]
[0684]
[0685]
[0686]
[0687]
[0688]
[0689]
[0690]
[0691]
[0692]
[0693]
[0694]
[0695]
[0696]
[0697]
[0698]
[0699]
[0700]
[0701]
[0702]
[0703]
[0704]
[0705]
[0706]
[0707]
[0708]
[0709]
[0710]
[0711]
[0712]
[0713]
[0714]
[0715]
[0716]
[0717]
[0718]
[0719]
[0720]
[0721]
[0722]
[0723]
[0724]
[0725]
[0726]
[0727]
[0728]
[0729]
[0730]
[0731]
[0732]
[0733]
[0734]
[0735]
[0736]
[0737]
[0738]
[0739]
[0740]
[0741]
[0742]
[0743]
[0744]
[0745]
[0746]
[0747]
[0748]
[0749]
[0750]
[0751]
[0752]
[0753]
[0754]
[0755]
[0756]
[0757]
[0758]
[0759]
[0760]
[0761]
[0762]
[0763]
[0764]
[0765]
[0766]
[0767]
[0768]
[0769]
[0770]
[0771]
[0772]
[0773]
[0774]
[0775]
[0776]
[0777]
[0778]
[0779]
[0780]
[0781]
[0782]
[0783]
[0784]
[0785]
[0786]
[0787]
[0788]
[0789]
[0790]
[0791]
[0792]
[0793]
[0794]
[0795]
[0796]
[0797]
[0798]
[0799]
[0800]
[0801]
[0802]
[0803]
[0804]
[0805]
[0806]
[0807]
[0808]
[0809]
[0810]
[0811]
[0812]
[0813]
[0814]
[0815]
[0816]
[0817]
[0818]
[0819]
[0820]
[0821]
[0822]
[0823]
[0824]
[0825]
[0826]
[0827]
[0828]
[0829]
[0830]
[0831]
[0832]
[0833]
[0834]
[0835]
[0836]
[0837]
[0838]
[0839]
[0840]
[0841]
[0842]
[0843]
[0844]
[0845]
[0846]
[0847]
[0848]
[0849]
[0850]
[0851]
[0852]
[0853]
[0854]
[0855]
[0856]
[0857]
[0858]
[0859]
[0860]
[0861]
[0862]
[0863]
[0864]
[0865]
[0866]
[0867]
[0868]
[0869]
[0870]
[0871]
[0872]
[0873]
[0874]
[0875]
[0876]
[0877]
[0878]
[0879]
[0880]
[0881]
[0882]
[0883]
[0884]
[0885]
[0886]
[0887]
[0888]
[0889]
[0890]
[0891]
[0892]
[0893]
[0894]
[0895]
[0896]
[0897]
[0898]
[0899]
[0900]
[0901]
[0902]
[0903]
[0904]
[0905]
[0906]
[0907]
[0908]
[0909]
[0910]
[0911]
[0912]
[0913]
[0914]
[0915]
[0916]
[0917]
[0918]
[0919]
[0920]
[0921]
[0922]
[0923]
[0924]
[0925]
[0926]
[0927]
[0928]
[0929]
[0930]
[0931]
[0932]
[0933]
[0934]
[0935]
[0936]
[0937]
[0938]
[0939]
[0940]
[0941]
[0942]
[0943]
[0944]
[0945]
[0946]
[0947]
[0948]
[0949]
[0950]
[0951]
[0952]
[0953]
[0954]
[0955]
[0956]
[0957]
[0958]
[0959]
[0960]
[0961]
[0962]
[0963]
[0964]
[0965]
[0966]
[0967]
[0968]
[0969]
[0970]
[0971]
[0972]
[0973]
[0974]
[0975]
[0976]
[0977]
[0978]
[0979]
[0980]
[0981]
[0982]
[0983]
[0984]
[0985]
[0986]
[0987]
[0988]
[0989]
[0990]
[0991]
[0992]
[0993]
[0994]
[0995]
[0996]
[0997]
[0998]
[0999]
[1000]
[1001]
[1002]
[1003]
[1004]
[1005]
[1006]
[1007]
[1008]
[1009]
[1010]
[1011]
[1012]
[1013]
[1014]
[1015]
[1016]
[1017]
[1018]
[1019]
[1020]
[1021]
[1022]
[1023]
[1024]
[1025]
[1026]
[1027]
[1028]
[1029]
[1030]
[1031]
[1032]
[1033]
[1034]
[1035]
[1036]
[1037]
[1038]
[1039]
[1040]
[1041]
[1042]
[1043]
[1044]
[1045]
[1046]
[1047]
[1048]
[1049]
[1050]
[1051]
[1052]
[1053]
[1054]
[1055]
[1056]
[1057]
[1058]
[1059]
[1060]
[1061]
[1062]
[1063]
[1064]
[1065]
[1066]
[1067]
[1068]
[1069]
[1070]
[1071]
[1072]
[1073]
[1074]
[1075]
[1076]
[1077]
[1078]
[1079]
[1080]
[1081]
[1082]
[1083]
[1084]
[1085]
[1086]
[1087]
[1088]
[1089]
[1090]
[1091]
[1092]
[1093]
[1094]
[1095]
[1096]
[1097]
[1098]
[1099]
[1100]
[1101]
[1102]
[1103]
[1104]
[1105]
[1106]
[1107]
[1108]
[1109]
[1110]
[1111]
[1112]
[1113]
[1114]
[1115]
[1116]
[1117]
[1118]
[1119]
[1120]
[1121]
[1122]
[1123]
[1124]
[1125]
[1126]
[1127]
[1128]
[1129]
[1130]
[1131]
[1132]
[1133]
[1134]
[1135]
[1136]
[1137]
[1138]
[1139]
[1140]
[1141]
[1142]
[1143]
[1144]
[1145]
[1146]
[1147]
[1148]
[1149]
[1150]
[1151]
[1152]
[1153]
[1154]
[1155]
[1156]
[1157]
[1158]
[1159]
[1160]
[1161]
[1162]
[1163]
[1164]
[1165]
[1166]
[1167]
[1168]
[1169]
[1170]
[1171]
[1172]
[1173]
[1174]
[1175]
[1176]
[1177]
[1178]
[1179]
[1180]
[1181]
[1182]
[1183]
[1184]
[1185]
[1186]
[1187]
[1188]
[1189]
[1190]
[1191]
[1192]
[1193]
[1194]
[1195]
[1196]
[1197]
[1198]
[1199]
[1200]
[1201]
[1202]
[1203]
[1204]
[1205]
[1206]
[1207]
[1208]
[1209]
[1210]
[1211]
[1212]
[1213]
[1214]
[1215]
[1216]
[1217]
[1218]
[1219]
[1220]
[1221]
[1222]
[1223]
[1224]
[1225]
[1226]
[1227]
[1228]
[1229]
[1230]
[1231]
[1232]
[1233]
[1234]
[1235]
[1236]
[1237]
[1238]
[1239]
[1240]
[1241]
[1242]
[1243]
[1244]
[1245]
[1246]
[1247]
[1248]
[1249]
[1250]
[1251]
[1252]
[1253]
[1254]
[1255]
[1256]
[1257]
[1258]
[1259]
[1260]
[1261]
[1262]
[1263]
[1264]
[1265]
[1266]
[1267]
[1268]
[1269]
[1270]
[1271]
[1272]
[1273]
[1274]
[1275]
[1276]
[1277]
[1278]
[1279]
[1280]
[1281]
[1282]
[1283]
[1284]
[1285]
[1286]
[1287]
[1288]
[1289]
[1290]
[1291]
[1292]
[1293]
[1294]
[1295]
[1296]
[1297]
[1298]
[1299]
[1300]
[1301]
[1302]
[1303]
[1304]
[1305]
[1306]
[1307]
[1308]
[1309]
[1310]
[1311]
[1312]
[1313]
[1314]
[1315]
[1316]
[1317]
[1318]
[1319]
[1320]
[1321]
[1322]
[1323]
[1324]
[1325]
[1326]
[1327]
[1328]
[1329]
[1330]
[1331]
[1332]
[1333]
[1334]
[1335]
[1336]
[1337]
[1338]
[1339]
[1340]
[1341]
[1342]
[1343]
[1344]
[1345]
[1346]
[1347]
[1348]
[1349]
[1350]
[1351]
[1352]
[1353]
[1354]
[1355]
[1356]
[1357]
[1358]
[1359]
[1360]
[1361]
[1362]
[1363]
[1364]
[1365]
[1366]
[1367]
[1368]
[1369]
[1370]
[1371]
[1372]
[1373]
[1374]
[1375]
[1376]
[1377]
[1378]
[1379]
[1380]
[1381]
[1382]
[1383]
[1384]
[1385]
[1386]
[1387]
[1388]
[1389]
[1390]
[1391]
[1392]
[1393]
[1394]
[1395]
[1396]
[1397]
[1398]
[1399]
[1400]
[1401]
[1402]
[1403]
[1404]
[1405]
[1406]
[1407]
[1408]
[1409]
[1410]
[1411]
[1412]
[1413]
[1414]
[1415]
[1416]
[1417]
[1418]
[1419]
[1420]
[1421]
[1422]
[1423]
[1424]
[1425]
[1426]
[1427]
[1428]
[1429]
[1430]
[1431]
[1432]
[1433]
[1434]
[1435]
[1436]
[1437]
[1438]
[1439]
[1440]
[1441]
[1442]
[1443]
[1444]
[1445]
[1446]
[1447]
[1448]
[1449]
[1450]
[1451]
[1452]
[1453]
[1454]
[1455]
[1456]
[1457]
[1458]
[1459]
[1460]
[1461]
[1462]
[1463]
[1464]
[1465]
[1466]
[1467]
[1468]
[1469]
[1470]
[1471]
[1472]
[1473]
[1474]
[1475]
[1476]
[1477]
[1478]
[1479]
[1480]
[1481]
[1482]
[1483]
[1484]
[1485]
[1486]
[1487]
[1488]
[1489]
[1490]
[1491]
[1492]
[1493]
[1494]
[1495]
[1496]
[1497]
[1498]
[1499]
[1500]
[1501]
[1502]
[1503]
[1504]
[1505]
[1506]
[1507]
[1508]
[1509]
[1510]
[1511]
[1512]
[1513]
[1514]
[1515]
[1516]
[1517]
[1518]
[1519]
[1520]
[1521]
[1522]
[1523]
[1524]
[1525]
[1526]
[1527]
[1528]
[1529]
[1530]
[1531]
[1532]
[1533]
[1534]
[1535]
[1536]
[1537]
[1538]
[1539]
[1540]
[1541]
[1542]
[1543]
[1544]
[1545]
[1546]
[1547]
[1548]
[1549]
[1550]
[1551]
[1552]
[1553]
[1554]
[1555]
[1556]
[1557]
[1558]
[1559]
[1560]
[1561]
[1562]
[1563]
[1564]
[1565]
[1566]
[1567]
[1568]
[1569]
[1570]
[1571]
[1572]
[1573]
[1574]
[1575]
[1576]
[1577]
[1578]
[1579]
[1580]
[1581]
[1582]
[1583]
[1584]
[1585]
[1586]
[1587]
[1588]
[1589]
[1590]
[1591]
[1592]
[1593]
[1594]
[1595]
[1596]
[1597]
[1598]
[1599]
[1600]
[1601]
[1602]
[1603]
[1604]
[1605]
[1606]
[1607]
[1608]
[1609]
[1610]
[1611]
[1612]
[1613]
[1614]
[1615]
[1616]
[1617]
[1618]
[1619]
[1620]
[1621]
[1622]
[1623]
[1624]
[1625]
[1626]
[1627]
[1628]
[1629]
[1630]
[1631]
[1632]
[1633]
[1634]
[1635]
[1636]
[1637]
[1638]
[1639]
[1640]
[1641]
[1642]
[1643]
[1644]
[1645]
[1646]
[1647]
[1648]
[1649]
[1650]
[1651]
[1652]
[1653]
[1654]
[1655]
[1656]
[1657]
[1658]
[1659]
[1660]
[1661]
[1662]
[1663]
[1664]
[1665]
[1666]
[1667]
[1668]
[1669]
[1670]
[1671]
[1672]
[1673]
[1674]
[1675]
[1676]
[1677]
[1678]
[1679]
[1680]
[1681]
[1682]
[1683]
[1684]
[1685]
[1686]
[1687]
[1688]
[1689]
[1690]
[1691]
[1692]
[1693]
[1694]
[1695]
[1696]
[1697]
[1698]
[1699]
[1700]
[1701]
[1702]
[1703]
[1704]
[1705]
[1706]
[1707]
[1708]
[1709]
[1710]
[1711]
[1712]
[1713]
[1714]
[1715]
[1716]
[1717]
[1718]
[1719]
[1720]
[1721]
[1722]
[1723]
[1724]
[1725]
[1726]
[1727]
[1728]
[1729]
[1730]
[1731]
[1732]
[1733]
[1734]
[1735]
[1736]
[1737]
[1738]
[1739]
[1740]
[1741]
[1742]
[1743]
[1744]
[1745]
[1746]
[1747]
[1748]
[1749]
[1750]
/*
 * This program is run by the WWWEXEC scriptserver to do pre-processing of
 * html files, dynamically inserting files or other generated data.
 *
 * To specify the preprocessing you must give the file a distinct file type
 * (e.g. htmlx) and add the following to the configuration file:
 *
 *	suffix .htmlx text/x-server-parsed-html
 *	presentation text/x-server-parsed-html html_preproc
 *
 * WWWEXEC will then execute the following command line:
 *
 *	html_preproc method url protocol
 *
 *	argv[1]		Method specified in request (e.g. GET).
 *	argv[2]		Ident portion of requested URL, after translation by
 *			rule file.  See special parsing note below.
 *	argv[3]		Protocol specified in request, "" or "HTTP/1.0".
 *
 * None of the resulting file contents is returned until the entire file
 * has been processed.  This is required for 2 reasons:
 *
 *   1. The HTTP status, which is the first part of the response, is not known
 *       until processing is complete.
 *
 *   2.  Processing of the file may require using additional server functions,
 *       such as <DNETXLATE>, which become unavailable once the HTTP response
 *       <DNETRAW> is started.
 *
 * Argv[2] parsing:
 *    If the path in argv[2] is of the form /dir/filename.partname.type,
 *    then partname is extracted from /dir/filename.type.
 *
 * Author:	David Jones
 * Date:	25-AUG-1994
 * Revised:	3-SEP-1994	Re-coded parsing.
 * Revised:	6-OCT-1994	Support fsize/flastmod.
 * Revised:	11-OCT_1994	Adjust length in translate.
 * Revised:	20-DEC-1994	Fix length in do_include
 * Revised:	22-APR-1995	Added extra echo variables suggested by
 *			        Kent Covert (kacovert@miavx1.acs.muohio.edu):
 *				    DOCUMENT_NAME, LAST_MODIFIED, 
 *				    ACCESSES/ACCESSES_ORDINAL
 * Revised:	23-APR-1995	Add permissions file checks to ACCESSES var.
 * Revised:	25-APR-1995	Overhaul:
 *				  - Add strftime() formatting options to
 *				    LAST_MODIFIED, DATE_LOCAL, ACCESSES*
 *				  - Add version numbering to ACCESSES.
 *				  - Rename indexio.h to access_db.h
 * Revised:	15-MAY-1995	Use <DNETXLATEV> for virtual includes
 *				(does protection check).
 * Revised:	21-JUN-1995	Remove '\r's from net_link_printf formats as
 *				net_link_printf will add them.
 * Revised:	8-AUG-1995	Use CGI mode rather than RAW mode (stsline 
 *				can be sent using status: CGI header)
 * Revised:	31-AUG-1995	Fixup formatting of ACCESSES values for
 *				VAXC compatibility.
 * Revised:	13-NOV-1995	Add tag_verify config option.
 * Revised:	12-MAY-1996	Convert for MST support, (symbol IS_MST defined
 *				when compiled as part of MST image).
 * Revised:	18-MAY-1996	Add part include idea of Richard Levitte.
 * Revised:	19-MAY-1996	Changed to understand multiple instances of
 *				a part.
 * Revised:	27-MAY-1996	Changed to accept multiple part names in
 *				#begin and #end directives.  Separate names
 *				with whitespace, e.g.:
 *				   <!--#begin small big -->
 *				(allows you to condense comments around
 *				command parts such as header tags).
 * Revised:	28-MAY-1996	Bug fix, supply missing argument in open error
 *				messages (MST's only).
 * Revised:	9-SEP-1996	Change member names to avoid recursive
 *				macro substituition on MST variant.
 * Revised:	25-OCT-1996	Place limit on size of file to process,
 *				100,000 for script, 60000 for MST.
 * Revised:	25-DEC-1996	Split main routine into 2 parts to enable
 *				pre-processor to be called from other
 *				scripts (must define CALLABLE_PREPROC).
 * Revised:	3-FEB-1997	Fix bug handling directives that return zero
 *				length segment.
 * Revised:	26-AUG-1997	Add last-modified header if not using counter.
 * Revised:	8-DEC-1997	Add limited support for nested includes.
 *				(lastmod date is always for primary file).
 * Revised:	12-DEC-1997	Consolidate file loads into single routine.
 * Revised:	15-DEC-1997	Save to cache in 4000 byte chunks vs. 16000
 * Revised:	28-JAN-1998	Modify parse_part_name to ignore null part
 *				names in the filename, allowing specification
 *				of files with multiple dots:
 *				    foo.htmlx		file foo.htmlx
 *				    foo.bar.htmlx	part bar in foo.htmlx
 *				    foo.bar.x.htmlx	part x in foo.bar.htmlx
 *				    foo..htmlx		file foo.htmlx
 *				    foo.bar..html	file foo.bar.htmlx
 */

#ifndef IS_MST
/*
 * Include files only needed when not an MST
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "scriptlib.h"
#include "access_db.h"
#define LOCK_C_RTL
#define UNLOCK_C_RTL
#define INPUT_LIMIT 100000
#else
#define INPUT_LIMIT 60000
#endif
#include <time.h>
#include <syidef.h>
#include <descrip.h>
#include <stat.h>
int LIB$GETSYI();

#if defined(DEBUG) && defined(IS_MST)
#error "The combination of DEBUG and IS_MST is not allowed."
#endif

struct segment { int length; char *addr; };
typedef struct segment *segptr;

static int send_http_header ( char *stsline, char *content );
static int parse_directive 
	( char *path, char **targ, int *outlen, char **outbuf );
static int parse_tag ( char *tag, int maxlen, int *taglen, char **targ );
static int parse_part_name ( char *fname, char **pfile, char **pname );
static int extract_part ( char *, char *, int *, char ** );
static segptr alloc_segment ( segptr seg, int sc, int *seg_size, char *start );

static int tag_verify, abort_status;
static char *access_file_fdl = "\
  FILE; ORGANIZATION indexed; PROTECTION (system:RWED,owner:RWED,group,world);\
  RECORD; CARRIAGE_CONTROL carriage_return; FORMAT fixed; SIZE 120;\
  KEY 0; CHANGES no; PROLOG 3; SEG0_LENGTH 100; SEG0_POSITION 0; TYPE string;\
";
static int preprocess_html_source ( char *method, char *ident,
	char *source, int source_length );
static int load_file ( char *fname, char * lname, int *outlen, char **outbuf, int initial );
/****************************************************************************/
/* Main program/routine
 */
#ifndef CALLABLE_PREPROC
#ifdef IS_MST
static int preproc_main ( int argc, char **argv )
#else
int main ( int argc, char **argv )
#endif
{
    int status, i, j, k, length, s_size, used, sc, sg_size;
    char *source, *part_file, *part_name;
    char hdrline[64];
#ifdef IS_MST
    struct private_ctx *ctx;
    void *hfile, *link;
    char errmsg[256];
    /*
     * Get pointer to thread's private data (particularly link).
     */
    GET_SPECIFIC ( private_context, ctx )
    if ( http_log_level > 6 ) tlog_putlog ( 7,
	"Context address: !XL, args: !AZ '!AZ' !AZ!/", ctx, 
	argv[1], argv[2], argv[3] );
#else
    FILE *hfile;
    /*
     * Make connection back to server and set protocol version field for
     * the response to server.
     */
    status = net_link_open();
    if ( 0 == (status&1) ) exit ( status );
#endif
    /*
     * Validate method (argv[1]).  We only understand GET and HEAD.
     */
    if ( argc < 4 ) exit ( 20 );
    if ( strncmp("GET",argv[1],4) && strncmp("HEAD",argv[1],5) ) {
	send_http_header ( "501 unsupported method", "text/plain" );
	net_link_printf ("Unsupported method (%s)\n", argv[1] );
	return 1;
    }
    if ( parse_part_name ( argv[2], &part_file, &part_name ) ) {
	/*
	 * argv[2] was of form /path/filename.partname.type.
	 */
	status = extract_part ( part_file, part_name, &used, &source );
	if ( status < 0 ) return status;
	argv[2] = part_file;
    } else {
        /*
         * Open file specified by argv[2] (fixed up and returned in part_file).
         */
#ifdef IS_MST
	ctx->last_mod = 0;
#endif
	status = load_file ( part_file, part_file, &used, &source, 1 );
        if ( status < 0 ) return status;
    }
    /*
     * Parse source.
     */
    status = preprocess_html_source ( argv[1], argv[2], source, used );
    return status;
}
#else
/*
 *  Set scriptlib mode to buffer all output.  Call instead of cgi_begin_output
 *  (called after cgi_init_env).   Do not next CGI response header
 *  (content-type) as this is assumed to be text/html.
 */
#include "scriptlib.h"
static int old_nl_mode;
static char *pp_method, *pp_ident;
int cgi_begin_preprocessed ( char *method, char *ident )
{
    int status, old_mode;
    pp_method = malloc ( strlen(method)+1 );
    strcpy ( pp_method, method );
    pp_ident = malloc ( strlen(ident)+1 );
    if ( !pp_ident ) return 0;
    strcpy ( pp_ident, ident );
    old_nl_mode = net_link_set_mode ( 2 );		/* text mode, save ouput */
    if ( (old_nl_mode&1) == 1 ) net_link_set_mode ( 3 );
    return 1;
}
int cgi_end_preprocessed()
{
    char *buffer;
    int status, length;
    /*
     * reset mode and get address of buffer holding saved data.
     */
    net_link_set_mode ( old_nl_mode );
    status = net_link_saved_output ( &buffer, &length );
    if ( status&1 ) status = preprocess_html_source ( pp_method,
	pp_ident, buffer, length );
    free ( pp_ident );
    free ( pp_method );
    return status;
}
#endif   /* CALLABLE_PREPROC */
/****************************************************************************/
/* Recursive routine to scan source text for tags to pre-process.
 * Return value is number of segments.
 */
static int build_segment ( char *ident,	char *source, int source_length,
	segptr *seglist, int *seglist_size, int seglist_start )
{
    int status, is_directive, i, j, k, length, s_size, sc, sg_size;
    segptr seg;
    char *targ[11], hdrline[128];
#ifdef IS_MST
    struct private_ctx *ctx;
    int reslen, tag_verify;
    /*
     * Get pointer to thread's private data and initialize local tag_verify.
     */
    GET_SPECIFIC ( private_context, ctx )
    tag_verify = ctx->tag_verify;
#endif
    
    /*
     * Parse file, making list of individual blocks of text to send
     * back to server.
     */
    seg = *seglist;
    sg_size = *seglist_size;		/* make local copies of parameters */
    sc = seglist_start;			/* first element to load */
    seg[sc].length = 0;
    seg[sc].addr = source;
    status = 1;
    for ( i = 0; i < source_length; i++ ) {
	char cur;
	cur = source[i];
	if ( cur == '<' ) {
	    /*
	     * At beggining of HTML tag, go to state machine to parse.
	     */
	    is_directive = parse_tag ( &source[i], source_length-i, &j, targ );
#ifdef DEBUG
printf("Tag at %d (seg[%d].addr[%d]), length: %d, directive: %d verify: %d\n", i, 
sc, seg[sc].length, j, is_directive, tag_verify );
#endif
	    if ( j > 0 && (!is_directive || tag_verify) ) {
		/*
		 * Include parsed tag in output, as a comment the client
		 * will ignore it.
		 */
		seg[sc].length += j;
		while ( seg[sc].length >= 1024 ) {
		    /* Make current segment 1024 and put rest in new segment */
		    seg = alloc_segment ( seg, sc, &sg_size, 
				&seg[sc].addr[1024] );
		    seg[sc+1].length = seg[sc].length - 1024;
		    seg[sc++].length = 1024;
		}
	    }
	    i += j - 1;

	    if ( is_directive ) {
		/*
		 * Terminate current segment and make fresh one to hold
		 * included info.  Init start address of new segment to
		 * point after tag.
		 */
		if ( seg[sc].length > 0 ) {
		    seg = alloc_segment ( seg, sc++, &sg_size, &source[i] );
		} else seg[sc].addr = &source[i];
	 	j = 0;	/* remove already added. */
		status = parse_directive 
			( ident, targ, &seg[sc].length, &seg[sc].addr );
		if ( status < 0 ) return status;
#ifdef IS_MST
		tag_verify = ctx->tag_verify;
#endif
		if ( status > 1 ) {		/* recusive scan */
#ifdef IS_MST
		    ctx->recur_level++;
		    if ( ctx->recur_level < 5 ) sc = build_segment ( ident, 
			seg[sc].addr, seg[sc].length, &seg, &sg_size, sc );
		    --ctx->recur_level;
#else
		    sc = build_segment ( ident, seg[sc].addr,
			seg[sc].length, &seg, &sg_size, sc );
#endif
		    if ( sc < 0 ) return sc;
		}
		while ( seg[sc].length > 1024 ) {
		    /* Make current segment 1024 and put rest in new segment */
		    seg = alloc_segment ( seg, sc, &sg_size, 
				&seg[sc].addr[1024] );
		    seg[sc+1].length = seg[sc].length - 1024;
		    seg[sc++].length = 1024;
		}
		if ( seg[sc].length > 0 ) {
		    seg = alloc_segment ( seg, sc++, &sg_size, &source[i+1] );
		} else {
		    /* 
		     * result of directive was zero length, update start 
		     * point
		     */
		    seg[sc].addr = &source[i+1];
		}
	    }
	} else {
	    /*
	     * Extend current segment to include this character.
	     */
	    seg[sc].length++;
	    if ( seg[sc].length >= 1024 ) {
		/* Segment at write limit, advance to next */
		seg = alloc_segment ( seg, sc++, &sg_size, &source[i+1] );
	    }
	}
    }
    /*
     * Check for error status.
     */
    if ( status < 0 ) return status;
    *seglist = seg;
    *seglist_size = sg_size;
    return sc;
}
/****************************************************************************/
/* Main engine for interpreting the HTML source for pre-processor directives.
 * When finished, network link is placed in CGI mode and result is dumped
 * to net_link.
 */
static int preprocess_html_source ( char *method, char *ident,
	char *source, int source_length )
{
    int status, is_directive, i, j, k, length, s_size, sc, sg_size;
    segptr seg;
    char *targ[11], hdrline[128];
#ifdef IS_MST
    struct private_ctx *ctx;
    int reslen, tag_verify;
    /*
     * Get pointer to thread's private data and initialize local tag_verify.
     */
    GET_SPECIFIC ( private_context, ctx )
    tag_verify = ctx->tag_verify;
#else
    /*
     * Initialize global tag_verify.
     */
    tag_verify = 0;
#endif
    /*
     * Parse file, making list of individual blocks of text to send
     * back to server.
     */
    sg_size = 1000;
    seg = (struct segment *) malloc ( sg_size * sizeof(struct segment) );
    sc = build_segment ( ident, source, source_length, &seg, &sg_size, 0 );
    if ( sc < 0 ) return -1;
    /*
     * Flush accumulated segments, compute total length and include in header.
     */
    for (k = i = 0; i <= sc; i++) if (seg[i].length > 0) k += seg[i].length;
#ifdef IS_MST	       /* 123456789012345678901234567 89012345678901234 */
    tu_strcpy ( hdrline, "200 Sending Processed HTMLX\nContent-length: " );
    tu_strint ( k, &hdrline[44] );
    if ( !ctx->accessesKnown && ctx->last_mod ) {
	int hlen, cstatus, i, j;
	struct mstshr_envbuf env;
	char *if_modified_since;
	/*
	 * Check for if-modified-since header.
	 */
	env.used = 1;
	env.prolog[0] = ctx->prolog[0];
	env.prolog[1] = ctx->prolog[1];
	env.prolog[2] = ctx->prolog[2];
	env.prolog[3] = ctx->prolog[3];
	cstatus = mstshr_cgi_symbols2 ( ctx->link, "", &env );
	if_modified_since = mstshr_getenv ( "HTTP_IF_MODIFIED_SINCE", &env );

	if ( http_log_level > 6 ) tlog_putlog ( 7, 
	    "if-modified header: !XL '!AZ'!/",
	   if_modified_since, if_modified_since ? if_modified_since : "" );
	if ( if_modified_since ) {
	    unsigned int cache_time;
	    for ( i = j = 0; if_modified_since[i]; i++ ) {
		if ( if_modified_since[i] != ' ' ) {
		    if_modified_since[j++] = if_modified_since[i];
		}
	    }
	    if_modified_since[j] = '\0';
	    cache_time = tf_decode_time ( if_modified_since );

	    if ( ctx->last_mod <= cache_time ) {
	        tu_strcpy ( hdrline, "304 Cached copy good\nContent-length: " );
	        tu_strint ( k, &hdrline[37] );
		method = "HEAD";		/* eliminate body */
	    }
	}
	
	/* Append last-modified header since counter not used */
	hlen = tu_strlen ( hdrline );
	tu_strcpy ( &hdrline[hlen], "\nLast-modified: " ); hlen += 16;
	tf_format_time ( ctx->last_mod, &hdrline[hlen] );
    }
#else
    sprintf ( hdrline, "200 Sending Processed HTMLX\nContent-length: %d", k );
#endif
    send_http_header ( hdrline, "text/html" );
    if ( 0 == strncmp(method,"HEAD",5) ) return 1;

    for ( i = 0; i <= sc; i++ ) if ( seg[i].length > 0 ) {
#ifdef IS_MST
	status = mst_write ( ctx->link, seg[i].addr, seg[i].length, &reslen );
#else
	status = net_link_write ( seg[i].addr, seg[i].length );
#endif
	if ( (status&1) == 0 ) break;
    }
    return 1;
}
/**************************************************************************/
/* Convert binary time to ascii string.  If fmt_str does not start with '=',
 * use ctime format, otherwise use string after '=' as format for strftime().
 */
static char *format_time (char *buffer, int bufsize, char *fmt, void *timebuf)
{
    int i;
#ifdef __DECC
    if ( fmt ) if ( *fmt == '=' ) {
	size_t size;
	LOCK_C_RTL
	size = strftime ( buffer, bufsize, &fmt[1], 
		localtime((time_t *)timebuf) );
	UNLOCK_C_RTL
	if ( (size > 0) && (size < bufsize) ) buffer[size] = '\0';
	return buffer;
    }
#endif
    /*
     * fallback to using ctime() routine.
     */
#ifdef IS_MST
    pthread_lock_global_np();
    strncpy ( buffer, ctime((unsigned long *) timebuf), bufsize-1 );
    buffer[bufsize-1] = '\0';
    pthread_unlock_global_np();
#else
    strncpy ( buffer, ctime((unsigned long *) timebuf), bufsize-1 );
    buffer[bufsize-1] = '\0';
#endif
    for ( i = 0; buffer[i]; i++ ) if ( buffer[i] == '\n' ) {
	buffer[i] = '\0'; break;
    }
    return buffer;
}
/**************************************************************************/
static segptr alloc_segment ( segptr seg, int sc, int *seg_size, char *start ) {
    sc = sc + 1;
    if ( sc >= *seg_size ) {
	*seg_size += 1000;
	seg = realloc ( seg, sizeof(struct segment)*(*seg_size) );
    }
    seg[sc].length = 0;
    seg[sc].addr = start;
    return seg;
}
/**************************************************************************/
/* Prepare to send back response.  Build standard response header.
 */
static int send_http_header ( char *stsline, char *content )
{
    int status;
#ifdef IS_MST
    struct private_ctx *ctx;
    int reslen;
    /*
     * Get pointer to thread's private data (particularly link).
     */
    GET_SPECIFIC ( private_context, ctx );
#endif
    /*
     * Enter CGI mode, set rundown to terminate mode on exit.
     */
#ifdef IS_MST
    status = mst_write ( ctx->link, "<DNETCGI>", 9, &reslen );
    if ( (status&1) == 0 ) { 
	abort_status = status; pthread_exit (&abort_status);
    }
    ctx->cgimode_active = 1;
#else
    status = net_link_write ( "<DNETCGI>", 9 );
    if ( 0 == (status&1) ) exit ( status );
    status = net_link_set_rundown ( "</DNETCGI>" );
#endif
    /*
     * Send back standard header.
     */
    if ( status&1 ) status = net_link_printf ( 
	"Content-type:%s\nstatus: %s\n\n", content, stsline );

    return status;
}
/*****************************************************************************/
static int extract_part (
	char *fname,	/* pathname of file being parsed */
	char *partname,	/* name of part to extract */
	int *outlen, 
	char **outbuf )
{
    char *ibuf, *tag, *name;
    int length, buf_used, buf_size;
    int state, tagstate, tagstart, isquoted, ibuf_i;
    char *partname_p;
    struct part_chunk { int start, end; } *parts;
    int current_part;
    int parts_size;
#ifdef IS_MST
    void *ifile;
    char errmsg[256];
    /*
     * Get pointer to private data.
     */
#else
    FILE *ifile;
#endif
    *outlen = 0;
    /*
     * Open the file.
     */
    length = strlen ( fname );
#ifdef IS_MST
    ifile = (length > 0) ? tf_open ( fname, "r", errmsg ) : (void *) 0;
    if ( ifile ) {
	/* Update last mod date in ctx if later than existing */
	int size;  unsigned uic, cdate, mdate;
	struct private_ctx *ctx;
	GET_SPECIFIC ( private_context, ctx )
	tf_header_info ( ifile, &size, &uic, &cdate, &mdate );
	if ( mdate > ctx->last_mod ) ctx->last_mod = mdate;
    }
#else
    ifile = (length > 0) ? fopen ( fname, "r", "mbc=32" ) : (FILE *) 0;
#endif
    if ( !ifile ) {
	send_http_header ( "500 Failed to open include file", "text/plain");
#ifdef IS_MST
   	net_link_printf 
	    ( "Could not open include part file (%s)\n%s", fname, errmsg );
#else
   	net_link_printf ( "Could not open include part file (%s)\n%s", fname,
			     strerror ( errno, vaxc$errno ) );
#endif
	return -1;
    }
    buf_size = 10000;
    buf_used = 0;
    ibuf = malloc ( buf_size );

    /* I make sure to allocate 8192 bytes, because that's the minimum memory
       page on an Alpha, and malloc() allocates full memory pages anyway... */
    parts_size = 8192 / sizeof (struct part_chunk);
    parts = malloc ( parts_size * sizeof ( struct part_chunk ) );
    current_part = 0;
    parts[current_part].start = -1;
    parts[current_part].end = 0;

    state = 0;
    tagstate = 10;		/* 10 when looking for <!--#begin word-->,
				   20 when looking for <!--#end word--> */
    isquoted = 0;
    ibuf_i = 0;
#ifdef IS_MST
    while ( (length=tf_read(ifile,&ibuf[buf_used], buf_size-buf_used)) > 0) {
#else
    while ( (length=fread(&ibuf[buf_used], 1, buf_size-buf_used, ifile)) > 0) {
#endif
   	if (parts[current_part].end == buf_used)
	    parts[current_part].end += length;
	buf_used += length;
	if ( buf_used >= buf_size ) {
	    buf_size += 10000;
	    ibuf = realloc ( ibuf, buf_size );
	}
   	for (; state >= 0 && ibuf_i < buf_used; ibuf_i++) {
	    char cur = ibuf[ibuf_i];
#ifdef DEBUG
	    printf ("ibuf_i{1,2,3} = {%d, %d, %d}, ",
		    parts[current_part].start, ibuf_i,
		    parts[current_part].end);
	    printf ("state = %d, ", state);
	    printf ("tagstate = %d, tagstart = %d, ", tagstate, tagstart);
	    printf ("cur = '%c' (0%%x%X), *partname_p = '%c' (0%%x%X)\n",
		    (cur & 127) >= 32 ? cur : '.', cur,
		    (*partname_p & 127 ) >= 32 ? *partname_p : '.',
		    *partname_p);
#endif
	    switch (state) {
   		case 0:
   			if (cur == '<') { state = 1; tagstart = ibuf_i; }
   			break;
   		case 1:
   			if (cur == '!') { state = 3; break; }
   			state = 2;
   		case 2:
   			if (cur == '>') state = 0;
   			break;
   
   		case 3:
   			if (cur != '-') { state = 2; ibuf_i--; break; }
   			state = 4;
   			break;
   		case 4:
   			if (cur != '-') { state = 2; ibuf_i--; break; }
   			state = 5;
   			break;
   		case 5:
   			if (cur != '#') { state = 2; ibuf_i--; break; }
   			state = tagstate;
   			break;
   
   		case 6:		/* Find terminator */
   			if (cur == '-') state = 7;
   			else if (tagstate > 0 && !isspace(cur) && cur != '\n')
   			    state = 2;
   			break;
   		case 7:
   			if (cur == '-') state = 8;
   			else if (tagstate > 0) state = 2;
   			else state = 6;
   			break;
   		case 8:
   			if (isspace(cur) || cur == '\n') break;
   			if (cur != '>') {
   			    tagstate = -tagstate; state = 6; break;
   			}
   			state = 0;
#ifdef DEBUG
		        { int saved_part = current_part;
			  printf ("before: tagstate = %d, part = %d, start = %d, end = %d\n",
				  tagstate, saved_part,
				  parts[current_part].start,
				  parts[current_part].end);
#endif
			  if (tagstate < 0) {
			      /* restart from the beginning */
			      tagstate = -tagstate;
			      if (tagstate == 20)
				  parts[current_part].end = buf_used;
			  } else if (tagstate == 10) {
			      parts[current_part].start = tagstart;
			      parts[current_part].end = buf_used;
			      tagstate = 20;
			  } else if (tagstate == 20) {
			      parts[current_part++].end = ibuf_i + 1;

			      if ( current_part >= parts_size ) {
				  parts_size += 8192 / sizeof ( struct part_chunk );
				  parts = realloc ( parts, parts_size * sizeof ( struct part_chunk ) );
			      }

			      /* -1 indicates it's not been set yet */
			      parts[current_part].start = -1;
			      parts[current_part].end = buf_used;
			      tagstate = 10;
			  }
#ifdef DEBUG
			  printf ("after: tagstate = %d, part = %d, start = %d, end = %d\n",
				  tagstate, saved_part,
				  parts[current_part].start,
				  parts[current_part].end);
		        }
#endif
   			break;

   		case 10:
   			if (isspace(cur) || cur == '\n') break;
   			if (cur != 'b' && cur != 'B') { state = 2; break; }
   			state = 11;
   			break;
   		case 11:
   			if (cur != 'e' && cur != 'E') { state = 2; break; }
   			state = 12;
   			break;
   		case 12:
   			if (cur != 'g' && cur != 'G') { state = 2; break; }
   			state = 13;
   			break;
   		case 13:
   			if (cur != 'i' && cur != 'I') { state = 2; break; }
   			state = 14;
   			break;
   		case 14:
   			if (cur != 'n' && cur != 'N') { state = 2; break; }
   			state = 30;
   			partname_p = partname;
   			break;
   
   		case 20:
   			if (isspace(cur) || cur == '\n') break;
   			if (cur != 'e' && cur != 'E') { state = 2; break; }
   			state = 21;
   			break;
   		case 21:
   			if (cur != 'n' && cur != 'N') { state = 2; break; }
   			state = 22;
   			break;
   		case 22:
   			if (cur != 'd' && cur != 'D') { state = 2; break; }
   			state = 30;
   			partname_p = partname;
   			break;
   
   		case 30:
   			if (isspace(cur) || cur == '\n') break;
   			partname_p = partname;
			state = 31;
			isquoted = 0;
   			if (cur == '"') { isquoted = 1; break; }
   		case 31:
   			if (*partname_p == cur) { 
			    partname_p++; 
			    break;
			}
			if ( (*partname_p == '\0') ) {
			    if ( isquoted ) {
				if ( cur == '"' ) { state = 33; break; }
			    } else {
				if ( (cur == '-') || isspace(cur) || 
					(cur == '\n') ) {
				    if ( cur == '-' ) ibuf_i--;
				     state = 33; break;
				}
			    }
			}
			if ( cur == '-' ) {
			    tagstate = -tagstate; ibuf_i--; state = 6; break;
			}
			partname_p = partname;	/* restart search */
			if ( isspace(cur) || (cur == '\n') ) state = 30;
			else state = 32;
		    break;
		case 32:	/* remainder of rejected label label */
		    if ( isspace(cur) || (cur == '\n') ) state = 30;
		    if ( cur == '-' ) { 
			ibuf_i--; tagstate = (-tagstate); state = 6; }
   		    break;
		case 33:
		    if ( cur == '-' || cur == '>' ) { state = 6; ibuf_i--; }
		    break;
   	    }
   	}
	if (tagstate == 20 && state == -1) break;
    }
    /*
     * rundown file.
     */
#ifdef IS_MST
    tf_close ( ifile );
#else
    fclose(ifile);
#endif
    {
      int i, tmp;
      for (i = 0, ibuf_i = 0; i <= current_part; i++)
	  if (parts[i].start >= 0) {
	      /*
	       * Copy contents.
	       */
	      strncpy(ibuf + ibuf_i,
		      &ibuf[parts[i].start],
		      tmp = parts[i].end - parts[i].start);
	      ibuf_i += tmp;
	  }
      if (ibuf_i) buf_used = ibuf_i;
    }
    ibuf[buf_used] = '\0';
    *outbuf = ibuf;
    *outlen = buf_used;
    return *outlen;
}
#ifdef IS_MST
/*****************************************************************************/
/* Read named file into memory.  Return -1 on error, 2 on success.
 */
static int load_file ( char *fname, char *lname, int *outlen, char **outbuf, 
	int initial )
{
    char *ibuf;
    int length, buf_used, buf_size, cache_allowed, cache_status;
    struct private_ctx *ctx;
    void *ifile;
    struct tfc_context tfc;
    char errmsg[256];
    /*
     * Get pointer to private data.
     */
    GET_SPECIFIC ( private_context, ctx )
    /*
     * cache status: 0-nocache, 1-reading from cache, 2 writing cache.
     */
    cache_status = tfc.mode = 0;
    if ( *fname && ctx->cache_allowed ) {
	cache_status = tfc_cached_open ( 1, fname, &ifile, errmsg, &tfc );
#ifdef DEBUG
	printf("Cache lookup on %s, status: %d\n", fname, cache_status );
#endif
    } else ifile = *fname ? tf_open ( fname, "r", errmsg ) : (void *) 0;
    
    if ( ifile ) {
	/* Update last mod date in ctx */
	int size;  unsigned uic, cdate, mdate;
	tf_header_info ( ifile, &size, &uic, &cdate, &mdate );
	if ( mdate > ctx->last_mod ) ctx->last_mod = mdate;
    } else if ( cache_status == 1 ) {
	/* Update last mod date from tfc header */
	if ( tfc.hdr.mdate > ctx->last_mod ) ctx->last_mod = tfc.hdr.mdate;
    }
    if ( !ifile && (cache_status != 1) ) {
        send_http_header ( initial ? "500 Failed to open file" :
		"500 Failed to open include file", "text/plain");
   	net_link_printf ( initial ? 
		"Could not open file for '%s' HTML pre-processing\n%s" :
		"Could not open include file (%s)\n%s",  lname,	errmsg );
	return -1;
    }
    buf_used = 0;
    if ( ifile ) {
	/*
	 * Read file contents.
	 */
       buf_size = initial ? 20000 : 10000;
       ibuf = malloc ( buf_size );
        while ( ibuf &&
	    (length=tf_read(ifile,&ibuf[buf_used], buf_size-buf_used)) > 0) {
	    buf_used += length;
	        if ( buf_used >= buf_size ) {
	        buf_size += (initial ? 20000 : 10000);
	        ibuf = realloc ( ibuf, buf_size );
   	    }
        }
	tf_close ( ifile );
	if ( cache_status == 2 && ibuf ) {
	    /*
	     * Save contents in cache in 4000 byte chunks.
	     */
	    int i, rec_size;;
	    for ( i = 0; i < buf_used; i += rec_size ) {
		rec_size = buf_used-i;
		if ( rec_size > 4000 ) rec_size = 4000;
		if ( 1 != tfc_put ( &tfc, &ibuf[i], rec_size ) ) break;
	    }
	}
    } else if ( cache_status == 1 ) {
	/*
	 * copy cache contents.
	 */
	buf_size = tfc.hdr.size;
	ibuf = malloc ( buf_size );
	for ( buf_used = 0; ibuf && (buf_used < buf_size); buf_used+=length) {
	    void *rec;
	    if ( 1 != tfc_get ( &tfc, &rec, &length ) ) break;
	    memcpy ( &ibuf[buf_used], rec, length );
	}
    }
    /*
     * Delete cache entry on error (null ibuf)
     */
    if ( tfc.mode ) tfc_rundown_context ( &tfc, ibuf ? 0 : 1 );

    *outbuf = ibuf;
    *outlen = buf_used;
    if ( !ibuf ) {
        send_http_header ( initial ? "500 Failed to load file" :
		"500 Failed to load include file", "text/plain");
   	net_link_printf ( "Memory allocation failure while loading %s", lname);
	return -1;
    }
    return 2;
}
#else /* no MST */
/*****************************************************************************/
/* Read named file into memory.  Return -1 on error, 2 on success.
 */
static int load_file ( char *fname, char *lname, int *outlen, char **outbuf, 
	int initial )
{
    char *ibuf;
    int length, buf_used, buf_size;
    FILE *ifile;

    ifile = *fname ? fopen ( fname, "r", "mbc=32" ) : (FILE *) 0;

    if ( !ifile ) {
        send_http_header ( initial ? "500 Failed to open file" :
		"500 Failed to open include file", "text/plain");
   	net_link_printf ( initial ?
		"Could not open file for '%s' HTML pre-processing\n%s" :
			"Could not open include file (%s)\n%s", lname,
			     strerror ( errno, vaxc$errno ) );
	return -1;
    }
    buf_used = 0;
    buf_size = initial ? 20000 : 10000;
    ibuf = malloc ( buf_size );
    while ( ibuf &&
	    (length=fread(&ibuf[buf_used], 1, buf_size-buf_used, ifile)) > 0) {
	buf_used += length;
	if ( buf_used >= buf_size ) {
	    buf_size += (initial ? 20000 : 10000);
	    ibuf = realloc ( ibuf, buf_size );
   	}
    }
    fclose(ifile);

    *outbuf = ibuf;
    *outlen = buf_used;
    if ( !ibuf ) {
        send_http_header ( initial ? "500 Failed to load file" :
		"500 Failed to load include file", "text/plain");
   	net_link_printf ( "Memory allocation failure while loading %s", lname);
	return -1;
    }
    return 2;
}
#endif /* MST */
/*****************************************************************************/
/* Handle the file-related server directives (include, fsize, flastmod).
 */
static int do_include ( int opcode,	/* 1 - include, 2-fsize, 3-flastmod */
	char *path,	/* pathname of file being parsed */
	char **drctv,		/* Parsed directive tokens 1=tag, 2=fname */
	int *outlen, 
	char **outbuf )
{
    char *ibuf, *tag, *name, fname[600];
    int length, buf_used, buf_size;
    int have_part;
#ifdef IS_MST
    struct private_ctx *ctx;
    void *ifile;
    /*
     * Get pointer to private data.
     */
    GET_SPECIFIC ( private_context, ctx )
#else
    FILE *ifile;
#endif
    /*
     * Examine field1 argument to determine include type.
     */
    tag = drctv[1];
    name = drctv[2];
    *outlen = 0;

    have_part = 0;
    if ( opcode == 1 && 0 == strncmp ( drctv[3], "PART", 5 ) ) {
	have_part = 1;

#ifdef DEBUG
   	printf("Part name is %s\n", fname);
#endif
    }

    if ( 0 == strncmp ( tag, "FILE", 5 ) ) {
	/*
	 * Construct filename relative to current path.
	 */
	strncpy ( fname, path, 255 ); fname[255] = '\0'; 
	for ( length = strlen(fname); 
		(length > 0) && (fname[length] != '/'); --length );
	length++;
	strcpy ( &fname[length], name );
	length = strlen ( fname );
	if ( length > 0 ) if ( fname[length-1] == '"' ) fname[length-1] = '\0';

    } else if ( 0 == strncmp ( tag, "VIRTUAL", 8 ) ) {
	/*
	 * Have server translate name.
	 */
	int status;
#ifdef IS_MST
	mst_write ( ctx->link, "<DNETXLATEV>", 12, &length );
#else
	net_link_write ( "<DNETXLATEV>",  12 );
#endif
	status = net_link_query ( name, fname, sizeof(fname)-1, &length);
	if ( 0 == (status&1) ) length = 0;
	fname[length] = '\0';
    }
    /*
     * Take action based upon opcode.
     */
    if ( opcode == 1 && !have_part ) {
	/*
	 * Include the file.
	 */
	int status;
	status = load_file ( fname, name, outlen, outbuf, 0 );
	if ( status < 0 ) return status;
	return status;
    } else if ( opcode == 1 && have_part ) {
	/*
	 * Scan file and return named part.
	 */
	length = extract_part ( fname, drctv[4], outlen, outbuf );
	return 2;

    } else {
	/*
	 * fsize(2) and flastmod(3).  Load stat structure.
	 */
	stat_t info;
	int status;
	LOCK_C_RTL
	status = (length > 0) ? stat ( fname, &info ) : -1;
	UNLOCK_C_RTL
	if ( status != 0 ) {
	    send_http_header ( "500 Failed to access file", "text/plain");
	    net_link_printf ( "Could not read file attributes (%s)\n", name );
	    return -1;
	}
	/*
	 * Format information.
	 */
	*outbuf = malloc ( 64 );
	if ( opcode == 2 ) {
	    sprintf ( *outbuf, "%d", info.st_size );
	} else {
	    /*
	     * Check if user included FMT tag.
	     */
	    char fmt[100];
	    fmt[0] = '\0';
	    if ( strncmp(drctv[3],"FMT",4) == 0 ) {
		fmt[0] = '=';
		strncpy ( &fmt[1], drctv[4], sizeof(fmt)-2 );
		fmt[99] = '\0';
	    }
	    format_time ( *outbuf, 64, fmt, &info.st_mtime );
	}
	*outlen = strlen ( *outbuf );
    }
    return 1;
}
/*****************************************************************************/
/* Return accesses count for indicated path, updating count by 1 on first call
 * A version number less than or equal to 0 means ignore the version number.
 */
static int get_access_count ( char *path, int version ) {
#ifdef IS_MST
    IFILE *fp;
    struct private_ctx *ctx;
#define accesses_known ctx->accessesKnown
#define accesses ctx->Accesses
#define rec_version ctx->version
#else
    IFILE *fp;
    static int accesses_known = 0, accesses, rec_version;
#endif
    char accessesRecord[128];
    char accessesPath[104];
    char accessesStr[20];
    int status, i, new_rec;
    size_t length;
    stat_t info;
#ifdef IS_MST
    /*
     * Get private data.
     */
   GET_SPECIFIC ( private_context, ctx )
#endif
    /*
     * Reset accesses_known if version changes.
     */
    if ( accesses_known ) if ( (version > 0) && (version != rec_version) ) {
	accesses_known = 0;
    }

    if ( !accesses_known ) {
	/*
	 * Get/update access count/version in file.  Try to open file.
	 */
#ifdef IS_MST
	iacquire_db();		/* serialize access */
	ctx->have_lock = 1;
	fp = count_db ? count_db :
	    ifopen ( "www_root:[000000]accesses.dat","r+" );
#else
	fp = ifopen ( "www_root:[000000]accesses.dat","r+" );
#endif
	if ( !fp ) {
	    /*
	     * See if we have permission to write new records.
	     */
	    LOCK_C_RTL
	    status = stat ( path, &info );
	    UNLOCK_C_RTL
	    if ( status == 0 ) 	status = icheck_access 
		( "www_root:[000000]accesses.permissions", info.st_uid );
	    if ( (status&1) == 0 ) {
	        send_http_header ( "500 Failed to create file", "text/plain");
	        net_link_printf ( 
		    "No permission to create accesses.dat file, code %d\n",
			status );
	        return -1;
	    }
	    if ( (status&1) == 0 ) {
	    }
	    /*
	     * Attempt to create indexed file and retry open.
	     */
	    status = ifdlcreate 
		( access_file_fdl, "accesses.dat", "www_root:[000000]" );
	    if ( status & 1 ) 
		fp = ifopen ( "www_root:[000000]accesses.dat","r+" );
	}
	if ( !fp ) {
	    send_http_header ( "500 Failed to access file", "text/plain" );
	    net_link_printf ( "Could not open accesses.dat file.");
	    return -1;
	}
	/*
	 * File is now open, do indexed read to get record for specified path.
	 */
#ifdef IS_MST
	count_db = fp;		/* share amongst several threads */
	tu_strnzcpy ( accessesPath, path, 100 );
	tu_strupcase ( accessesPath, accessesPath );
	i = tu_strlen ( accessesPath );
#else
	for ( i = 0; (i < 100) && path[i]; i++ ) 
		accessesPath[i] = _toupper ( path[i] );
#endif
	while ( i < 100 ) accessesPath[i++] = ' ';	/* pad to 100 bytes */
	accessesPath[i] = '\0';

	status = ifread_rec(accessesRecord,120,&length,fp,0,accessesPath,100);
	if ( status & 1 ) {
	    accessesRecord[120] = '\0';
	    sscanf(accessesRecord+100,"%10d%10d",&accesses, &rec_version);
	    new_rec = 0;
	} else {
	    /*
	     * Read error on record, assume non-existent and make initial.
	     */
	    accesses = 0;
	    rec_version = 0;
	    new_rec = 1;
	    /*
	     * Check if file owner has permission to add to accesses.dat file.
	     */
	    LOCK_C_RTL
	    status = stat ( path, &info );
	    UNLOCK_C_RTL
	    if ( status == 0 ) 	status = icheck_access 
		( "www_root:[000000]accesses.permissions", info.st_uid );
	    if ( (status&1) == 0 ) {
	        send_http_header ( "500 Failed to extend file", "text/plain");
	        net_link_printf ( 
		    "No permission to add records to accesses.dat, code %d\n",
			status );
	        return -1;
	    }
	}
        /*
	 * Reset or update access count by 1 and update record in file.
	 */
	if ( version <= 0 ) version = rec_version;
	else if ( version != rec_version ) accesses = 0;
	accesses++;

	sprintf(accessesRecord,"%-100.100s%10.10d%10.10d",accessesPath,accesses,
		version);
	if ( new_rec ) {
#ifndef IS_MST
	    iferror(fp);
#endif
	    status = ifwrite_rec(accessesRecord,120,fp);
	} else {
	    status = ifupdate_rec(accessesRecord,120,fp);
	}
#ifdef IS_MST
	ctx->have_lock = 0;
	irelease_db();
#else
	if ( (status&1) == 0 ) iferror(fp);
	ifclose(fp);
#endif
	accesses_known = 1;
    }
    return accesses;
}
#ifdef IS_MST
#undef accesses
#undef accesses_known
#undef rec_version
#endif
/*****************************************************************************/
/* Lookup indicate variables and return their values.
 */
static int do_echo ( char *path,	/* pathname of file being parsed */
	char **drctv,			/* Elements of directive */
	int *outlen, 
	char **outbuf )
{
    int SYS$ASCTIM(), timlen, i, j, length;
    char *timestr, *tag, *name;
    static enum vcodes { C_DATE_LOCAL, C_DOCUMENT_NAME, C_LAST_MODIFIED,
	C_ACCESSES, C_ACCESSES_ORDINAL, C_VMS_VERSION, C_HW_NAME,
	C_SERVER_ACCOUNT, C_SERVER_NAME, C_SERVER_VERSION, C_GETENV, C_FINIS };
    enum vcodes vcode;
    static struct {
	enum vcodes code;
	char delimiter; 
	int min_len;
	char *keyword;
    } vtbl[] = {
	{ C_DATE_LOCAL, '=', 10,  "DATE_LOCAL" },
	{ C_DOCUMENT_NAME, '\0', 13, "DOCUMENT_NAME" },
	{ C_LAST_MODIFIED, '=', 13,  "LAST_MODIFIED" },
	{ C_ACCESSES, ';', 8, "ACCESSES" },
	{ C_ACCESSES_ORDINAL, ';', 16,"ACCESSES_ORDINAL" },
	{ C_SERVER_NAME, '\0', 11, "SERVER_NAME" },
	{ C_SERVER_VERSION, '\0', 14, "SERVER_VERSION" },
	{ C_GETENV, '=', 6, "GETENV" },
	{ C_VMS_VERSION, '\0', 11, "VMS_VERSION" },
	{ C_HW_NAME, '\0', 7, "HW_NAME" },
	{ C_FINIS, '\0', 0, "" }
    };
    static char hardware[31], os_version[12];
    static $DESCRIPTOR(vms_version_dx, os_version);
    static $DESCRIPTOR(hardware_dx,hardware);
    tag = drctv[1];
    name = drctv[2];
    *outlen = 0;
    if ( strncmp ( tag, "VAR",4 ) ) return 0;	/* syntax error */

    /*
     * Lookup var keyword in table, putting it's code number in vcode.
     */
    length = strlen ( name );
    for ( vcode = C_FINIS, i = 0; vtbl[i].code != C_FINIS; i++ ) {
	if ( length >= vtbl[i].min_len ) {
	    j = vtbl[i].min_len;
	    if ( 0 == strncmp ( name,vtbl[i].keyword,j) ) {
		if ( name[j] == '\0' || name[j] == vtbl[i].delimiter ) {
		    vcode = vtbl[i].code;
		    break;
		}
	    }
	}
    }
    /*
     * Process based upon vcode.
     */
    switch ( vcode ) {
	stat_t info;
	int status, code, accesses, version, last, last2;
	char *env_val;
      case C_DATE_LOCAL:
	/*
	 * See if format info was supplied.
	 */
	if ( name[10] == '=' ) {
	    time_t now;
	    *outbuf = malloc ( 64 );
	    now = time(NULL);
	    format_time ( *outbuf, 64, &name[10], &now );
	    *outlen = strlen ( *outbuf );
	} else {
	    struct { long l; char * a; } timebuf;
	    *outbuf = malloc ( 24 );
	    timebuf.l = 24;
	    timebuf.a = *outbuf;
	    SYS$ASCTIM ( outlen, &timebuf, 0, 0 );
	}
      break;

      case C_DOCUMENT_NAME:
	/* Echo filename of document (part to right of last /) */
	*outbuf = strrchr(path,'/');
	if ( *outbuf ) *outbuf = *outbuf+1; else *outbuf = path;
	*outlen = strlen ( *outbuf );
      break;

      case C_LAST_MODIFIED:
	/*
	 * Echo last modified date of current file path.
	 */
	LOCK_C_RTL
	status = stat ( path, &info );
	UNLOCK_C_RTL
	if ( status != 0 ) {
	    send_http_header ( "500 Failed to access file", "text/plain");
	    net_link_printf ( "Could not read file attributes (%s)\n", path );
	    return -1;
	}
	/*
	 * Format information.
	 */
	*outbuf = malloc ( 64 );
	format_time ( *outbuf, 64, &name[13], &info.st_mtime );
	*outlen = strlen ( *outbuf );
	break;

      case C_ACCESSES:
      case C_ACCESSES_ORDINAL:
	/*
	 * Echo access count.
	 */
	/*
	 * see if name includes version number.
	 */
	version = 0;
	if ( (vcode == C_ACCESSES) && (name[8] == ';') ) 
		version = atoi(&name[9]);
	else if ( (vcode == C_ACCESSES_ORDINAL) && (name[16] == ';') )
		version = atoi(&name[17]);

	accesses = get_access_count ( path, version );
	if ( accesses < 0 ) return accesses;
	*outbuf = malloc ( 24 );
	if ( accesses < 1000 ) sprintf(*outbuf, "%d",accesses );
	else if ( accesses < 1000000 ) sprintf(*outbuf, "%d,%03.3d",
		accesses/1000, accesses%1000 );
	else sprintf ( *outbuf, "%d,%03.3d,%03.3d", accesses/1000000,
	    accesses%1000000/1000, accesses%1000 );
	if ( vcode == C_ACCESSES_ORDINAL ) {
	    /* tack on suffix */
	    static char *ordination[10] = { "th", "st", "nd", "rd",
		"th", "th", "th", "th", "th", "th" };
	    char *suffix;
	    last2 = accesses %100;
	    suffix = &(*outbuf)[strlen(*outbuf)];
	    last = accesses % 10;
	    if ( (last2 >= 11) && (last2 <= 13) ) last = 0;
	    strcpy ( suffix, ordination[last] );
	}
	*outlen = strlen ( *outbuf );
	break;

      case C_SERVER_NAME:
	*outbuf = malloc ( 256 );
	status = net_link_query ( "<DNETHOST>", *outbuf, 255, outlen );
	if ( (status&1) == 0 ) *outlen = 0;
	break;

      case C_SERVER_VERSION:
	*outbuf = malloc ( 256 );
	status = net_link_query ( "<DNETID2>", *outbuf, 255, outlen );
	if ( (status&1) == 0 ) *outlen = 0;
	else *outlen = strchr(*outbuf, ' ') - *outbuf;
	break;

      case C_GETENV:
	/*
	 * Return value of arbitrary logical or DCL symbol.
	 */
	if ( length < 8 ) break;	/* invalid syntax */
#ifdef IS_MST
	pthread_lock_global_np();
#endif
	env_val = getenv ( &name[7] );
	if ( env_val ) {
	    *outlen = strlen ( env_val );
	    *outbuf = malloc ( *outlen + 1 );
	    strcpy ( *outbuf, env_val );
	}
	else { *outbuf = "unknown"; *outlen = 7; }
#ifdef IS_MST
	pthread_unlock_global_np();
#endif
	break;

      case C_VMS_VERSION:
	code = SYI$_VERSION;
#ifdef IS_MST
	*outbuf = malloc ( sizeof(os_version)+1 );
	pthread_lock_global_np();
	LIB$GETSYI ( &code, 0, &vms_version_dx, outlen, 0, 0 );
	tu_strnzcpy ( *outbuf, vms_version_dx.dsc$a_pointer, *outlen );
	pthread_unlock_global_np();
#else
	*outbuf = os_version;
	LIB$GETSYI ( &code, 0, &vms_version_dx, outlen, 0, 0 );
	if ( *outlen < sizeof(os_version) ) os_version[*outlen] = '\0';
#endif
	break;

      case C_HW_NAME:
	code = SYI$_HW_NAME;
#ifdef IS_MST
	*outbuf = malloc ( sizeof(hardware)+1 );
	pthread_lock_global_np();
	LIB$GETSYI ( &code, 0, &hardware_dx, outlen, 0, 0 );
	tu_strnzcpy ( *outbuf, hardware_dx.dsc$a_pointer, *outlen );
	pthread_unlock_global_np();
#else
	*outbuf = hardware;
	LIB$GETSYI ( &code, 0, &hardware_dx, outlen, 0, 0 );
	if ( *outlen < sizeof(hardware) ) hardware[*outlen] = '\0';
#endif
	break;

      default:
	/* 
	 * Unknown var.
	 */
	*outbuf = malloc ( length + 3 );
	sprintf ( *outbuf, "?%s?", name );
	*outlen = length + 2;
	break;
    }
    return 1;
}
/*****************************************************************************/
/* Handle config directive.
 * Tags:
 *       verify=[0|1]
 */
static int do_config ( char *path,
	char *field1, char *field2,
	int *outlen, 
	char **outbuf )
{ 
    if ( strncmp ( field1, "VERIFY", 7) == 0 ) {
	/*
	 * Set tag verify global that governs whether tag will be included.
	 */
#ifdef IS_MST
	struct private_ctx *ctx;
	GET_SPECIFIC ( private_context, ctx )
	if ( *field2 == '1' || *field2 == 'Y' ) ctx->tag_verify = 1;
	else ctx->tag_verify = 0;
#else
	if ( *field2 == '1' || *field2 == 'Y' ) tag_verify = 1;
	else tag_verify = 0;
#endif
    }
    *outlen = 0; *outbuf=" "; return 0; 
}
/*****************************************************************************/
/* Top routine to handle interpreting an server-side html directive,
 * optionally returning to the caller a pointer to buffer containing
 * additional data to send to the client (inserted at the point of
 * the directive).  A zero outlen indicates no optional data is returned.
 *
 * The directive takes the form <!--#command [tag1=value [tag2=value]]-->
 *
 * Note that tag array is modified by this routine.
 * Note also that output buffers must be statically allocated, they are not
 * read by the caller until final processing.
 *
 * If this routine generates an error response, it must return -1 as the
 * function value.
 */
static int parse_directive ( 
	char *path,			/* Pathname of file being parsed */
	char **drctv,			/* 5 elements of parsed directive */
	int *outlen, 			/* Size of result output buffer */
	char **outbuf )			/* Address of result buffer */
{
    int i, taglen;
    char *command;
    /*
     * Scan the directive tag and parse into command and tags.
     */
#ifdef DEBUG
printf("Directive: '%s' '%s'='%s' '%s'='%s'\n\n", drctv[0], drctv[1],
drctv[2],drctv[3],drctv[4] );
#endif
    command = drctv[0];
    /*
     * Now interpret command.
     */
    if ( 0 == strncmp(command,"INCLUDE",8) ) {
	/*
	 * Include file.
	 */
	return do_include ( 1, path, drctv, outlen, outbuf );

    } else if ( 0 == strncmp ( command, "ECHO",5 ) ) {
	/*
	 * Echo will insert values of special variables into stream.
	 */
	return do_echo ( path, drctv, outlen, outbuf );
    } else if ( 0 == strncmp(command,"FSIZE",6) ) {
	/*
	 * File attributes.
	 */
	return do_include ( 2, path, drctv, outlen, outbuf );
    } else if ( 0 == strncmp(command,"FLASTMOD",9) ) {
	return do_include ( 3, path, drctv, outlen, outbuf );

    } else if ( 0 == strncmp ( command, "CONFIG",7 ) ) {
	/*
	 * Set configuration parameters.
	 */
	return do_config ( path, drctv[1], drctv[2], outlen, outbuf );
    } else if ( 0 == strncmp ( command, "BEGIN", 6 )
	       || 0 == strncmp ( command, "END", 4 ) ) {
	/* For some reason, I HAVE to put one space in the output
	   buffer.  If I just do '*outlen = 0;', the ending ">" will
	   be preserved in the .htmlx file.  If I do '*outbuf = "";
	   *outlen = 0;', everything after on of these directives
	   will be ignored...  --  Richard Levitte  */
	*outbuf = " ";
	*outlen = 0;		/* fixed bug, so can use zero length, DLJ */
	return 1;
    }
    /*
     * Invalid directive in file.
     */
    send_http_header ( "500 Bad directive in file", "text/plain" );
    net_link_printf("invalid directive: (%s %s %s)\n", command, drctv[1],
		drctv[2] );
    *outlen = 0;
    return -1;
}
/*****************************************************************************/
/* Parse HTML tag.  If HTML tag looks like a pre-processor directive,
 * fill in targ array with parsed elments. (command, (tag,value) pairs).
 *
 * Return value:
 *	0	Tag is NOT a pro
 */
static int parse_tag ( char *tag, 	/* start of tag */
    int maxlen, 			/* remaining bytes in input file */
    int *taglen, 			/* Size of tag, including closing '>'*/
    char **targ )			/* Elements, cmd and up to 5 tag/value
					   pairs */
{
    int state, i, j, eot_ok;
    char *directive, cur;
    /*
     * First, determine if tag if directive or not, Size of tag is
     * also determined.
     */
    if ( *tag != '<' ) { *taglen = 0; return 0; }

    for ( state = i = 0; (state >= 0) && (i < maxlen); i++ ) {
	cur = tag[i];
	switch (state)  {
	  case 0:
	    if ( cur != '<' ) { *taglen = i+1; return 0; }
	    state = 1;
	    break;
	   case 1:
	     if ( cur == '!' ) { state = 3; break; }
	     state = 2;
	   case 2:	/* Not a directive, search for end. */
	     if ( cur == '>' ) { *taglen = i+1; return 0; }
	     break;

	   case 3:
	     if ( cur != '-' ) { state = 2; --i; break; }
	     state = 4;
	     break;
	   case 4:
	     if ( cur != '-' ) { state = 2; --i; break; }
	     state = 5;
	     break;
	   case 5:
	     if ( cur != '#' ) { state = 2; --i; break; }
	     state = 6;
	     break;

	   case 6:		/* Find terminator */
	     if ( cur == '-' ) state = 7;
	     break;
	   case 7:
	     if ( cur == '-' ) state = 8; else state = 6;
	     break;
	   case 8:
	    if ( cur == '>' ) state = -1; else state = (cur=='-') ? 8 : 6;
	    break;
	}
    }
    /*
     * Getting to this point means we parsed a directive (state = -1).
     * Make copy of tag and parse the copy (portions of copy will be upcased).
     */
    directive = malloc ( i + 1 );
    strncpy ( directive, tag, i );
    directive[i] = '\0';
    tag = directive;
#ifdef DEBUG
printf("comment: '%s' l = %d\n", directive, i );
#endif
    /*
     * Initialize return array to all null strings.
     */
    *taglen = i;
    targ[0] = &tag[5];
    for ( i = 1; i < 11; i++ ) targ[i] = "";
    eot_ok = 1;
    /*
     * Parse tag into directive components using state machine.
     */
    for ( state = j = 0, i = 5; (state >=0) && (i < *taglen-3); i++ ) {
#ifdef DEBUG
printf("state = %d, tag[%d] = '%c'\n", state, i, tag[i] );
#endif
	cur = tag[i];
	switch ( state ) {
	   case 0:
		if ( isspace(cur) || (cur=='\n') )  {
		    /* found end of targ[0] */
		    tag[i] = '\0'; 
#ifdef IS_MST
		    tu_strupcase ( tag, tag );
#endif
		    state = 1;
		    j = 1;
		    targ[j] = &tag[i]; 
		}
#ifndef IS_MST
		else tag[i] = _toupper(cur);
#endif
	        break;

	   case 1:			/* look for start of targ[j] */
		if ( !isspace(cur) /* || (cur == '\n') */ ) {
		    targ[j] = &tag[i];
		    state = 2;
		    eot_ok = 0;
		}
		else break;
	   case 2:			/* look for end of targ[1] */
		if ( cur == '=' ) { 
		    tag[i] = '\0'; 
		    state = 3;
#ifdef IS_MST
		    tu_strupcase ( targ[j], targ[j] );
#endif
		}
		else if ( isspace(cur) ) state = -1; /* syntax error */
#ifndef IS_MST
		else tag[i] = _toupper(cur);
#endif
		break;

	   case 3:			/* Check for proper syntax */
		if ( cur == '"' ) {
		    j++;		/* Advance to value part */
		    targ[j] = &tag[i+1];
		    state = 4;
	 	} else state = -1;
		break;
	    case 4:
		if ( cur == '"' ) {	/* end of targ[2] */
		    tag[i] = '\0';
		    j++;
		    targ[j] = &tag[i];
		    state = 1;		/* look for next value */
		    eot_ok = 1;
		}
		break;
	}
    }
    if ( state == -1 ) {
	free ( directive );
	return 0;	/* improper syntax */
    }
    return 1;
}
/*****************************************************************************/
/*
 * Scan file specification to see if the filename contains an embedded
 * part name.  If a partname is present, copy the partname to a separate
 * string and reconstruct filename without the embedded partname.
 *
 * Function value returned is 1 if partname present and 0 if not.
 */
static int parse_part_name ( char *fname, char **pfile, char **pname )
{
    int length, i, dot1, dot2;
    char cur, *tail;
    /*
     * Parse from back, looking for postiions in fname of 1st 2 dots after
     * the last slash.
     */
    dot1 = dot2 = -1;
    length = strlen ( fname );
    for ( i = length - 1; i >= 0; --i ) {
	cur = fname[i];
	if ( (cur == '/') || (cur == '>') || (cur == ']') ) break;
	if ( cur == '.' ) {
	    dot2 = dot1;
	    dot1 = i;
	}
    }
    *pfile = fname;
    if ( dot2 < 0 ) return 0;		/* normal filename */
    if ( fname[dot2+1] >= '0' && fname[dot2+1] <= '9' ) return 0;
    /*
     * Reconstruct filename without part and put part in separate.
     */
    *pname = malloc ( dot2 - dot1 );
    strncpy ( *pname, &fname[dot1+1], (dot2-dot1-1) );
    (*pname)[dot2-dot1-1] = '\0';

    *pfile = malloc ( length +1 );
    strncpy ( *pfile, fname, dot1 );		/* all before 1st dot */
    strcpy ( (*pfile)+dot1, &fname[dot2] );	/* rest */
    if ( (dot1+1) == dot2 ) return 0;		/* part name null */
    return 1;
}