[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] [1751] [1752] [1753] [1754] [1755] [1756] [1757] [1758] [1759] [1760] [1761] [1762] [1763] [1764] [1765] [1766] [1767] [1768] [1769] [1770] [1771] [1772] [1773] [1774] [1775] [1776] [1777] [1778] [1779] [1780] [1781] [1782] [1783] [1784] [1785] [1786] [1787] [1788] [1789] [1790] [1791] [1792] [1793] [1794] [1795] [1796] [1797] [1798] [1799] [1800] [1801] [1802] [1803] [1804] [1805] [1806] [1807] [1808] [1809] [1810] [1811] [1812] [1813] [1814] [1815] [1816] [1817] [1818] [1819] [1820] [1821] [1822] [1823] [1824] [1825] [1826] [1827] [1828] [1829] [1830] [1831] [1832] [1833] [1834] [1835] [1836] [1837] [1838] [1839] [1840] [1841] [1842] [1843] [1844] [1845] [1846] [1847] [1848] [1849] [1850] [1851] [1852] [1853] [1854] [1855] [1856] [1857] [1858] [1859] [1860] [1861] [1862] [1863] [1864] [1865] [1866] [1867] [1868] [1869] [1870] [1871] [1872] [1873] [1874] [1875] [1876] [1877] [1878] [1879] [1880] [1881] [1882] [1883] [1884] [1885] [1886] [1887] [1888] [1889] [1890] [1891] [1892] [1893] [1894] [1895] [1896] [1897] [1898] [1899] [1900] [1901] [1902] [1903] [1904] [1905] [1906] [1907] [1908] [1909] [1910] [1911] [1912] [1913] [1914] [1915] [1916] [1917] [1918] [1919] [1920] [1921] [1922] [1923] [1924] [1925] [1926] [1927] [1928] [1929] [1930] [1931] [1932] [1933] [1934] [1935] [1936] [1937] [1938] [1939] [1940] [1941] [1942] [1943] [1944] [1945] [1946] [1947] [1948] [1949] [1950] [1951] [1952] [1953] [1954] [1955] [1956] [1957] [1958] [1959] [1960] [1961] [1962] [1963] [1964] [1965] [1966] [1967] [1968] [1969] [1970] [1971] [1972] [1973] [1974] [1975] [1976] [1977] [1978] [1979] [1980] [1981] [1982] [1983] [1984] [1985] [1986] [1987] [1988] [1989] [1990] [1991] [1992] [1993] [1994] [1995] [1996] [1997] [1998] [1999] [2000] [2001] [2002] [2003] [2004] [2005] [2006] [2007] [2008] [2009] [2010] [2011] [2012] [2013] [2014] [2015] [2016] [2017] [2018] [2019] [2020] [2021] [2022] [2023] [2024] [2025] [2026] [2027] [2028] [2029] [2030] [2031] [2032] [2033] [2034] [2035] [2036] [2037] [2038] [2039] [2040] [2041] [2042] [2043] [2044] [2045] [2046] [2047] [2048] [2049] [2050] [2051] [2052] [2053] [2054] [2055] [2056] [2057] [2058] [2059] [2060] [2061] [2062] [2063] [2064] [2065] [2066] [2067] [2068] [2069] [2070] [2071] [2072] [2073] [2074] [2075] [2076] [2077] [2078] [2079] [2080] [2081] [2082] [2083] [2084] [2085] [2086] [2087] [2088] [2089] [2090] [2091] [2092] [2093] [2094] [2095] [2096] [2097] [2098] [2099] [2100] [2101] [2102] [2103] [2104] [2105] [2106] [2107] [2108] [2109] [2110] [2111] [2112] [2113] [2114] [2115] [2116] [2117] [2118] [2119] [2120] [2121] [2122] [2123] [2124] [2125] [2126] [2127] [2128] [2129] [2130] [2131] [2132] [2133] [2134] [2135] [2136] [2137] [2138] [2139] [2140] [2141] [2142] [2143] [2144] [2145] [2146] [2147] [2148] [2149] [2150] [2151] [2152] [2153] [2154] [2155] [2156] [2157] [2158] [2159] [2160] [2161] [2162] [2163] [2164] [2165] [2166] [2167] [2168] [2169] [2170] [2171] [2172] [2173] [2174] [2175] [2176] [2177] [2178] [2179] [2180] [2181] [2182] [2183] [2184] [2185] [2186] [2187] [2188] [2189] [2190] [2191] [2192] [2193] [2194] [2195] [2196] [2197] [2198] [2199] [2200] [2201] [2202] [2203] [2204] [2205] [2206] [2207] [2208] [2209] [2210] [2211] [2212] [2213] [2214] [2215] [2216] [2217] [2218] [2219] [2220] [2221] [2222] [2223] [2224] [2225] [2226] [2227] [2228] [2229] [2230] [2231] [2232] [2233] [2234] [2235] [2236] [2237] [2238] [2239] [2240] [2241] [2242] [2243] [2244] [2245] [2246] [2247] [2248] [2249] [2250] [2251] [2252] [2253] [2254] [2255] [2256] [2257] [2258] [2259] [2260] [2261] [2262] [2263] [2264] [2265] [2266] [2267] [2268] [2269] [2270] [2271] [2272] [2273] [2274] [2275] [2276] [2277] [2278] [2279] [2280] [2281] [2282] [2283] [2284] [2285] [2286] [2287] [2288] [2289] [2290] [2291] [2292] [2293] [2294] [2295] [2296] [2297] [2298] [2299] [2300] [2301] [2302] [2303] [2304] [2305] [2306] [2307] [2308] [2309] [2310] [2311] [2312] [2313] [2314] [2315] [2316] [2317] [2318] [2319] [2320] [2321] [2322] [2323] [2324] [2325] [2326] [2327] [2328] [2329] [2330] [2331] [2332] [2333] [2334] [2335] [2336] [2337] [2338] [2339] [2340] [2341] [2342] [2343] [2344] [2345] [2346] [2347] [2348] [2349] [2350] [2351] [2352] [2353] [2354] [2355] [2356] [2357] [2358] [2359] [2360] [2361] [2362] [2363] [2364] [2365] [2366] [2367] [2368] [2369] [2370] [2371] [2372] [2373] [2374] [2375] [2376] [2377] [2378] [2379] [2380] [2381] [2382] [2383] [2384] [2385] [2386] [2387] [2388] [2389] [2390] [2391] [2392] [2393] [2394] [2395] [2396] [2397] [2398] [2399] [2400] [2401] [2402] [2403] [2404] [2405] [2406] [2407] [2408] [2409] [2410] [2411] [2412] [2413] [2414] [2415] [2416] [2417] [2418] [2419] [2420] [2421] [2422] [2423] [2424] [2425] [2426] [2427] [2428] [2429] [2430] [2431] [2432] [2433] [2434] [2435] [2436] [2437] [2438] [2439] [2440] [2441] [2442] [2443] [2444] [2445] [2446] [2447] [2448] [2449] [2450] [2451] [2452] [2453] [2454] [2455] [2456] [2457] [2458] [2459] [2460] [2461] [2462] [2463] [2464] [2465] [2466] [2467] [2468] [2469] [2470] [2471] [2472] [2473] [2474] [2475] [2476] [2477] [2478] [2479] [2480] [2481] [2482] [2483] [2484] [2485] [2486] [2487] [2488] [2489] [2490] [2491] [2492] [2493] [2494] [2495] [2496] [2497] [2498] [2499] [2500] [2501] [2502] [2503] [2504] [2505] [2506] [2507] [2508] [2509] [2510] [2511] [2512] [2513] [2514] [2515] [2516] [2517] [2518] [2519] [2520] [2521] [2522] [2523] [2524] [2525] [2526] [2527] [2528] [2529] [2530] [2531] [2532] [2533] [2534] [2535] [2536] [2537] [2538] [2539] [2540] [2541] [2542] [2543] [2544] [2545] [2546] [2547] [2548] [2549] [2550] [2551] [2552] [2553] [2554] [2555] [2556] [2557] [2558] [2559] [2560] [2561] [2562] [2563] [2564] [2565] [2566] [2567] [2568] [2569] [2570] [2571] [2572] [2573] [2574] [2575] [2576] [2577] [2578] [2579] [2580] [2581] [2582] [2583] [2584] [2585] [2586] [2587] [2588] [2589] [2590] [2591] [2592] [2593] [2594] [2595] [2596] [2597] [2598] [2599] [2600] [2601] [2602] [2603] [2604] [2605] [2606] [2607] [2608] [2609] [2610] [2611] [2612] [2613] [2614] [2615] [2616] [2617] [2618] [2619] [2620] [2621] [2622] [2623] [2624] [2625] [2626] [2627] [2628] [2629] [2630] [2631] [2632] [2633] [2634] [2635] [2636] [2637] [2638] [2639] [2640] [2641] [2642] [2643] [2644] [2645] [2646] [2647] [2648] [2649] [2650] [2651] [2652] [2653] [2654] [2655] [2656] [2657] [2658] [2659] [2660] [2661] [2662] [2663] [2664] [2665] [2666] [2667] [2668] [2669] [2670] [2671] [2672] [2673] [2674] [2675] [2676] [2677] [2678] [2679] [2680] [2681] [2682] [2683] [2684] [2685] [2686] [2687] [2688] [2689] [2690] [2691] [2692] [2693] [2694] [2695] [2696] [2697] [2698] [2699] [2700] [2701] [2702] [2703] [2704] [2705] [2706] [2707] [2708] [2709] [2710] [2711] [2712] [2713] [2714] [2715] [2716] [2717] [2718] [2719] [2720] [2721] [2722] [2723] [2724] [2725] [2726] [2727] [2728] [2729] [2730] [2731] [2732] [2733] [2734] [2735] [2736] [2737] [2738] [2739] [2740] [2741] [2742] [2743] [2744] [2745] [2746] [2747] [2748] [2749] [2750] [2751] [2752] [2753] [2754] [2755] [2756] [2757] [2758] [2759] [2760] [2761] [2762] [2763] [2764] [2765] [2766] [2767] [2768] [2769] [2770] [2771] [2772] [2773] [2774] [2775] [2776] [2777] [2778] [2779] [2780] [2781] [2782] [2783] [2784] [2785] [2786] [2787] [2788] [2789] [2790] [2791] [2792] [2793] [2794] [2795] [2796] [2797] [2798] [2799] [2800] [2801] [2802] [2803] [2804] [2805] [2806] [2807] [2808] [2809] [2810] [2811] [2812] [2813] [2814] [2815] [2816] [2817] [2818] [2819] [2820] [2821] [2822] [2823] [2824] [2825] [2826] [2827] [2828] [2829] [2830] [2831] [2832] [2833] [2834] [2835] [2836] [2837] [2838] [2839] [2840] [2841] [2842] [2843] [2844] [2845] [2846] [2847] [2848] [2849] [2850] [2851] [2852] [2853] [2854] [2855] [2856] [2857] [2858] [2859] [2860] [2861] [2862] [2863] [2864] [2865] [2866] [2867] [2868] [2869] [2870] [2871] [2872] [2873] [2874] [2875] [2876] [2877] [2878] [2879] [2880] [2881] [2882] [2883] [2884] [2885] [2886] [2887] [2888] [2889] [2890] [2891] [2892] [2893] [2894] [2895] [2896] [2897] [2898] [2899] [2900] [2901] [2902] [2903] [2904] [2905] [2906] [2907] [2908] [2909] [2910] [2911] [2912] [2913] [2914] [2915] [2916] [2917] [2918] [2919] [2920] [2921] [2922] [2923] [2924] [2925] [2926] [2927] [2928] [2929] [2930] [2931] [2932] [2933] [2934] [2935] [2936] [2937] [2938] [2939] [2940] [2941] [2942] [2943] [2944] [2945] [2946] [2947] [2948] [2949] [2950] [2951] [2952] [2953] [2954] [2955] [2956] [2957] [2958] [2959] [2960] [2961] [2962] [2963] [2964] [2965] [2966] [2967] [2968] [2969] [2970] [2971] [2972] [2973] [2974] [2975] [2976] [2977] [2978] [2979] [2980] [2981] [2982] [2983] [2984] [2985] [2986] [2987] [2988] [2989] [2990] [2991] [2992] [2993] [2994] [2995] [2996] [2997] [2998] [2999] [3000] [3001] [3002] [3003] [3004] [3005] [3006] [3007] [3008] [3009] [3010] [3011] [3012] [3013] [3014] [3015] [3016] [3017] [3018] [3019] [3020] [3021] [3022] [3023] [3024] [3025] [3026] [3027] [3028] [3029] [3030] [3031] [3032] [3033] [3034] [3035] [3036] [3037] [3038] [3039] [3040] [3041] [3042] [3043] [3044] [3045] [3046] [3047] [3048] [3049] [3050] [3051] [3052] [3053] [3054] [3055] [3056] [3057] [3058] [3059] [3060] [3061] [3062] [3063] [3064] [3065] [3066] [3067] [3068] [3069] [3070] [3071] [3072] [3073] [3074] [3075] [3076] [3077] [3078] [3079] [3080] [3081] [3082] [3083] [3084] [3085] [3086] [3087] [3088] [3089] [3090] [3091] [3092] [3093] [3094] [3095] [3096] [3097] [3098] [3099] [3100] [3101] [3102] [3103] [3104] [3105] [3106] [3107] [3108] [3109] [3110] [3111] [3112] [3113] [3114] [3115] [3116] [3117] [3118] [3119] [3120] [3121] [3122] [3123] [3124] [3125] [3126] [3127] [3128] [3129] [3130] [3131] [3132] [3133] [3134] [3135] [3136] [3137] [3138] [3139] [3140] [3141] [3142] [3143] [3144] [3145] [3146] [3147] [3148] [3149] [3150] [3151] [3152] [3153] [3154] [3155] [3156] [3157] [3158] [3159] [3160] [3161] [3162] [3163] [3164] [3165] [3166] [3167] [3168] [3169] [3170] [3171] [3172] [3173] [3174] [3175] [3176] [3177] [3178] [3179] [3180] [3181] [3182] [3183] [3184] [3185] [3186] [3187] [3188] [3189] [3190] [3191] [3192] [3193] [3194] [3195] [3196] [3197] [3198] [3199] [3200] [3201] [3202] [3203] [3204] [3205] [3206] [3207] [3208] [3209] [3210] [3211] [3212] [3213] [3214] [3215] [3216] [3217] [3218] [3219] [3220] [3221] [3222] [3223] [3224] [3225] [3226] [3227] [3228] [3229] [3230] [3231] [3232] [3233] [3234] [3235] [3236] [3237] [3238] [3239] [3240] [3241] [3242] [3243] [3244] [3245] [3246] [3247] [3248] [3249] [3250] [3251] [3252] [3253] [3254] [3255] [3256] [3257] [3258] [3259] [3260] [3261] [3262] [3263] [3264] [3265] [3266] [3267] [3268] [3269] [3270] [3271] [3272] [3273] [3274] [3275] [3276] [3277] [3278] [3279] [3280] [3281] [3282] [3283] [3284] [3285] [3286] [3287] [3288] [3289] [3290] [3291] [3292] [3293] [3294] [3295] [3296] [3297] [3298] [3299] [3300] [3301] [3302] [3303] [3304] [3305] [3306] [3307] [3308] [3309] [3310] [3311] [3312] [3313] [3314] [3315] [3316] [3317] [3318] [3319] [3320] [3321] [3322] [3323] [3324] [3325] [3326] [3327] [3328] [3329] [3330] [3331] [3332] [3333] [3334] [3335] [3336] [3337] [3338] [3339] [3340] [3341] [3342] [3343] [3344] [3345] [3346] [3347] [3348] [3349] [3350] [3351] [3352] [3353] [3354] [3355] [3356] [3357] [3358] [3359] [3360] [3361] [3362] [3363] [3364] [3365] [3366] [3367] [3368] [3369] [3370] [3371] [3372] [3373] [3374] [3375] [3376] [3377] [3378] [3379] [3380] [3381] [3382] [3383] [3384] [3385] [3386] [3387] [3388] [3389] [3390] [3391] [3392] [3393] [3394] [3395] [3396] [3397] [3398] [3399] [3400] [3401] [3402] [3403] [3404] [3405] [3406] [3407] [3408] [3409] [3410] [3411] [3412] [3413] [3414] [3415] [3416] [3417] [3418] [3419] [3420] [3421] [3422] [3423] [3424] [3425] [3426] [3427] [3428] [3429] [3430] [3431] [3432] [3433] [3434] [3435] [3436] [3437] [3438] [3439] [3440] [3441] [3442] [3443] [3444] [3445] [3446] [3447] [3448] [3449] [3450] [3451] [3452] [3453] [3454] [3455] [3456] [3457] [3458] [3459] [3460] [3461] [3462] [3463] [3464] [3465] [3466] [3467] [3468] [3469] [3470] [3471] [3472] [3473] [3474] [3475] [3476] [3477] [3478] [3479] [3480] [3481] [3482] [3483] [3484] [3485] [3486] [3487] [3488] [3489] [3490] [3491] [3492] [3493] [3494] [3495] [3496] [3497] [3498] [3499] [3500] [3501] [3502] [3503] [3504] [3505] [3506] [3507] [3508] [3509] [3510] [3511] [3512] [3513] [3514] [3515] [3516] [3517] [3518] [3519] [3520] [3521] [3522] [3523] [3524] [3525] [3526] [3527] [3528] [3529] [3530] [3531] [3532] [3533] [3534] [3535] [3536] [3537] [3538] [3539] [3540] [3541] [3542] [3543] [3544] [3545] [3546] [3547] [3548] [3549] [3550] [3551] [3552] [3553] [3554] [3555] [3556] [3557] [3558] [3559] [3560] [3561] [3562] [3563] [3564] [3565] [3566] [3567] [3568] [3569] [3570] [3571] [3572] [3573] [3574] [3575] [3576] [3577] [3578] [3579] [3580] [3581] [3582] [3583] [3584] [3585] [3586] [3587] [3588] [3589] [3590] [3591] [3592] [3593] [3594] [3595] [3596] [3597] [3598] [3599] [3600] [3601] [3602] [3603] [3604] [3605] [3606] [3607] [3608] [3609] [3610] [3611] [3612] [3613] [3614] [3615] [3616] [3617] [3618] [3619] [3620] [3621] [3622] [3623] [3624] [3625] [3626] [3627] [3628] [3629] [3630] [3631] [3632] [3633] [3634] [3635] [3636] [3637] [3638] [3639] [3640] [3641] [3642] [3643] [3644] [3645] [3646] [3647] [3648] [3649] [3650] [3651] [3652] [3653] [3654] [3655] [3656] [3657] [3658] [3659] [3660] [3661] [3662] [3663] [3664] [3665] [3666] [3667] [3668] [3669] [3670] [3671] [3672] [3673] [3674] [3675] [3676] [3677] [3678] [3679] [3680] [3681] [3682] [3683] [3684] [3685] [3686] [3687] [3688] [3689] [3690] [3691] [3692] [3693] [3694] [3695] [3696] [3697] [3698] [3699] [3700] [3701] [3702] [3703] [3704] [3705] [3706] [3707] [3708] [3709] [3710] [3711] [3712] [3713] [3714] [3715] [3716] [3717] [3718] [3719] [3720] [3721] [3722] [3723] [3724] [3725] [3726] [3727] [3728] [3729] [3730] [3731] [3732] [3733] [3734] [3735] [3736] [3737] [3738] [3739] [3740] [3741] [3742] [3743] [3744] [3745] [3746] [3747] [3748] [3749] [3750] [3751] [3752] [3753] [3754] [3755] [3756] [3757] [3758] [3759] [3760] [3761] [3762] [3763] [3764] [3765] [3766] [3767] [3768] [3769] [3770] [3771] [3772] [3773] [3774] [3775] [3776] [3777] [3778] [3779] [3780] [3781] [3782] [3783] [3784] [3785] [3786] [3787] [3788] [3789] [3790] [3791] [3792] [3793] [3794] [3795] [3796] [3797] [3798] [3799] [3800] [3801] [3802] [3803] [3804] [3805] [3806] [3807] [3808] [3809] [3810] [3811] [3812] [3813] [3814] [3815] [3816] [3817] [3818] [3819] [3820] [3821] [3822] [3823] [3824] [3825] [3826] [3827] [3828] [3829] [3830] [3831] [3832] [3833] [3834] [3835] [3836] [3837] [3838] [3839] [3840] [3841] [3842] [3843] [3844] [3845] [3846] [3847] [3848] [3849] [3850] [3851] [3852] [3853] [3854] [3855] [3856] [3857] [3858] [3859] [3860] [3861] [3862] [3863] [3864] [3865] [3866] [3867] [3868] [3869] [3870] [3871] [3872] [3873] [3874] [3875] [3876] [3877] [3878] [3879] [3880] [3881] [3882] [3883] [3884] [3885] [3886] [3887] [3888] [3889] [3890] [3891] [3892] [3893] [3894] [3895] [3896] [3897] [3898] [3899] [3900] [3901] [3902] [3903] [3904] [3905] [3906] [3907] [3908] [3909] [3910] [3911] [3912] [3913] [3914] [3915] [3916] [3917] [3918] [3919] [3920] [3921] [3922] [3923] [3924] [3925] [3926] [3927] [3928] [3929] [3930] [3931] [3932] [3933] [3934] [3935] [3936] [3937] [3938] [3939] [3940] [3941] [3942] [3943] [3944] [3945] [3946] [3947] [3948] [3949] [3950] [3951] [3952] [3953] [3954] [3955] [3956] [3957] [3958] [3959] [3960] [3961] [3962] [3963] [3964] [3965] [3966] [3967] [3968] [3969] [3970] [3971] [3972] [3973] [3974] [3975] [3976] [3977] [3978] [3979] [3980] [3981] [3982] [3983] [3984] [3985] [3986] [3987] [3988] [3989] [3990] [3991] [3992] [3993] [3994] [3995] [3996] [3997] [3998] [3999] [4000] [4001] [4002] [4003] [4004] [4005] [4006] [4007] [4008] [4009] [4010] [4011] [4012] [4013] [4014] [4015] [4016] [4017] [4018] [4019] [4020] [4021] [4022] [4023] [4024] [4025] [4026] [4027] [4028] [4029] [4030] [4031] [4032] [4033] [4034] [4035] [4036] [4037] [4038] [4039] [4040] [4041] [4042] [4043] [4044] [4045] [4046] [4047] [4048] [4049] [4050] [4051] [4052] [4053] [4054] [4055] [4056] [4057] [4058] [4059] [4060] [4061] [4062] [4063] [4064] [4065] [4066] [4067] [4068] [4069] [4070] [4071] [4072] [4073] [4074] [4075] [4076] [4077] [4078] [4079] [4080] [4081] [4082] [4083] [4084] [4085] [4086] [4087] [4088] [4089] [4090] [4091] [4092] [4093] [4094] [4095] [4096] [4097] [4098] [4099] [4100] [4101] [4102] [4103] [4104] [4105] [4106] [4107] [4108] [4109] [4110] [4111] [4112] [4113] [4114] [4115] [4116] [4117] [4118] [4119] [4120] [4121] [4122] [4123] [4124] [4125] [4126] [4127] [4128] [4129] [4130] [4131] [4132] [4133] [4134] [4135] [4136] [4137] [4138] [4139] [4140] [4141] [4142] [4143] [4144] [4145] [4146] [4147] [4148] [4149] [4150] [4151] [4152] [4153] [4154] [4155] [4156] [4157] [4158] [4159] [4160] [4161] [4162] [4163] [4164] [4165] [4166] [4167] [4168] [4169] [4170] [4171] [4172] [4173] [4174] [4175] [4176] [4177] [4178] [4179] [4180] [4181] [4182] [4183] [4184] [4185] [4186] [4187] [4188] [4189] [4190] [4191] [4192] [4193] [4194] [4195] [4196] [4197] [4198] [4199] [4200] [4201] [4202] [4203] [4204] [4205] [4206] [4207] [4208] [4209] [4210] [4211] [4212] [4213] [4214] [4215] [4216] [4217] [4218] [4219] [4220] [4221] [4222] [4223] [4224] [4225] [4226] [4227] [4228] [4229] [4230] [4231] [4232] [4233] [4234] [4235] [4236] [4237] [4238] [4239] [4240] [4241] [4242] [4243] [4244] [4245] [4246] [4247] [4248] [4249] [4250] [4251] [4252] [4253] [4254] [4255] [4256] [4257] [4258] [4259] [4260] [4261] [4262] [4263] [4264] [4265] [4266] [4267] [4268] [4269] [4270] [4271] [4272] [4273] [4274] [4275] [4276] [4277] [4278] [4279] [4280] [4281] [4282] [4283] [4284] [4285] [4286] [4287] [4288] [4289] [4290] [4291] [4292] [4293] [4294] [4295] [4296] [4297] [4298] [4299] [4300] [4301] [4302] [4303] [4304] [4305] [4306] [4307] [4308] [4309] [4310] [4311] [4312] [4313] [4314] [4315] [4316] [4317] [4318] [4319] [4320] [4321] [4322] [4323] [4324] [4325] [4326] [4327] [4328] [4329] [4330] [4331] [4332] [4333] [4334] [4335] [4336] [4337] [4338] [4339] [4340] [4341] [4342] [4343] [4344] [4345] [4346] [4347] [4348] [4349] [4350] [4351] [4352] [4353] [4354] [4355] [4356] [4357] [4358] [4359] [4360] [4361] [4362] [4363] [4364] [4365] [4366] [4367] [4368] [4369] [4370] [4371] [4372] [4373] [4374] [4375] [4376] [4377] [4378] [4379] [4380] [4381] [4382] [4383] [4384] [4385] [4386] [4387] [4388] [4389] [4390] [4391] [4392] [4393] [4394] [4395] [4396] [4397] [4398] [4399] [4400] [4401] [4402] [4403] [4404] [4405] [4406] [4407] [4408] [4409] [4410] [4411] [4412] [4413] [4414] [4415] [4416] [4417] [4418] [4419] [4420] [4421] [4422] [4423] [4424] [4425] [4426] [4427] [4428] [4429] [4430] [4431] [4432] [4433] [4434] [4435] [4436] [4437] [4438] [4439] [4440] [4441] [4442] [4443] [4444] [4445] [4446] [4447] [4448] [4449] [4450] [4451] [4452] [4453] [4454] [4455] [4456] [4457] [4458] [4459] [4460] [4461] [4462] [4463] [4464] [4465] [4466] [4467] [4468] [4469] [4470] [4471] [4472] [4473] [4474] [4475] [4476] [4477] [4478] [4479] [4480] [4481] [4482] [4483] [4484] [4485] [4486] [4487] [4488] [4489] [4490] [4491] [4492] [4493] [4494] [4495] [4496] [4497] [4498] [4499] [4500] [4501] [4502] [4503] [4504] [4505] [4506] [4507] [4508] [4509] [4510] [4511] [4512] [4513] [4514] [4515] [4516] [4517] [4518] [4519] [4520] [4521] [4522] [4523] [4524] [4525] [4526] [4527] [4528] [4529] [4530] [4531] [4532] [4533] [4534] [4535] [4536] [4537] [4538] [4539] [4540] [4541] [4542] [4543] [4544] [4545] [4546] [4547] [4548] [4549] [4550] [4551] [4552] [4553] [4554] [4555] [4556] [4557] [4558] [4559] [4560] [4561] [4562] [4563] [4564] [4565] [4566] [4567] [4568] [4569] [4570] [4571] [4572] [4573] [4574] [4575] [4576] [4577] [4578] [4579] [4580] [4581] [4582] [4583] [4584] [4585] [4586] [4587] [4588] [4589] [4590] [4591] [4592] [4593] [4594] [4595] [4596] [4597] [4598] [4599] [4600] [4601] [4602] [4603] [4604] [4605] [4606] [4607] [4608] [4609] [4610] [4611] [4612] [4613] [4614] [4615] [4616] [4617] [4618] [4619] [4620] [4621] [4622] [4623] [4624] [4625] [4626] [4627] [4628] [4629] [4630] [4631] [4632] [4633] [4634] [4635] [4636] [4637] [4638] [4639] [4640] [4641] [4642] [4643] [4644] [4645] [4646] [4647] [4648] [4649] [4650] [4651] [4652] [4653] [4654] [4655] [4656] [4657] [4658] [4659] [4660] [4661] [4662] [4663] [4664] [4665] [4666] [4667] [4668] [4669] [4670] [4671] [4672] [4673] [4674] [4675] [4676] [4677] [4678] [4679] [4680] [4681] [4682] [4683] [4684] [4685] [4686] [4687] [4688] [4689] [4690] [4691] [4692] [4693] [4694] [4695] [4696] [4697] [4698] [4699] [4700] [4701] [4702] [4703] [4704] [4705] [4706] [4707] [4708] [4709] [4710] [4711] [4712] [4713] [4714] [4715] [4716] [4717] [4718] [4719] [4720] [4721] [4722] [4723] [4724] [4725] [4726] [4727] [4728] [4729] [4730] [4731] [4732] [4733] [4734] [4735] [4736] [4737] [4738] [4739] [4740] [4741] [4742] [4743] [4744] [4745] [4746] [4747] [4748] [4749] [4750] [4751] [4752] [4753] [4754] [4755] [4756] [4757] [4758] [4759] [4760] [4761] [4762] [4763] [4764] [4765] [4766] [4767] [4768] [4769] [4770] [4771] [4772] [4773] [4774] [4775] [4776] [4777] [4778] [4779] [4780] [4781] [4782] [4783] [4784] [4785] [4786] [4787] [4788] [4789] [4790] [4791] [4792] [4793] [4794] [4795] [4796] [4797] [4798] [4799] [4800] [4801] [4802] [4803] [4804] [4805] [4806] [4807] [4808] [4809] [4810] [4811] [4812] [4813] [4814] [4815] [4816] [4817] [4818] [4819] [4820] [4821] [4822] [4823] [4824] [4825] [4826] [4827] [4828] [4829] [4830] [4831] [4832] [4833] [4834] [4835] [4836] [4837] [4838] [4839] [4840] [4841] [4842] [4843] [4844] [4845] [4846] [4847] [4848] [4849] [4850] [4851] [4852] [4853] [4854] [4855] [4856] [4857] [4858] [4859] [4860] [4861] [4862] [4863] [4864] [4865] [4866] [4867] [4868] [4869] [4870] [4871] [4872] [4873] [4874] [4875] [4876] [4877] [4878] [4879] [4880] [4881] [4882] [4883] [4884] [4885] [4886] [4887] [4888] [4889] [4890] [4891] [4892] [4893] [4894] [4895] [4896] [4897] [4898] [4899] [4900] [4901] [4902] [4903] [4904] [4905] [4906] [4907] [4908] [4909] [4910] [4911] [4912] [4913] [4914] [4915] [4916] [4917] [4918] [4919] [4920] [4921] [4922] [4923] [4924] [4925] [4926] [4927] [4928] [4929] [4930] [4931] [4932] [4933] [4934] [4935] [4936] [4937] [4938] [4939] [4940] [4941] [4942] [4943] [4944] [4945] [4946] [4947] [4948] [4949] [4950] [4951] [4952] [4953] [4954] [4955] [4956] [4957] [4958] [4959] [4960] [4961] [4962] [4963] [4964] [4965] [4966] [4967] [4968] [4969] [4970] [4971] [4972] [4973] [4974] [4975] [4976] [4977] [4978] [4979] [4980] [4981] [4982] [4983] [4984] [4985] [4986] [4987] [4988] [4989] [4990] [4991] [4992] [4993] [4994] [4995] [4996] [4997] [4998] [4999] [5000] [5001] [5002] [5003] [5004] [5005] [5006] [5007] [5008] [5009] [5010] [5011] [5012] [5013] [5014] [5015] [5016] [5017] [5018] [5019] [5020] [5021] [5022] [5023] [5024] [5025] [5026] [5027] [5028] [5029] [5030] [5031] [5032] [5033] [5034] [5035] [5036] [5037] [5038] [5039] [5040] [5041] [5042] [5043] [5044] [5045] [5046] [5047] [5048] [5049] [5050] [5051] [5052] [5053] [5054] [5055] [5056] [5057] [5058] [5059] [5060] [5061] [5062] [5063] [5064] [5065] [5066] [5067] [5068] [5069] [5070] [5071] [5072] [5073] [5074] [5075] [5076] [5077] [5078] [5079] [5080] [5081] [5082] [5083] [5084] [5085] [5086] [5087] [5088] [5089] [5090] [5091] [5092] [5093] [5094] [5095] [5096] [5097] [5098] [5099] [5100] [5101] [5102] [5103] [5104] [5105] [5106] [5107] [5108] [5109] [5110] [5111] [5112] [5113] [5114] [5115] [5116] [5117] [5118] [5119] [5120] [5121] [5122] [5123] [5124] [5125] [5126] [5127] [5128] [5129] [5130] [5131] [5132] [5133] [5134] [5135] [5136] [5137] [5138] [5139] [5140] [5141] [5142] [5143] [5144] [5145] [5146] [5147] [5148] [5149] [5150] [5151] [5152] [5153] [5154] [5155] [5156] [5157] [5158] [5159] [5160] [5161] [5162] [5163] [5164] [5165] [5166] [5167] [5168] [5169] [5170] [5171] [5172] [5173] [5174] [5175] [5176] [5177] [5178] [5179] [5180] [5181] [5182] [5183] [5184] [5185] [5186] [5187] [5188] [5189] [5190] [5191] [5192] [5193] [5194] [5195] [5196] [5197] [5198] [5199] [5200] [5201] [5202] [5203] [5204] [5205] [5206] [5207] [5208] [5209] [5210] [5211] [5212] [5213] [5214] [5215] [5216] [5217] [5218] [5219] [5220] [5221] [5222] [5223] [5224] [5225] [5226] [5227] [5228] [5229] [5230] [5231] [5232] [5233] [5234] [5235] [5236] [5237] [5238] [5239] [5240] [5241] [5242] [5243] [5244] [5245] [5246] [5247] [5248] [5249] [5250] [5251] [5252] [5253] [5254] [5255] [5256] [5257] [5258] [5259] [5260] [5261] [5262] [5263] [5264] [5265] [5266] [5267] [5268] [5269] [5270] [5271] [5272] [5273] [5274] [5275] [5276] [5277] [5278] [5279] [5280] [5281] [5282] [5283] [5284] [5285] [5286] [5287] [5288] [5289] [5290] [5291] [5292] [5293] [5294] [5295] [5296] [5297] [5298] [5299] [5300] [5301] [5302] [5303] [5304] [5305] [5306] [5307] [5308] [5309] [5310] [5311] [5312] [5313] [5314] [5315] [5316] [5317] [5318] [5319] [5320] [5321] [5322] [5323] [5324] [5325] [5326] [5327] [5328] [5329] [5330] [5331] [5332] [5333] [5334] [5335] [5336] [5337] [5338] [5339] [5340] [5341] [5342] [5343] [5344] [5345] [5346] [5347] [5348] [5349] [5350] [5351] [5352] [5353] [5354] [5355] [5356] [5357] [5358] [5359] [5360] [5361] [5362] [5363] [5364] [5365] [5366] [5367] [5368] [5369] [5370] [5371] [5372] [5373] [5374] [5375] [5376] [5377] [5378] [5379] [5380] [5381] [5382] [5383] [5384] [5385] [5386] [5387] [5388] [5389] [5390] [5391] [5392] [5393] [5394] [5395] [5396] [5397] [5398] [5399] [5400] [5401] [5402] [5403] [5404] [5405] [5406] [5407] [5408] [5409] [5410] [5411] [5412] [5413] [5414] [5415] [5416] [5417] [5418] [5419] [5420] [5421] [5422] [5423] [5424] [5425] [5426] [5427] [5428] [5429] [5430] [5431] [5432] [5433] [5434] [5435] [5436] [5437] [5438] [5439] [5440] [5441] [5442] [5443] [5444] [5445] [5446] [5447] [5448] [5449] [5450] [5451] [5452] [5453] [5454] [5455] [5456] [5457] [5458] [5459] [5460] [5461] [5462] [5463] [5464] [5465] [5466] [5467] [5468] [5469] [5470] [5471] [5472] [5473] [5474] [5475] [5476] [5477] [5478] [5479] [5480] [5481] [5482]
/*****************************************************************************/ /* SSI.c This module implements a full multi-threaded, AST-driven, asynchronous HTML Server Side Includes pre-processor. The AST-driven nature makes the code a little more difficult to follow, but creates a powerful, event-driven, multi-threaded server. All of the necessary functions implementing this module are designed to be non-blocking. The SSI functionality in the WASD server includes the basics of NCSA SSI, extensions similar to Apache's XSSI (eXtended SSI), as well as WASD, OSU and VMS idiosyncratic elements. The module has become an even bigger nightmare as the compatibility between WASD, OSU and "vanilla" SSIs has attempted to be implemented and maintained. It eventually provides a reasonably capable (but somewhat overly complex) server-side document engine including: o inclusion of server information (e.g. date, name, software) o inclusion of document information (e.g. path, last modification) o inclusion of custom layout directory listings o innocuous DCL command execution (e.g. "SHOW TIME") o privileged DCL command execution (e.g. DCL procedures) o expression evaluation and user variable assignment o flow control in document output (e.g. if-then-else) o document access counter An SSI statement CAN BE SPLIT OVER MULTIPLE LINES, up to a maximum dictated by the size of the statement buffer. Occasionally this buffer is exhausted resulting in a buffer overflow error. It can be set by the document using the <!--#config buffersize="" --> directive. The default is the longest record length of the file but can be set to anything up to 32767. This directive should be the first in the SSI document. The '\' character may be used to escape characters reserved or forbidden inside of SSI directives. Here are some common examples '\#', '\"', '\{', '\}'. The flow control statements and associated evaluations were designed for simplicity, both for the author (who didn't want to spend a huge amount of time building a complex expression parser, etc.) and also for the server, with a simple scheme presumably resulting in a lower execution overhead. It is assumed most documents will have simple internal flows so this shouldn't be an issue. If very complex decision making is required it is probably better exported to a legitimate script. To ease the clutter often present in XSSI documents (lots of SSI statements all over the place) the <!--#ssi ... --> statement allows multiple SSI statements to be grouped where the Ellipsis is shown. See the example below. Of course only SSI statements may be included in this structure! No plain HTML! The leading '#' of new SSI statement ends any previous statement. The "-->" of the "<!--#ssi" ends the last statement. Other SSI documents may be #included with full SSI processing up to a nested limit imposed by SSI_MAX_DEPTH. Files #included are automatically encapsulated in <pre></pre> tags and HTML- escaped if not "text/html" content-type (i.e. if they are "text/plain" content-type). They can be forced to be directly included by including a 'par="text/html"' parameter. Conversly, "text/html" files can be forced to be included as plain-text using the 'par="text/plain"' parameter. By default the #include directive reports an error if it cannot access the file for any reason. The optional 'fmt="?"' tag modifies this behaviour so that processing will continue. For example, '#include virtual="test.txt" fmt="?"' would not report an error or stop processing if TEST.TXT did not exist. Format ('fmt=""') specifications for time values follow those allowed by strftime(). If none is specified it defaults to a fairly standard looking VMS-style time. Documents containing *PRIVILEGED* statements are only allowed in o documents owned by SYSTEM ([1,4]) and that are NOT world writable! o documents that have been mapped by the "set PRIVSSI" setting o the privileged statement is found in a "set SSI=exec=<string>" OSU-COMPATIBLILITY ------------------ The SSI engine can process OSU-specific directives. In addition, the server can be configured to tranparently process OSU .HTMLX SSI files. This eases any transition between the two. There may be minor incompatibilities reflecting the UNIX-style file syntax used by OSU and the DECnet environment of OSU scripts. Also see comments in section "Evaluations and Flow Control" below. The following lists the WASD configuration requirements. WASD_CONFIG_CONFIG: [AddType] .HTMLX text/x-shtml - OSU SSI HTML Note that the content description must contain the string "OSU" to activate some compliancy behaviours. WASD_CONFIG_MAP: redirect /*.*.htmlx /*.htmlx?httpd=ssi&__part=* This provides a mechanism for the OSU part-document facility. (Yes, the "__part" has two leading underscores!) DIRECTIVES ---------- <!--## --> comment (not in resulting document!) <!--#" "--> synonym for echo <!--#accesses [ordinal] [since=""] [timefmt=""] --> accesses of document <!--#begin label --> OSU-compliant, #include delimiter <!--#config compliance="" --> backward-compatibility integer <!--#config errmsg="" --> SSI error message header <!--#config sizefmt="" --> set file size output format <!--#config timefmt="" --> set default time format <!--#config OSU="1|0" --> "force" to be processed as OSU (or not) <!--#config trace="1|0" --> set trace statements on or off <!--#config verify="1|0" --> OSU-compliant, include commented tags <!--#dcl --> synonym for #exec <!--#dir file="" [par=""] --> directory (index of) file spec <!--#dir virtual="" [par=""] --> directory (index of) virtual spec <!--#index file="" [par=""] --> synonym for the above <!--#index virtual="" [par=""] --> synonym for the above <!--#echo accesses --> OSU-compliant, accesses of document <!--#echo accesses_ordinal --> OSU-compliant, accesses of document <!--#echo created[=fmt] --> current document creation date/time <!--#echo date_local[=fmt] --> current local date/time <!--#echo date_gmt[=fmt] --> current GMT date/time <!--#echo document_name --> current document VMS file path <!--#echo document_uri - -> current document URL path <!--#echo file_name --> current document VMS file path <!--#echo getenv="" --> OSU-compliant, symbol or logical <!--#echo header="" --> append this string to response header <!--#echo hw_name --> OSU-compliant, hardware name <!--#echo last_modified[=fmt] --> current document modified date <!--#echo query_string_unescaped --> same as CGI query_string <!--#echo server_name --> OSU-compliant, HTTPd server name <!--#echo server_version --> OSU-compliant, HTTPd server software <!--#echo vms_version --> OSU-compliant, VMS version <!--#echo var="" --> synonym for any of the above echos <!--#echo <any-CGI-variable> --> any CGI scripting variable name <!--#end label --> OSU-compliant, #include delimiter NOTE: the "#dcl" and "#exec" statements are identical <!--#exec cgi="" --> execute CGI/NPH script absorbing header <!--#exec dir="" [par=""] --> DIRECTORY file-spec [qualifiers] <!--#exec vdir="" [par=""] --> DIRECTORY virtual-file-spec [qualifiers] <!--#exec show="" --> SHOW command <!--#exec say="" --> WRITE SYS$OUTPUT 'anything' <!--#exec script="" --> execute CGI/NPH script absorbing header <!--#exec exec="" --> *PRIVILEGED* execute any DCL command <!--#exec file="" [par=""] --> *PRIVILEGED* execute command procedure <!--#exec run="" [par=""] --> *PRIVILEGED* execute image <!--#exec virtual="" [par=""] --> *PRIVILEGED* execute command procedure <!--#exec vrun="" [par=""] --> *PRIVILEGED* execute virtual image <!--#exit --> stop processing the current file <!--#fcreated file="" [fmt=""] --> specified file creation date/time <!--#fcreated virtual="" [fmt=""] --> specified URL document creation date <!--#flastmod file="" [fmt=""] --> specified file last modified date/time <!--#flastmod virtual="" [fmt=""] --> specified URL document last modified <!--#fsize file="" --> specified file size (bytes, Kb, Mb) <!--#fsize virtual="" --> specified URL document size <!--#include file="" [type=""] [fmt=""] --> include file's contents <!--#include virtual="" [type=""] [fmt=""] --> include URL document contents <!--#include file="" [part=""] --> OSU-compliant, include only part <!--#include virtual="" [part=""] --> OSU-compliant, include only part <!--#if value="" [...] --> flow control <!--#orif value="" [...] --> flow control <!--#elif value="" [...] --> flow control <!--#else --> flow control <!--#endif --> flow control <!--#modified file="" [fmt=""] --> get the RDT of this file <!--#modified virtual="" [fmt=""] --> get the RDT of this file <!--#modified if-modified-since --> 304 response returned if not modified <!--#modified last-modified --> "Last-Modified:" response field added <!--#modified expires="" --> "Expires:" response field added <!--#printenv --> prints all assigned variables <!--#set var= value="" --> sets the variable name to value <!--#ssi [statement|...] --> multiple SSI statements <!--#stop --> stop processing the virtual document VARIABLES --------- There are server and user variables. User variables may be assigned using the "#set" statement. The values of both may be substituted into any tag value associated with any statement. User variable names may comprise alphanumeric and underscore characters, and are not case sensitive. Variable substitution is indicated by delimitting the variable name with '{' and '}'. The variable name may be followed by one or two comma separated numbers which serve as an extraction start index and count. A single number results in the variable text from zero for the specified. Two numbers specify a start index and count. <!--#set var=EXAMPLE1 value="This is an example!" --> <!--#set var=EXAMPLE2 value={EXAMPLE1,0,10} --> <!--#set var=EXAMPLE3 value="{EXAMPLE2}other example ..." --> <!--#set var=EXAMPLE4 value="{EXAMPLE1,10}other example!" --> <!--#echo "<p>" var={EXAMPLE3} "<br>" var={EXAMPLE4} --> The output from the "#echo" would be <p>This is another example ... <br>This is another example! All variables available, server, CGI and user, may be displayed using the #printenv directive. All variables are global in scope. This means the same set of variables are visible to all #included SSI documents, and user variables set by #included documents are seen by their parents, etc. Server variables containing document related information generally refer to the top-level, or parent document, with these local exceptions. COMPLIANCE ......... the level of compliance that should be applied DOCUMENT_DEPTH ..... level of "#include"d documents (root file is 1) PARENT_FILE_NAME ... name of file "#include"ing this current file THIS_FILE_NAME ..... name of the current file GENERAL GUIDELINES: To use the extended functionality of WASD variables leave the quotes off of 'var=' tags wherever possible. When they are present the SSI engine attempts to retain compatibility with other, general SSI implmentations, and the OSU cersion, and this may cause unexpected interactions or results. Another option is to use the 'value=' tag. EVALUATIONS and FLOW CONTROL ---------------------------- Flow control statements work in the same fashion to other implementations. The possible WASD idiosyncrasy is "#orif" which, in the absence of a true expression parser, allows multiple conditions to execute a single block of statements. <!--#if value="" [...=""] --> <!--#orif value="" [...=""] --> <!--#elif value="" [...=""] --> <!--#else --> <!--#endif --> The evalution of a flow control decision in an "#if", "#orif" or "#elif" is based on one or more tests against one or more variables. <!--#if value="" --> if string not empty, or not numeric 0 <!--#if value="" eqs="" --> string equal to <!--#if value="" srch="" --> string search (* and % wildcards) <!--#if value="" lt="" --> numeric less than <!--#if value="" eq="" --> numeric equal to <!--#if value="" gt="" --> numeric greater than If a single string/variable is supplied in the test then if it is numeric and non-zero then the test is true, if a string and non-empty it is true. Other states are false. The "eqs=" test does a case-insensitive string compare of the supplied string and the "value=" string. The "srch=" test searches "value=" with the supplied string, which can contain the wildcards "*" matching multiple characters and "%" matching any one character. The "lt=", "eq=" and "gt=" convert the parameters to integers and do an arithetic compare. Multiple comparisons, even with multiple variables, may occur in the one decision statement, these will act as a logical AND. Logical ORs may be created using "#orif" statements. Any evaluation tag preceded by an exclamation point will have it's result logically negated. For example, <!--#if value="0" !eq="0" --> returns a BOOL value of false. Note that the OSU-compliant #begin and #end directives are implemented as flow control statements. They are only permitted in SSI files identified as OSU-compliant (i.e. .HTMLX) and cannot be used in standard WASD SSI. SIMPLE EXAMPLE -------------- This example demonstrates some of the salient features of WASD's XSSI. <!--##config trace=1 --> <html> <!--#ssi #set var=HOUR value={DATE_LOCAL,12,2} #if value={HOUR} lt=12 #set var=GREETING value="Good morning" #elif value={HOUR} lt=19 #set var=GREETING value="Good afternoon" #else #set var=GREETING value="Good evening" #endif --> <head> <title><!--#echo value={GREETING} --> <!--#echo value="{REMOTE_HOST}!" --></title> </head> <body> <h2>Simple XSSI Demonstration</h2> <!--#echo value={GREETING} --> <!--#echo value={REMOTE_HOST} -->, the time here is <!--#echo value={DATE_LOCAL,12,5} -->. </body> </html> FILE EXIST/NOT-EXIST -------------------- Generally, errors encountered halt document processing and report the error. For some common circumstances, in particular the existance or not of a particular file, may require an alternative action. For file activities (e.g. #include, #flastmod, #created, #fsize) the optional 'fmt=""' tag provides some measure of control on error behaviour. If the format string begins with a '?' files not found are not reported as errors and processing continues. Other file systems errors, such as directory not found, syntax errors, etc., are always reported. Every time a file is accessed (e.g. #include, #flastmod) the server variable THE_FILE_NAME gets set to that name if successful, or reset to empty if unsuccessful. This variable can be checked to determine success or otherwise. For #included files, the 'fmt="?"' just suppresses an error report, if the file exists then it is included. For #modified file specifications use 'fmt="?"' to suppress error reporting on evaluation of files that may exist but are not mandatory. For file statistic directives (e.g. #flastmod, #fcreated, #fsize) the 'fmt="?"' tag completely suppresses all output as well as error reporting. This can be used to check for the existance of a file. For example if the file TEST.TXT exists in the following example the variable THE_FILE_NAME would contain the full file name, if it does not exist it would be empty, and the code example would behave accordingly. <!--#fcreated virtual="TEST.TXT" fmt="?" --> <!--#if var={THE_FILE_NAME} eqs="" --> File does not exist! <!--#else --> File exists! <!--#endif --> "IF-MODIFIED-SINCE:" -------------------- SSI documents generally contain dynamic elements, that is those that may change with each access to the document (e.g. current date/time). This makes evaluation of any document modification date difficult (i.e. too much for the author to bother coding ;^) and so by default no "Last-Modified:" information is supplied against an SSI document. The potential efficiencies of having document last-modified timestamps so that if-modified-since requests can generate not-modified responses are significant in a range of cases (basically where any dynamic document elements relate only to the file(s) of which the document is composed). With the significant CPU overheads of processing SSI documents this approach has the potential to return substantial benefits for suitable documents and sites. WASD allows the document author to determine whether or not a last-modified header field should be generated for a particular document and which contributing document(s) should be used to determine it. This is done using the #modified directive. Where multiple source documents (files) are employed each can be checked using the virtual= or file= tags of the #modified directive, the most recently modified will be used to determine if it's been modified and also to generate the last-modified timestamp. By default the #modified directive gets the last-modified date of the current document (i.e. when no tags follow the directive, as in the first line of the example below). The "if-modified-since" tag compares the (most recent) revision date/time of the file(s) checked using the virtual= or file= tags, with any "If-Modified-Since:" date/time supplied with the request. If the file(s) revision date/time is the same or older than the request's then a not-modified (304 status) header is generated and sent to the client and document processing stops. If more recent an appropriate "Last-Modified:" header field is added to the document and it continues to be processed. If a request has a "Pragma: no-cache" field (as with Navigator's "reload" function) the document is always generated (this is consistent with general WASD behaviour). The following (rather exagerated) example illustrates the essential features. <!--#ssi #modified #modified virtual="/web/common/header.shtml" #modified virtual="header.html" fmt="?" #modified virtual="index.html" fmt="?" #modified virtual="footer.html" fmt="?" #modified virtual="/web/common/footer.shtml" #modified if-modified-since --> This construct should be placed at the very beginning of the SSI document, and certainly before there is any chance of output being sent to the browser. Once output to the client has occured there can be no change to the response header information (not unreasonably). The "last-modified" tag generates a "Last-Modified:" field using the (most recent) revision date/time of the file(s) and adds it to the response header. It is not necessary to do this if the "if-not-modified" tag has been used as this implicitly generates one, but there may be circumstances where a last-modified field might be desired even though the document is always generated. The 'expires=""' tag (which is bundled in with the #modified directive) takes a string literal and generates then adds an "Expires:" response header field. The string literal should be a legitimate RFC-1123 date string. This can be used for pre-expiring documents (so they are always reloaded), set it to a date in the not-too-distant past (e.g. expires="Fri, 13 Jan 1978 14:00:00 GMT"). To try really hard to pre-expire a document (some browsers seem reluctant to do this) set the expires to 0 or a negative number (e.g. expires="0" or expires="-1") and SSI will add cache-control headers in an attempt to invalidate any caching. Of course it could also be used for setting the legitimate future expiry of documents. VERSION HISTORY --------------- 30-JUN-2016 MGD SsiGetServerVar() add ERROR_URI 26-JAN-2016 MGD user variables use Dict..() functionality 12-FEB-2014 MGD bugfix; module accounting 05-NOV-2013 MGD SsiEnd() detect and report non-SSI problem encountered 17-JUN-2010 MGD SsiDoSet() and SsiGetTagValue() allow '$' in variable names 23-MAR-2010 MGD bugfix; SsiDoDcl() report cgi=/script= query string as error 03-NOV-2006 MGD bugfix; SsiEnd() propagate included document user variables back into parent document to ensure they remain *global* 31-OCT-2006 MGD appropriate buffer spaces to SSI_STRING_SIZE 11-JUN-2005 MGD <!--#echo header="<string>" --> to response header, <!--#modified expires="0" --> to pre-expire 10-JAN-2004 MGD <!--#exec script="/cgi-bin/blah" -->, SSI #exec (#dcl) directives can be allowed on per-path basis using SET ssi=exec=<string> (e.g. 'ssi=exec=say,show'), SSI can now be enabled on a per-path basis using 'ssi=exec=#' 07-JUL-2003 MGD cache loading from SSI using network output 05-OCT-2002 MGD refine VMS security profile usage 10-JUL-2002 MGD provide real path to directory listing 27-APR-2002 MGD make SYSPRV enabled ASTs asynchronous, bugfix; SsiAccessesClose() now synchronous using SYSPRV (J.Begg demonstrated it was required for alarm-free closure) 04-AUG-2001 MGD support module WATCHing, bugfix; ++AccountingPtr->DoSsiCount; 24-JUN-2001 MGD add setlocale() as suggested by jfp@altavista.com 30-DEC-2000 MGD rework for FILE.C getting file contents in-memory (allows CACHE.C to cache the file, the test-bench showing a 100% improvement in throughput for simple SSIs!) 01-SEP-2000 MGD add optional, local path authorization (for calls from the likes of SSI.C) 18-MAY-2000 MGD bugfix; prevent '#exec blah="@file.com"' 09-APR-2000 MGD modify ERROR_... special variables in line with ERROR.C 04-MAR-2000 MGD use FaolToNet(), et.al., add the [NO]PRIVSSI setting 31-DEC-1999 MGD support ODS-2 and ODS-5 using ODS module, allow for carriage-control in FIX/UDF format files 12-NOV-1999 MGD bugfix; brain-dead code in SsiOsuFileName() 10-OCT-1999 MGD support "scrunched" :^) SSI files, support OSU-compatible directives, bugfix; sys$parse() NAM$M_NOCONCEAL for search lists, bugfix; no 'nxt' after 'FileXabPro'! 05-AUG-1999 MGD add #include content="text/..." whatever 11-APR-1999 MGD variables to ease the creation of virtual documents 08-APR-1999 MGD last-modified/not-modified functionality, 'fmt="?"' tag on #include and other file related directives 28-MAR-1999 MGD 'FileRab.rab$w_usz' now dynamic ('FileXabFhc.xab$w_lrl'), 'StatementBuffer' dynamic (<!--#config buffersize="" -->) 11-MAR-1999 MGD user variables have global scope (nested HTML documents), bugfix; GetFileSpec() wasn't using generic GetTagValue() 07-NOV-1998 MGD WATCH facility 18-OCT-1998 MGD error report support 19-SEP-1998 MGD improve granularity of file open, connect, close, ACP 19-JUL-1998 MGD bugfix; GetTagValue() must return number of characters! bugfix; MapUrl_Map() pass 'rqptr' for conditionals to work 13-APR-1998 MGD add <!--#ssi ... --> block statement, variable assignment and flow-control eXtensions 23-JAN-1998 MGD <!--#echo server_gmt --> and ordinal accesses 25-OCT-1997 MGD <!--#echo http_accept_charset, http_host, http_forward --> 17-AUG-1997 MGD message database, SYSUAF-authenticated users security-profile, additional <!--#echo ... --> (just for the hang of it :^) 27-FEB-1997 MGD delete on close for "temporary" files 01-FEB-1997 MGD HTTPd version 4 (also changed module name to SSI.c); extensive rework of some functions; statements now allowed to span multiple lines 04-JUN-1996 MGD bugfix; SsiFileDetails() error reporting 01-DEC-1995 MGD new for HTTPd version 3 */ /*****************************************************************************/ #ifdef WASD_VMS_V7 #undef _VMS__V6__SOURCE #define _VMS__V6__SOURCE #undef __VMS_VER #undef __VMS_VER #undef __CRTL_VER #define __CRTL_VER 70000000 #endif /* standard C header files */ #include <ctype.h> #include <locale.h> #include <stdio.h> #include <string.h> #include <stdarg.h> /* VMS related header files */ #include <descrip.h> #include <libdef.h> #include <prvdef.h> #include <rms.h> #include <ssdef.h> #include <stsdef.h> /* application-related header files */ #include "wasd.h" #define WASD_MODULE "SSI" #define SSI_DEFAULT_TIME_FORMAT "%Od-%b-%Y %T" /* a little VMSish! */ #define SSI_DEFAULT_SIZE_FORMAT "abbrev" #define SSI_DEFAULT_COMPLIANCE_LEVEL 603 /* current SSI implemented v6.0.3 */ #define SSI_OSU_COMPLIANCE_LEVEL 603 /* OSU HTMLX supported at v6.0.3 */ #define SSI_VAR_FMT_COMPLIANCE_LEVEL 603 /* #echo var="name=fmt" at v6.0.3 */ #define FILE_LAST_MODIFIED 1 #define FILE_FCREATED 2 #define FILE_FLASTMOD 3 #define FILE_FSIZE 4 #define SSI_STATE_DEFAULT 1 #define SSI_STATE_IF 2 #define SSI_STATE_ELIF 3 #define SSI_STATE_ELSE 4 /* for OSU #include part compliance */ #define SSI_STATE_BEGIN 5 #define SSI_MAX_DEPTH 5 /******************/ /* global storage */ /******************/ int SsiComplianceLevel, SsiSizeMax; /********************/ /* external storage */ /********************/ #ifdef DBUG extern BOOL Debug; #else #define Debug 0 #endif extern int DclCgiVariablePrefixLength; extern int ToLowerCase[], ToUpperCase[]; extern unsigned long SysPrvMask[]; extern char ConfigContentTypeSsi[], ErrorSanityCheck[], HttpProtocol[], SoftwareID[], TimeGmtString[]; extern ACCOUNTING_STRUCT *AccountingPtr; extern CONFIG_STRUCT Config; extern MSG_STRUCT Msgs; extern SYS_INFO SysInfo; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* Open the specified file. If there is a problem opening it then just return the status, do not report an error or anything. This is used to determine whether the file exists, as when attempting to open one of multiple, possible home pages. As pre-processed HTML files are by definition dynamic, no check of any "If-Modified-Since:" request field date/time is made, and no "Last-Modified:" field is included in any response header. When successfully opened and connected generate an HTTP header, if required. Once open and connected the pre-processing becomes AST-driven. */ SsiBegin (REQUEST_STRUCT *rqptr) { int status; char *cptr, *sptr, *zptr; FILE_CONTENT *fcptr; SSI_TASK *tkptr, *ParentDocumentTaskPtr, *RootDocumentTaskPtr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiBegin() !&F !&X !UL !UL !UL", &SsiBegin, rqptr->FileContentPtr->ContentPtr, rqptr->FileContentPtr->ContentSize, rqptr->FileContentPtr->ContentLength, rqptr->FileContentPtr->ContentRemaining); if (!rqptr->SsiTaskPtr) rqptr->SsiTaskPtr = NULL; /* take control of the file contents structure */ fcptr = rqptr->FileContentPtr; rqptr->FileContentPtr = NULL; if (ERROR_REPORTED (rqptr)) { /* previous error, cause threaded processing to unravel */ SysDclAst (fcptr->NextTaskFunction, rqptr); return; } if (!(Config.cfSsi.Enabled || (rqptr->rqPathSet.SsiExecPtr && rqptr->rqPathSet.SsiExecPtr[0] == '#'))) { rqptr->rqResponse.HttpStatus = 403; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_DISABLED), FI_LI); SysDclAst (fcptr->NextTaskFunction, rqptr); return; } /* SSI documents can call other SSI documents ... infinite recursion! */ if (LIST_GET_COUNT (&rqptr->SsiTaskList) > SSI_MAX_DEPTH) { rqptr->rqResponse.HttpStatus = 403; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_SSI_RECURSION), FI_LI); SysDclAst (fcptr->NextTaskFunction, rqptr); return; } if (WATCHING (rqptr, WATCH_RESPONSE)) WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "SSI !AZ !UL bytes", fcptr->FileName, fcptr->ContentLength); if (rqptr->SsiTaskPtr) { /* this is an included SSI document */ ParentDocumentTaskPtr = rqptr->SsiTaskPtr; RootDocumentTaskPtr = rqptr->SsiTaskPtr->RootDocumentTaskPtr; } else { /* no parent SSI document, will generate new CGI/user variables */ ParentDocumentTaskPtr = RootDocumentTaskPtr = NULL; } /* set up the task structure (possibly multiple concurrent) */ if (rqptr->SsiTaskPtr && LIST_HAS_NEXT (rqptr->SsiTaskPtr)) { rqptr->SsiTaskPtr = tkptr = LIST_GET_NEXT (rqptr->SsiTaskPtr); memset (LIST_GET_DATA(tkptr), 0, sizeof(SSI_TASK)-sizeof(LIST_ENTRY)); } else { rqptr->SsiTaskPtr = tkptr = VmGetHeap (rqptr, sizeof(SSI_TASK)); ListAddTail (&rqptr->SsiTaskList, tkptr, LIST_ENTRY_TYPE_SSI); } OdsStructInit (&tkptr->DetailsOds, true); /* set up read to SSI parse the file's body */ tkptr->FileContentPtr = fcptr; tkptr->ParsePtr = fcptr->ContentPtr; /* buffer the request's escape-HTML flag */ tkptr->OutputBufferEscapeHtml = rqptr->NetWriteEscapeHtml; tkptr->SizeFmtPtr = SSI_DEFAULT_SIZE_FORMAT; tkptr->TimeFmtPtr = SSI_DEFAULT_TIME_FORMAT; if (RootDocumentTaskPtr) tkptr->RootDocumentTaskPtr = RootDocumentTaskPtr; else tkptr->RootDocumentTaskPtr = tkptr; tkptr->ParentDocumentTaskPtr = ParentDocumentTaskPtr; if (tkptr->ParentDocumentTaskPtr) { /* inherit these from the parent document */ tkptr->ComplianceLevel = ParentDocumentTaskPtr->ComplianceLevel; tkptr->OsuCompliant = ParentDocumentTaskPtr->OsuCompliant; tkptr->TagVerify = ParentDocumentTaskPtr->TagVerify; tkptr->TraceState = ParentDocumentTaskPtr->TraceState; tkptr->CgiBufferPtr = tkptr->ParentDocumentTaskPtr->CgiBufferPtr; tkptr->UserDictPtr = DictDuplicate (rqptr, tkptr->ParentDocumentTaskPtr->UserDictPtr); } else { /* top-level document */ rqptr->AccountingDone++; InstanceGblSecIncrLong (&AccountingPtr->DoSsiCount); /* set the default compliance level */ if (!SsiComplianceLevel) { /* first time though check for this kludge */ if (!(cptr = getenv ("WASD_SSI_COMPLIANCE")) && !(cptr = getenv ("HTTPD$SSI_COMPLIANCE"))) SsiComplianceLevel = SSI_DEFAULT_COMPLIANCE_LEVEL; else SsiComplianceLevel = atoi(cptr); } tkptr->ComplianceLevel = SsiComplianceLevel; if (tkptr->ComplianceLevel >= SSI_OSU_COMPLIANCE_LEVEL) { /* OSU SSI? (it is if the content description contains "OSU"!) */ if (rqptr->rqContentInfo.DescriptionPtr && strstr (rqptr->rqContentInfo.DescriptionPtr, "OSU")) tkptr->OsuCompliant = true; } /* generate top-level document CGI variables to be used thoughout */ if (VMSnok (CgiGenerateVariables (rqptr, CGI_VARIABLE_STREAM))) { SsiEnd (rqptr); return; } tkptr->CgiBufferPtr = rqptr->rqCgi.BufferPtr; rqptr->rqCgi.BufferPtr = NULL; tkptr->UserDictPtr = DictCreate (rqptr, -1); } if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z !UL !&B", fcptr->FileName, tkptr->ComplianceLevel, tkptr->OsuCompliant); if (tkptr->OsuCompliant && !tkptr->ParentDocumentTaskPtr && SsiGetVar (rqptr, "FORM___PART", NULL, true)) { /*****************/ /* OSU path part */ /*****************/ /* part was supplied with an OSU "/path/file.part.type" */ cptr = SsiGetVar (rqptr, "FORM___PART", NULL, false); zptr = (sptr = tkptr->CurrentPart) + sizeof(tkptr->CurrentPart); while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_GENERAL_OVERFLOW), FI_LI); SsiEnd (rqptr); return; } *sptr = '\0'; tkptr->FlowControlState[0] = SSI_STATE_DEFAULT; tkptr->FlowControlHasExecuted[0] = tkptr->FlowControlIsExecuting[0] = false; } else if (tkptr->OsuCompliant && tkptr->ParentDocumentTaskPtr && tkptr->ParentDocumentTaskPtr->IncludePart[0]) { /********************/ /* OSU include part */ /********************/ /* if the parent had an include part then can't begin straight off */ strcpy (tkptr->CurrentPart, tkptr->ParentDocumentTaskPtr->IncludePart); tkptr->FlowControlState[0] = SSI_STATE_DEFAULT; tkptr->FlowControlHasExecuted[0] = tkptr->FlowControlIsExecuting[0] = false; } else { /************/ /* WASD SSI */ /************/ /* no flow control structure always outputs */ tkptr->FlowControlState[0] = SSI_STATE_DEFAULT; tkptr->FlowControlHasExecuted[0] = false; tkptr->FlowControlIsExecuting[0] = true; } tkptr->FlowControlIndex = 0; if (!rqptr->rqResponse.HeaderGenerated) { rqptr->rqResponse.PreExpired = PRE_EXPIRE_SSI; ResponseHeader200 (rqptr, "text/html", NULL); if (rqptr->rqHeader.Method == HTTP_METHOD_HEAD) { SsiEnd (rqptr); return; } } /* network writes are checked for success, fudge the first one! */ rqptr->NetIoPtr->WriteStatus = SS$_NORMAL; if (*tkptr->ParsePtr) { tkptr->LineNumber++; if (tkptr->TraceState) SsiTraceLine (rqptr, tkptr->ParsePtr); } if (rqptr->rqPathSet.CacheSSI && !rqptr->rqCache.LoadCheck) { /* SSI output to be cached (using network output) */ rqptr->rqCache.LoadFromNet = CacheLoadBegin (rqptr, (int)rqptr->rqResponse.ContentLength64, rqptr->rqResponse.ContentTypePtr); } SsiParse (rqptr); } /*****************************************************************************/ /* End of SSI interpretation, successful or otherwise. */ SsiEnd (REQUEST_STRUCT *rqptr) { int HttpStatus; char *cptr, *sptr, *linptr, *modptr; DICT_STRUCT *dicptr; SSI_TASK *tkptr; REQUEST_AST NextTaskFunction; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiEnd() !&F !&A", &SsiEnd, rqptr->SsiTaskPtr->FileContentPtr->NextTaskFunction); tkptr = rqptr->SsiTaskPtr; if (ERROR_REPORTED (rqptr)) { if (rqptr->rqResponse.HeaderSent) { if (cptr = rqptr->rqResponse.ErrorReportPtr) { HttpStatus = rqptr->rqResponse.HttpStatus; /* oh boy, what a kludge :-{ */ if (modptr = strstr (cptr, "name=\"module\" content=\"")) modptr += 23; else modptr = "?"; if (linptr = strstr (cptr, "name=\"line\" content=\"")) linptr += 21; else linptr = "?"; for (sptr = modptr; *sptr && *sptr != '\"'; sptr++); if (*sptr) *sptr = '\0'; for (sptr = linptr; *sptr && *sptr != '\"'; sptr++); if (*sptr) *sptr = '\0'; SsiProblem (rqptr, "!AZ <!!-- status: !UL module: !AZ line: !AZ --> ", HttpStatusCodeText(HttpStatus), HttpStatus, modptr, linptr, FI_LI); } else SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_ERROR), FI_LI); } } OdsParseRelease (&tkptr->DetailsOds); NextTaskFunction = tkptr->FileContentPtr->NextTaskFunction; /* free the memory allocated to contain the file contents */ if (tkptr->FileContentPtr) { VmFreeFromHeap (rqptr, tkptr->FileContentPtr, FI_LI); tkptr->FileContentPtr = NULL; } /* restore previous SSI task (if any) */ rqptr->SsiTaskPtr = LIST_GET_PREV (tkptr); if (rqptr->SsiTaskPtr) { /* ensure global user variables are kept that way (thanks JPP) */ dicptr = rqptr->SsiTaskPtr->UserDictPtr; rqptr->SsiTaskPtr->UserDictPtr = tkptr->UserDictPtr; DictDestroy (dicptr); /* propagate any requirement to stop processing */ rqptr->SsiTaskPtr->StopProcessing = tkptr->StopProcessing; } /* restore the escape-HTML flag */ rqptr->NetWriteEscapeHtml = tkptr->OutputBufferEscapeHtml; /* pass control to the next task function */ SysDclAst (NextTaskFunction, rqptr); } /*****************************************************************************/ /* 'tkptr->ParsePtr' points to the currently parsed-up-to position in the buffer. Continue parsing the buffer looking for preprocessor statements. */ SsiParse (REQUEST_STRUCT *rqptr) { BOOL BeginSsi; int status; char ch; char *cptr; SSI_TASK *tkptr; REQUEST_AST AstFunction; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiParse() !&F", &SsiParse); tkptr = rqptr->SsiTaskPtr; if (rqptr->RequestState >= REQUEST_STATE_ABORT) { tkptr->StopProcessing = true; SsiEnd (rqptr); return; } if (ERROR_REPORTED (rqptr) || tkptr->StopProcessing) { tkptr->StopProcessing = true; SsiEnd (rqptr); return; } /* ensure the escape-HTML flag is "off" from any previous processing */ rqptr->NetWriteEscapeHtml = tkptr->TraceState; if (tkptr->TerminatedPtr) { /* For convenient (read lazy) and possibly more efficient programming each SSI directive has it's first character pointed to and it's next-after-last character overwritten with a null. This allows it to be parsed as a null-terminated string without needing to be copied to some other buffer, or the like. If this has happened then 'TerminatedPtr' points to the in-memory location and 'TerminatedChar' contains the character that was overwritten. Just restore the in-memory file to it's original condition. Must be restored *after* any asynchronous processing (e.g. #including a file) hence it being done here before we begin any next directive. */ *tkptr->TerminatedPtr = tkptr->TerminatedChar; tkptr->TerminatedPtr = NULL; } if (tkptr->StatementEndPtr) { /* adjust parse pointer for any statement previously processed */ cptr = tkptr->ParsePtr = tkptr->StatementEndPtr; if (cptr[0] == '-' && cptr[1] == '-' && cptr[2] == '>') { if (tkptr->InsideSsiStatement) tkptr->InsideSsiStatement = false; tkptr->ParsePtr += 3; } } BeginSsi = false; tkptr->StatementBeginPtr = tkptr->StatementEndPtr = NULL; /** if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchDataDump (tkptr->ParsePtr, strlen(tkptr->ParsePtr)); **/ if (tkptr->InsideSsiStatement) { /**********************************/ /* inside an "<!--#ssi" statement */ /**********************************/ cptr = tkptr->ParsePtr; while (*cptr) { while (*cptr && *cptr != '#' && *cptr != '-') { if (*cptr != '\n') { /* step over any escape character */ if (*cptr == '\\') cptr++; if (*cptr) cptr++; continue; } cptr++; tkptr->LineNumber++; if (tkptr->TraceState && *cptr) SsiTraceLine (rqptr, cptr); tkptr->SuppressLine = false; } if (!*cptr) break; if (*cptr == '#') { tkptr->StatementBeginPtr = cptr; break; } if (cptr[0] == '-' && cptr[1] == '-' && cptr[2] == '>') { tkptr->StatementEndPtr = cptr; break; } cptr++; } if (*cptr == '#') { cptr++; /* allow for "##" comments */ if (*cptr == '#') cptr++; while (*cptr) { while (*cptr && *cptr != '#' && *cptr != '-') { if (*cptr != '\n') { /* step over any escape character */ if (*cptr == '\\') cptr++; if (*cptr) cptr++; continue; } cptr++; tkptr->LineNumber++; if (tkptr->TraceState && *cptr) SsiTraceLine (rqptr, cptr); tkptr->SuppressLine = false; } if (!*cptr) break; if (*cptr == '#') { tkptr->StatementEndPtr = cptr; break; } if (cptr[0] == '-' && cptr[1] == '-' && cptr[2] == '>') { tkptr->StatementEndPtr = cptr; break; } cptr++; } } } else { /*****************************/ /* delimit the "<!--#...-->" */ /*****************************/ cptr = tkptr->ParsePtr; /* look for the start of a statement */ while (*cptr) { while (*cptr && *cptr != '<') { if (*cptr++ != '\n') continue; tkptr->LineNumber++; if (tkptr->TraceState && *cptr) SsiTraceLine (rqptr, cptr); if (tkptr->SuppressLine) { tkptr->ParsePtr = cptr; tkptr->SuppressLine = false; } } if (!*cptr) break; if (cptr[0] == '<' && cptr[1] == '!' && cptr[2] == '-' && cptr[3] == '-' && cptr[4] == '#') { tkptr->StatementBeginPtr = cptr; break; } cptr++; } if (tkptr->StatementBeginPtr && !strsame (tkptr->StatementBeginPtr, "<!--#ssi", 8)) { /* look for the end of a statement */ while (*cptr) { while (*cptr && *cptr != '-') { if (*cptr++ != '\n') continue; tkptr->LineNumber++; if (tkptr->TraceState && *cptr) SsiTraceLine (rqptr, cptr); if (tkptr->SuppressLine) { tkptr->ParsePtr = cptr; tkptr->SuppressLine = false; } } if (!*cptr) break; if (cptr[0] == '-' && cptr[1] == '-' && cptr[2] == '>') { tkptr->StatementEndPtr = cptr; break; } cptr++; } } } if (tkptr->StatementBeginPtr) { /*****************/ /* SSI statement */ /*****************/ if (!tkptr->StatementEndPtr) tkptr->StatementEndPtr = cptr; cptr = tkptr->StatementBeginPtr + 4; tkptr->StatementLineNumber = tkptr->LineNumber; /** if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchDataDump (cptr, strlen(cptr)); **/ ch = TOLO(cptr[1]); if (ch == 'b' && strsame (cptr, "#begin", 6) || ch == 'i' && strsame (cptr, "#if", 3) || ch == 'e' && strsame (cptr, "#elif", 5) || ch == 'e' && strsame (cptr, "#else", 5) || /* MUST precede #end for the obvious reason */ ch == 'e' && strsame (cptr, "#endif", 6) || ch == 'e' && strsame (cptr, "#end", 4) || ch == 'o' && strsame (cptr, "#orif", 5) || ch == 's' && strsame (cptr, "#set", 4) || /* #trace is kept for backward compatibility */ ch == 't' && strsame (cptr, "#trace", 6) || SAME2(cptr,'##')) { /* flow control line, etc., suppress resultant "blank" lines */ tkptr->SuppressLine = true; } else if (ch == 's' && strsame (cptr, "#ssi", 4)) { /* found the start of an "<!--#ssi" statement */ if (tkptr->InsideSsiStatement) { /* already inside an "<!--#ssi" statement! */ SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_STATEMENT), FI_LI); SsiEnd (rqptr); return; } BeginSsi = tkptr->InsideSsiStatement = tkptr->SuppressLine = true; } } if (!tkptr->StatementBeginPtr) { if (tkptr->FlowControlIndex) { /* hmmm, got to end of file and there's still nested control */ tkptr->StatementLineNumber = tkptr->LineNumber - 1; SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_FLOW_CONTROL), FI_LI); SsiEnd (rqptr); return; } } else cptr = tkptr->StatementBeginPtr; if (cptr > tkptr->ParsePtr) { /* output chars preceding the statement, or at the end-of-document */ if ((BeginSsi || !tkptr->InsideSsiStatement) && tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex]) { if (!tkptr->StatementBeginPtr) AstFunction = &SsiEnd; else if (BeginSsi) AstFunction = &SsiParse; else AstFunction = &SsiStatement; NetWriteBuffered (rqptr, AstFunction, tkptr->ParsePtr, cptr - tkptr->ParsePtr); tkptr->TraceOutput = true; if (BeginSsi) tkptr->StatementEndPtr = tkptr->StatementBeginPtr + 8; return; } } if (!tkptr->StatementBeginPtr) SsiEnd (rqptr); else if (BeginSsi) { tkptr->StatementEndPtr = tkptr->StatementBeginPtr + 8; SsiParse (rqptr); } else SsiStatement (rqptr); } /*****************************************************************************/ /* 'tkptr->ParsePtr' points to the start of a pre-processor statement. Parse that statement and execute it, or provide an error message. */ SsiStatement (REQUEST_STRUCT *rqptr) { int status; char ch; char *cptr; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiStatement() !&F", &SsiStatement); tkptr = rqptr->SsiTaskPtr; /* terminate the statement */ tkptr->TerminatedChar = *tkptr->StatementEndPtr; *(tkptr->TerminatedPtr = tkptr->StatementEndPtr) = '\0'; /* step over any leading "<!--" */ if (*tkptr->StatementBeginPtr == '<') tkptr->StatementBeginPtr += 4; /* trim trailing spaces */ cptr = tkptr->StatementEndPtr; if (cptr > tkptr->StatementBeginPtr) { cptr--; while (cptr > tkptr->StatementBeginPtr && isspace(*cptr)) cptr--; if (!isspace(*cptr)) cptr++; *tkptr->TerminatedPtr = tkptr->TerminatedChar; tkptr->TerminatedChar = *cptr; *(tkptr->TerminatedPtr = cptr) = '\0'; } tkptr->StatementLength = cptr - tkptr->StatementBeginPtr; if (WATCHMOD (rqptr, WATCH_MOD_SSI)) { WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "flow:!UL line:!UL length:!UL", tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex], tkptr->LineNumber, tkptr->TerminatedPtr - tkptr->StatementBeginPtr); WatchDataDump (tkptr->StatementBeginPtr, tkptr->TerminatedPtr - tkptr->StatementBeginPtr); } if (tkptr->TagVerify) { /* OSU-compliant, trace each statement inside HTML comments */ status = FaoToNet (rqptr, "<!!-- !#AZ -->", tkptr->StatementLength, tkptr->StatementBeginPtr); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); tkptr->TraceOutput = true; } cptr = tkptr->StatementBeginPtr; ch = TOLO(cptr[1]); if (tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex]) { /*************************************************/ /* flow-control is currently allowing processing */ /*************************************************/ if (SAME2(cptr,'##')) SsiDoComment (rqptr); else if (SAME2(cptr,'#"')) SsiDoEcho (rqptr); else if (ch == 'b' && strsame (cptr, "#begin", 6)) SsiDoBegin (rqptr); else if (ch == 'a' && strsame (cptr, "#accesses", 9)) SsiDoAccesses (rqptr); else if (ch == 'c' && strsame (cptr, "#config", 7)) SsiDoConfig (rqptr); else if (ch == 'd' && strsame (cptr, "#dcl", 4)) SsiDoDcl (rqptr); else if (ch == 'd' && strsame (cptr, "#dir", 4)) SsiDoDir (rqptr); else if (ch == 'e' && strsame (cptr, "#echo", 5)) SsiDoEcho (rqptr); else if (ch == 'e' && strsame (cptr, "#elif", 5)) SsiDoElif (rqptr); else if (ch == 'e' && strsame (cptr, "#else", 5)) SsiDoElse (rqptr); else /* MUST precede #end for the obvious reason */ if (ch == 'e' && strsame (cptr, "#endif", 6)) SsiDoEndif (rqptr); else if (ch == 'e' && strsame (cptr, "#end", 4)) SsiDoEnd (rqptr); else if (ch == 'e' && strsame (cptr, "#exec", 5)) SsiDoDcl (rqptr); else if (ch == 'e' && strsame (cptr, "#exit", 5)) SsiDoExit (rqptr); else if (ch == 'f' && strsame (cptr, "#fcreated", 9)) SsiDoFCreated (rqptr); else if (ch == 'f' && strsame (cptr, "#flastmod", 9)) SsiDoFLastMod (rqptr); else if (ch == 'f' && strsame (cptr, "#fsize", 6)) SsiDoFSize (rqptr); else if (ch == 'i' && strsame (cptr, "#if", 3)) SsiDoIf (rqptr); else if (ch == 'i' && strsame (cptr, "#include", 8)) SsiDoInclude (rqptr); else if (ch == 'i' && strsame (cptr, "#index", 6)) SsiDoDir (rqptr); else if (ch == 'm' && strsame (cptr, "#modified", 9)) SsiDoModified (rqptr); else if (ch == 'o' && strsame (cptr, "#orif", 5)) SsiDoOrif (rqptr); else if (ch == 'p' && strsame (cptr, "#printenv", 9)) SsiDoPrintEnv (rqptr); else /* not needed with WASD v7.2ff, backward compatibility, do nothing! */ if (ch == 's' && strsame (cptr, "#scrunch", 8)) status = 0; else if (ch == 's' && strsame (cptr, "#set", 4)) SsiDoSet (rqptr); else if (ch == 's' && strsame (cptr, "#stop", 5)) SsiDoStop (rqptr); else /* #trace is kept for backward compatibility */ if (ch == 't' && strsame (cptr, "#trace", 6)) SsiDoTrace (rqptr); else { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_STATEMENT_UNKNOWN), FI_LI); SsiEnd (rqptr); return; } } else { /********************************************/ /* flow control is currently not processing */ /********************************************/ /* looking only for the following */ if (ch == 'b' && strsame (cptr, "#begin", 6)) SsiDoBegin (rqptr); else if (ch == 'i' && strsame (cptr, "#if", 3)) SsiDoIf (rqptr); else if (ch == 'o' && strsame (cptr, "#orif", 5)) SsiDoOrif (rqptr); else if (ch == 'e' && strsame (cptr, "#elif", 5)) SsiDoElif (rqptr); else if (ch == 'e' && strsame (cptr, "#else", 5)) SsiDoElse (rqptr); else if (ch == 'e' && strsame (cptr, "#endif", 6)) SsiDoEndif (rqptr); else SysDclAst (&SsiParse, rqptr); } } /*****************************************************************************/ /* Number of times this document has been accessed. Working with AST-driven code is sometimes like participating in a lonnngg, sloowww root-canal. */ SsiDoAccesses (REQUEST_STRUCT *rqptr) { int status; char *dptr; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoAccesses()"); tkptr = rqptr->SsiTaskPtr; if (tkptr->TraceState) SsiTraceStatement (rqptr); if (!Config.cfSsi.AccessesEnabled) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_ACCESS_DISABLED), FI_LI); SsiEnd (rqptr); return; } tkptr->AccessSinceText[0] = tkptr->FormatString[0] = '\0'; tkptr->AccessOrdinal = false; dptr = rqptr->SsiTaskPtr->StatementBeginPtr; while (*dptr && !isspace(*dptr)) dptr++; while (*dptr) { if (rqptr->SsiTaskPtr->StopProcessing) break; while (*dptr && isspace(*dptr)) dptr++; if (!*dptr) break; if (strsame (dptr, "ORDINAL", 7)) { dptr += 7; tkptr->AccessOrdinal = true; } else if (strsame (dptr, "SINCE=", 6)) dptr += SsiGetTagValue (rqptr, dptr, tkptr->AccessSinceText, sizeof(tkptr->AccessSinceText)); else if (strsame (dptr, "TIMEFMT=", 8)) dptr += SsiGetTagValue (rqptr, dptr, tkptr->FormatString, sizeof(tkptr->FormatString)); else { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_TAG_UNKNOWN), FI_LI); SsiEnd (rqptr); return; } } if (rqptr->SsiTaskPtr->StopProcessing) { SsiEnd (rqptr); return; } /* if the file's access count has been previously determined */ if (tkptr->AccessCount) SsiAccessesOutput (rqptr); else SsiAccessesOpen (rqptr); } /*****************************************************************************/ /* AST-driven file create/open. These functions do not need to be ODS-5 aware as they do not use a NAM block. */ SsiAccessesOpen (REQUEST_STRUCT *rqptr) { int status; char *dptr, *cptr, *sptr, *zptr; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ tkptr = rqptr->SsiTaskPtr; if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiAccessesOpen() !&F", &SsiAccessesOpen); zptr = (sptr = tkptr->ScratchFileName) + sizeof(tkptr->ScratchFileName); for (cptr = tkptr->FileContentPtr->FileName; *cptr && *cptr != ';' && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = '$'; if (sptr >= zptr) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_GENERAL_OVERFLOW), FI_LI); SsiEnd (rqptr); return; } *sptr = '\0'; if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z", tkptr->ScratchFileName); tkptr->AccessFab = cc$rms_fab; tkptr->AccessFab.fab$l_ctx = rqptr; tkptr->AccessFab.fab$l_fna = tkptr->ScratchFileName; tkptr->AccessFab.fab$b_fns = sptr-tkptr->ScratchFileName; tkptr->AccessFab.fab$l_fop = FAB$M_CIF | FAB$M_SQO; tkptr->AccessFab.fab$b_fac = FAB$M_GET | FAB$M_PUT | FAB$M_UPD; tkptr->AccessFab.fab$w_mrs = sizeof(unsigned long); tkptr->AccessFab.fab$b_shr = FAB$M_SHRGET | FAB$M_SHRPUT | FAB$M_SHRUPD; tkptr->AccessFab.fab$b_org = FAB$C_SEQ; tkptr->AccessFab.fab$b_rfm = FAB$C_FIX; /* initialize the date extended attribute block */ tkptr->AccessFab.fab$l_xab = &tkptr->AccessXabDat; tkptr->AccessXabDat = cc$rms_xabdat; tkptr->AccessXabDat.xab$l_nxt = &tkptr->AccessXabPro; /* initialize the protection extended attribute block */ tkptr->AccessXabPro = cc$rms_xabpro; tkptr->AccessXabPro.xab$w_pro = 0xaa00; /* W:RE,G:RE,O:RWED,S:RWED */ /* "temporary" file, automatic delete on closing it */ if (rqptr->DeleteOnClose) tkptr->AccessFab.fab$l_fop |= FAB$M_DLT; /* turn on SYSPRV to allow access to the counter file */ sys$setprv (1, &SysPrvMask, 0, 0); tkptr->AccessFab.fab$l_fop |= FAB$M_ASY; status = sys$create (&tkptr->AccessFab, &SsiAccessesOpenAst, &SsiAccessesOpenAst); sys$setprv (0, &SysPrvMask, 0, 0); } /*****************************************************************************/ /* AST called from SsiDoAccesses() when asynchronous RAB connect completes. */ SsiAccessesOpenAst (struct FAB *FabPtr) { int status; REQUEST_STRUCT *rqptr; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ rqptr = FabPtr->fab$l_ctx; if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiAccessesOpenAst() !&F sts:!&S stv:!&S", &SsiAccessesOpenAst, FabPtr->fab$l_sts, FabPtr->fab$l_stv); #if WATCH_MOD HttpdCheckPriv (FI_LI); #endif /* WATCH_MOD */ tkptr = rqptr->SsiTaskPtr; if (VMSnok (FabPtr->fab$l_sts) && FabPtr->fab$l_stv) status = FabPtr->fab$l_stv; else status = FabPtr->fab$l_sts; if (VMSnok (status)) { SsiProblem (rqptr, "!&m", status, FI_LI); tkptr->AccessAstFunction = &SsiEnd; SsiAccessesClose (&tkptr->AccessFab); return; } tkptr->AccessRab = cc$rms_rab; tkptr->AccessRab.rab$l_ctx = rqptr; tkptr->AccessRab.rab$l_fab = &tkptr->AccessFab; tkptr->AccessRab.rab$l_rbf = tkptr->AccessCount; tkptr->AccessRab.rab$w_rsz = sizeof(tkptr->AccessCount); status = sys$connect (&tkptr->AccessRab, &SsiAccessesConnectAst, &SsiAccessesConnectAst); } /*****************************************************************************/ /* AST called from SsiDoAccesses() when asynchronous RAB connect completes. */ SsiAccessesConnectAst (struct RAB *RabPtr) { int status; REQUEST_STRUCT *rqptr; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ rqptr = RabPtr->rab$l_ctx; if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiAccessesConnectAst() !&F sts:!&S stv:!&S", &SsiAccessesConnectAst, RabPtr->rab$l_sts, RabPtr->rab$l_stv); tkptr = rqptr->SsiTaskPtr; if (VMSnok (RabPtr->rab$l_sts) && RabPtr->rab$l_stv) status = RabPtr->rab$l_stv; else status = RabPtr->rab$l_sts; if (VMSnok (status)) { SsiProblem (rqptr, "!&m", status, FI_LI); tkptr->AccessAstFunction = &SsiEnd; SsiAccessesClose (&tkptr->AccessFab); return; } if (RabPtr->rab$l_sts == RMS$_CREATED) { /***************************************/ /* count file did not previously exist */ /***************************************/ tkptr->AccessCount = 1; tkptr->AccessRab.rab$l_rbf = &tkptr->AccessCount; tkptr->AccessRab.rab$w_rsz = sizeof(tkptr->AccessCount); tkptr->AccessRab.rab$l_rop |= RAB$M_ASY; sys$put (&tkptr->AccessRab, &SsiAccessesUpdateAst, &SsiAccessesUpdateAst); return; } else { /**********************/ /* count file existed */ /**********************/ tkptr->AccessRab.rab$l_ubf = &tkptr->AccessCount; tkptr->AccessRab.rab$w_usz = sizeof(tkptr->AccessCount); tkptr->AccessRab.rab$l_rop |= RAB$M_ASY; status = sys$get (&tkptr->AccessRab, &SsiAccessesGetAst, &SsiAccessesGetAst); return; } } /*****************************************************************************/ /* Count file existed and the sys$get() of the previous count from SsiAccessesConnectAst() has generated an AST to this function. */ SsiAccessesGetAst (struct RAB *RabPtr) { int status; REQUEST_STRUCT *rqptr; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ rqptr = RabPtr->rab$l_ctx; tkptr = rqptr->SsiTaskPtr; if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiAccessesGetAst() !&F, sts:!&S stv:!&S !UL", &SsiAccessesGetAst, RabPtr->rab$l_sts, RabPtr->rab$l_stv, tkptr->AccessCount); if (VMSnok (RabPtr->rab$l_sts) && RabPtr->rab$l_stv) status = RabPtr->rab$l_stv; else status = RabPtr->rab$l_sts; if (VMSnok (status)) { if (status != RMS$_EOF) { SsiProblem (rqptr, "!&m", status, FI_LI); tkptr->AccessAstFunction = &SsiEnd; SsiAccessesClose (&tkptr->AccessFab); return; } /* EOF indicates the initial write failed (e.g. disk quota) */ tkptr->AccessCount = 1; tkptr->AccessRab.rab$l_rbf = &tkptr->AccessCount; tkptr->AccessRab.rab$w_rsz = sizeof(tkptr->AccessCount); tkptr->AccessRab.rab$l_rop |= RAB$M_ASY; sys$put (&tkptr->AccessRab, &SsiAccessesUpdateAst, &SsiAccessesUpdateAst); return; } tkptr->AccessCount++; tkptr->AccessRab.rab$l_rbf = &tkptr->AccessCount; tkptr->AccessRab.rab$w_rsz = sizeof(tkptr->AccessCount); tkptr->AccessRab.rab$l_rop |= RAB$M_ASY; status = sys$update (&tkptr->AccessRab, &SsiAccessesUpdateAst, &SsiAccessesUpdateAst); } /*****************************************************************************/ /* Called as an AST from either the sys$put() in SsiAccessesConnectAst() or the sys$update() in SsiAccessesgetAst(). Check the status of the update to the access count in the file and if OK generated the required SSI page output. */ SsiAccessesUpdateAst (struct RAB *RabPtr) { int status; REQUEST_STRUCT *rqptr; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ rqptr = RabPtr->rab$l_ctx; tkptr = rqptr->SsiTaskPtr; if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiAccessesUpdateAst() !&F sts:!&S stv:!&S !UL", &SsiAccessesUpdateAst, RabPtr->rab$l_sts, RabPtr->rab$l_stv, tkptr->AccessCount); if (VMSnok (RabPtr->rab$l_sts) && RabPtr->rab$l_stv) status = RabPtr->rab$l_stv; else status = RabPtr->rab$l_sts; if (VMSnok (status)) { SsiProblem (rqptr, "!&m", status, FI_LI); tkptr->AccessAstFunction = &SsiEnd; SsiAccessesClose (&tkptr->AccessFab); return; } SsiAccessesOutput (rqptr); } /*****************************************************************************/ /* This function can be called from a number of places to close an open access count file. Prior to calling this function 'tkptr->AccessAstFunction' should be set to the function required to be executed after the close is complete. */ SsiAccessesClose (struct FAB *FabPtr) { int status; REQUEST_STRUCT *rqptr; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ rqptr = FabPtr->fab$l_ctx; if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiAccessesClose() !&F", &SsiAccessesClose); tkptr = rqptr->SsiTaskPtr; if (tkptr->AccessFab.fab$w_ifi) { if (rqptr->DeleteOnClose) tkptr->AccessFab.fab$l_fop |= FAB$M_DLT; /* use SYSPRV to ensure alarm-free close of file */ sys$setprv (1, &SysPrvMask, 0, 0); tkptr->AccessFab.fab$l_fop |= FAB$M_ASY; status = sys$close (&tkptr->AccessFab, &SsiAccessesFileClosed, &SsiAccessesFileClosed); sys$setprv (0, &SysPrvMask, 0, 0); return; } SysDclAst (tkptr->AccessAstFunction, rqptr); } /*****************************************************************************/ /* May be called directly or as an AST from sys$close() above. */ SsiAccessesFileClosed (struct FAB *FabPtr) { REQUEST_STRUCT *rqptr; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ rqptr = FabPtr->fab$l_ctx; tkptr = rqptr->SsiTaskPtr; if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiAccessesFileClosed() !&F sts:!&S stv:!&S", &SsiAccessesFileClosed, FabPtr->fab$l_sts, FabPtr->fab$l_stv); #if WATCH_MOD HttpdCheckPriv (FI_LI); #endif /* WATCH_MOD */ SysDclAst (tkptr->AccessAstFunction, rqptr); } /*****************************************************************************/ /* Generate and output the formatted access count. */ SsiAccessesOutput (REQUEST_STRUCT *rqptr) { static char *Ordination [] = { "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" }; int status, TensUnits; unsigned short Length; char *OrdinalPtr; char TimeString [256]; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiAccessesOutput() !8XL", &SsiAccessesOutput); tkptr = rqptr->SsiTaskPtr; if (tkptr->AccessOrdinal) { TensUnits = tkptr->AccessCount % 100; if (TensUnits >= 11 && TensUnits <= 19) OrdinalPtr = "th"; else OrdinalPtr = Ordination[TensUnits%10]; } else OrdinalPtr = ""; if (tkptr->AccessSinceText[0]) { if (!SsiTimeString (rqptr, &tkptr->AccessXabDat.xab$q_cdt, tkptr->FormatString, TimeString, sizeof(TimeString))) { SsiEnd (rqptr); return; } } else TimeString[0] = '\0'; status = FaoToNet (rqptr, "!&L!AZ!AZ!AZ", tkptr->AccessCount, OrdinalPtr, tkptr->AccessSinceText, TimeString); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); tkptr->TraceOutput = true; if (tkptr->AccessFab.fab$w_ifi) { /* we've been access the on-disk file */ tkptr->AccessAstFunction = &SsiParse; SsiAccessesClose (&tkptr->AccessFab); } else { /* already had the access count, just continue */ SysDclAst (&SsiParse, rqptr); } } /*****************************************************************************/ /* OSU-compliant, marking the beginning of an #includable "part". */ SsiDoBegin (REQUEST_STRUCT *rqptr) { char String [SSI_INCLUDE_PART_MAX+1]; char *cptr, *dptr, *sptr, *zptr; SSI_TASK *tkptr, *ParentDocumentTaskPtr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoBegin()"); tkptr = rqptr->SsiTaskPtr; if (!tkptr->OsuCompliant || !tkptr->CurrentPart[0]) { /* declare an AST to execute the next function */ SysDclAst (&SsiParse, rqptr); return; } if (tkptr->TraceState) SsiTraceStatement (rqptr); dptr = tkptr->StatementBeginPtr; while (*dptr && !isspace(*dptr)) dptr++; while (*dptr && isspace(*dptr)) dptr++; while (*dptr) { while (*dptr && (*dptr == '\"' || isspace(*dptr))) dptr++; if (!*dptr) break; zptr = (sptr = String) + sizeof(String); while (*dptr && *dptr != '\"' && !isspace(*dptr) && sptr < zptr) *sptr++ = *dptr++; if (sptr >= zptr) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_GENERAL_OVERFLOW), FI_LI); SsiEnd (rqptr); return; } *sptr = '\0'; if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z !&Z", String, tkptr->CurrentPart); if (strcmp (String, tkptr->CurrentPart)) continue; tkptr->SuppressLine = true; tkptr->FlowControlIndex++; if (tkptr->FlowControlIndex > SSI_MAX_FLOW_CONTROL) { tkptr->FlowControlIndex = 0; SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_FLOW_CONTROL), FI_LI); SsiEnd (rqptr); return; } /* beginning of the #included part */ tkptr->FlowControlState[tkptr->FlowControlIndex] = SSI_STATE_BEGIN; tkptr->FlowControlHasExecuted[tkptr->FlowControlIndex] = tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex] = true; break; } /* declare an AST to execute the next function */ SysDclAst (&SsiParse, rqptr); } /*****************************************************************************/ /* Comment hidden from the final document because it's inside an SSI statement! */ SsiDoComment (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoComment()"); if (rqptr->SsiTaskPtr->TraceState) SsiTraceStatement (rqptr); rqptr->SsiTaskPtr->SuppressLine = true; SysDclAst (&SsiParse, rqptr); } /*****************************************************************************/ /* Config sets the default behaviour for a specific action for the rest of the document. */ SsiDoConfig (REQUEST_STRUCT *rqptr) { int status, BufferSize; char *cptr, *dptr; char TagValue [256]; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoConfig() !&Z", rqptr->SsiTaskPtr->StatementBeginPtr); tkptr = rqptr->SsiTaskPtr; if (tkptr->TraceState) SsiTraceStatement (rqptr); dptr = tkptr->StatementBeginPtr; while (*dptr && !isspace(*dptr)) dptr++; while (*dptr && isspace(*dptr)) dptr++; if (strsame (dptr, "BUFFERSIZE", 6)) { /* not needed with WASD v7.2ff, backward compatibility, do nothing! */ dptr += SsiGetTagValue (rqptr, dptr, TagValue, sizeof(TagValue)); } else if (strsame (dptr, "COMPLIANCE", 10)) { dptr += SsiGetTagValue (rqptr, dptr, TagValue, sizeof(TagValue)); tkptr->ComplianceLevel = atoi(TagValue); } else if (strsame (dptr, "ERRMSG", 6)) { dptr += SsiGetTagValue (rqptr, dptr, TagValue, sizeof(TagValue)); cptr = VmGetHeap (rqptr, strlen(TagValue)+1); strcpy (tkptr->ErrMsgPtr = cptr, TagValue); } else if (strsame (dptr, "SIZEFMT", 7)) { dptr += SsiGetTagValue (rqptr, dptr, TagValue, sizeof(TagValue)); if (!(strsame (TagValue, "abbrev", -1) || strsame (TagValue, "bytes", -1) || strsame (TagValue, "blocks", -1))) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_TAG_VALUE), FI_LI); SsiEnd (rqptr); return; } cptr = VmGetHeap (rqptr, strlen(TagValue)+1); strcpy (tkptr->SizeFmtPtr = cptr, TagValue); } else if (strsame (dptr, "OSU", 3)) { dptr += SsiGetTagValue (rqptr, dptr, TagValue, sizeof(TagValue)); if (TOUP(TagValue[0]) == 'Y' || TagValue[0] == '1') tkptr->OsuCompliant = true; else if (TOUP(TagValue[0]) == 'N' || TagValue[0] == '0') tkptr->OsuCompliant = false; else { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_TAG_VALUE), FI_LI); SsiEnd (rqptr); return; } } else if (strsame (dptr, "TIMEFMT", 7)) { dptr += SsiGetTagValue (rqptr, dptr, TagValue, sizeof(TagValue)); cptr = VmGetHeap (rqptr, strlen(TagValue)+1); strcpy (tkptr->TimeFmtPtr = cptr, TagValue); } else if (strsame (dptr, "TRACE", 5)) { dptr += SsiGetTagValue (rqptr, dptr, TagValue, sizeof(TagValue)); if ((TOUP(TagValue[0]) == 'O' && TOUP(TagValue[1]) == 'N') || TOUP(TagValue[0]) == 'Y' || TagValue[0] == '1') { if (!rqptr->SsiTaskPtr->TraceState) { NetWriteBuffered (rqptr, NULL, "<pre>", 5); rqptr->NetWriteEscapeHtml = true; tkptr->TraceState = true; } SsiTraceStatement (rqptr); } else if ((TOUP(TagValue[0]) == 'O' && TOUP(TagValue[1]) == 'F') || TOUP(TagValue[0]) == 'N' || TagValue[0] == '0') { if (rqptr->SsiTaskPtr->TraceState) { SsiTraceStatement (rqptr); rqptr->NetWriteEscapeHtml = false; NetWriteBuffered (rqptr, NULL, "</pre>", 6); tkptr->TraceState = false; } } else { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_TAG_VALUE), FI_LI); SsiEnd (rqptr); return; } } else if (strsame (dptr, "VERIFY", 6)) { dptr += SsiGetTagValue (rqptr, dptr, TagValue, sizeof(TagValue)); if (TOUP(TagValue[0]) == 'Y' || TagValue[0] == '1') tkptr->TagVerify = true; else if (TOUP(TagValue[0]) == 'N' || TagValue[0] == '0') tkptr->TagVerify = false; else { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_TAG_VALUE), FI_LI); SsiEnd (rqptr); return; } } else { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_TAG_UNKNOWN), FI_LI); SsiEnd (rqptr); return; } /* declare an AST to execute the required function */ SysDclAst (&SsiParse, rqptr); } /*****************************************************************************/ /* Process a DCL/EXEC statement. The array at the beginning of this function provides the allowed DCL statements and their DCL command equivalents. Only innocuous DCL commands are allowed (hopefully! e.g. SHOW, WRITE SYS$OUTPUT) for the average document. For documents owned by SYSTEM and NOT WORLD WRITEABLE (for obvious reasons) the execution of any DCL command or command procedure is allowed allowing maximum flexibility for the privileged document author. */ SsiDoDcl (REQUEST_STRUCT *rqptr) { /* First element is allowed DCL command. Second is actual DCL verb/command/syntax. Third, if non-empty, idicates a file specification is required. Fourth, if 'P', indicates it is for privileged documents only! if '@', that parameter should be checked for "@file.com" hack! */ static char *SupportedDcl [] = { "cgi", "", "", "@", /* any document */ "cmd", "", "", "P", /* privileged only! */ "dir", "directory ", "", "@", /* any document */ "exec", "", "", "P", /* privileged only! */ "file", "@", "Y", "P", /* privileged only! */ "run", "run ", "Y", "P", /* privileged only! */ "say", "write sys$output", "", "@", /* any document */ "script", "", "", "@", /* any document */ "show", "show", "", "@", /* any document */ "vdir", "directory ", "", "@", /* any document */ "virtual", "@", "Y", "P", /* privileged only! */ "vrun", "run ", "Y", "P", /* privileged only! */ "", "", "", "P" /* must be terminated by empty/privileged command! */ }; int idx, status; char *cptr, *dptr, *sptr, *zptr; char DclCommand [1024], MappedFile [ODS_MAX_FILE_NAME_LENGTH+1], ScriptName [ODS_MAX_FILE_NAME_LENGTH+1], MappedScript [ODS_MAX_FILE_NAME_LENGTH+1], MappedRunTime [ODS_MAX_FILE_NAME_LENGTH+1], TagValue [SSI_STRING_SIZE]; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoDcl() !&Z", rqptr->SsiTaskPtr->StatementBeginPtr); tkptr = rqptr->SsiTaskPtr; if (tkptr->TraceState) SsiTraceStatement (rqptr); dptr = rqptr->SsiTaskPtr->StatementBeginPtr; while (*dptr && !isspace(*dptr)) dptr++; while (*dptr && isspace(*dptr)) dptr++; for (idx = 0; *SupportedDcl[idx]; idx += 4) { if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z !&Z !&Z !&Z", SupportedDcl[idx], SupportedDcl[idx+1], SupportedDcl[idx+2], SupportedDcl[idx+3]); /* compare the user-supplied DCL command to the list of allowed */ cptr = SupportedDcl[idx]; sptr = dptr; while (*cptr && *sptr != '=' && TOUP(*cptr) == TOUP(*sptr)) { cptr++; sptr++; } if (!*cptr && *sptr == '=') break; } if (!SupportedDcl[idx][0] && !strsame (dptr = rqptr->SsiTaskPtr->StatementBeginPtr, "#exec ", 6)) { /************************/ /* unsupported command! */ /************************/ SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_DCL_UNSUPPORTED), FI_LI); SsiEnd (rqptr); return; } /*************************/ /* supported DCL command */ /*************************/ if (rqptr->rqPathSet.SsiExecPtr) { /**************************/ /* path SETing controlled */ /**************************/ cptr = rqptr->rqPathSet.SsiExecPtr; while (*cptr == '#') cptr++; while (*cptr && *cptr != '*') { sptr = SupportedDcl[idx]; while (*cptr && *cptr != ',' && *sptr && TOLO(*cptr) == TOLO(*sptr)) { cptr++; sptr++; } if (!*sptr && (!*cptr || *cptr == ',')) { cptr = "*"; break; } while (*cptr && *cptr != ',') cptr++; while (*cptr == ',') cptr++; } if (!*cptr) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_DCL_DISABLED), FI_LI); SsiEnd (rqptr); return; } } else if (SupportedDcl[idx+3][0] == 'P') { /******************************/ /* "privileged" DCL requested */ /******************************/ if (!Config.cfSsi.ExecEnabled) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_DCL_DISABLED), FI_LI); SsiEnd (rqptr); return; } if (!rqptr->rqPathSet.PrivSsi) { /* SYSTEM is UIC [1,4] */ if (tkptr->FileContentPtr->UicGroup != 1 || tkptr->FileContentPtr->UicMember != 4) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_DCL_NOT_SYSTEM), FI_LI); SsiEnd (rqptr); return; } /* protect word: wwggooss, protect bits: dewr, 1 denies access */ if (!(tkptr->FileContentPtr->Protection & 0x2000)) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_DCL_NOT_WORLD), FI_LI); SsiEnd (rqptr); return; } } } /****************************************************/ /* create the DCL command from array and parameters */ /****************************************************/ cptr = SupportedDcl[idx+1]; sptr = DclCommand; while (*cptr) *sptr++ = *cptr++; *sptr = '\0'; if (SupportedDcl[idx+2][0]) { /* file specification involved */ dptr += SsiGetFileSpec (rqptr, dptr, TagValue, sizeof(TagValue)); } else { /* some DCL verb/parameters or another */ dptr += SsiGetTagValue (rqptr, dptr, TagValue, sizeof(TagValue)); if (TagValue[0] && sptr > DclCommand) *sptr++ = ' '; } for (cptr = TagValue; *cptr; *sptr++ = *cptr++); *sptr = '\0'; if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z", DclCommand); /************************/ /* check for this issue */ /************************/ if (SupportedDcl[idx+3][0] == '@') { for (cptr = TagValue; *cptr && *cptr != '@' && *cptr != '\''; cptr++); if (*cptr) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_DCL_UNSUPPORTED), FI_LI); SsiEnd (rqptr); return; } } /************/ /* continue */ /************/ /* by default HTML-forbidden characters in DCL output are escaped */ rqptr->NetWriteEscapeHtml = true; while (*dptr) { if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z", dptr); if (rqptr->SsiTaskPtr->StopProcessing) break; while (*dptr && isspace(*dptr)) dptr++; if (!*dptr) break; if (strsame (dptr, "PAR=", 4)) { /* parameters to the command procedure, directory, etc. */ dptr += SsiGetTagValue (rqptr, dptr, TagValue, sizeof(TagValue)); if (TagValue[0]) *sptr++ = ' '; for (cptr = TagValue; *cptr; *sptr++ = *cptr++); *sptr = '\0'; } else if (strsame (dptr, "type=", 5)) { dptr += SsiGetTagValue (rqptr, dptr, TagValue, sizeof(TagValue)); if (TagValue[0] && strsame (TagValue, "text/html", -1)) rqptr->NetWriteEscapeHtml = tkptr->TraceState; else rqptr->NetWriteEscapeHtml = true; } else if (*dptr) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_TAG_UNKNOWN), FI_LI); SsiEnd (rqptr); return; } } if (rqptr->SsiTaskPtr->StopProcessing) { SsiEnd (rqptr); return; } if (strsame (SupportedDcl[idx], "cgi", -1) || strsame (SupportedDcl[idx], "script", -1)) { for (sptr = DclCommand; *sptr && *sptr != '?'; sptr++); if (*sptr) { SsiProblem (rqptr, "!AZ", sptr, FI_LI); SsiEnd (rqptr); return; } /* initialize with some nulls */ *(ULONGPTR)MappedFile = *(ULONGPTR)ScriptName = *(ULONGPTR)MappedScript = *(ULONGPTR)MappedRunTime = 0; cptr = MapUrl_Map (DclCommand, sizeof(DclCommand), MappedFile, sizeof(MappedFile), ScriptName, sizeof(ScriptName), MappedScript, sizeof(MappedScript), MappedRunTime, sizeof(MappedRunTime), NULL, rqptr, NULL); if (!cptr[0] && cptr[1]) { SsiProblem (rqptr, "!AZ", cptr+1, FI_LI); SsiEnd (rqptr); return; } if (!ScriptName[0]) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SCRIPT_NOT_FOUND), FI_LI); SsiEnd (rqptr); return; } rqptr->rqCgi.AbsorbHeader = rqptr->rqCgi.BufferRecords = true; for (cptr = MappedScript; *cptr && *cptr != ':'; cptr++); if (SAME2(cptr,'::')) { /* hmmm, 'ScriptName' could present a bit of a problem here */ DECnetBegin (rqptr, &SsiParse, MappedScript, MappedRunTime); } else if (ScriptName[0] == '+') { ScriptName[0] = '/'; DclBegin (rqptr, &SsiParse, NULL, ScriptName, NULL, MappedScript, MappedRunTime, NULL); } else DclBegin (rqptr, &SsiParse, NULL, ScriptName, MappedScript, NULL, MappedRunTime, NULL); } else DclBegin (rqptr, &SsiParse, DclCommand, NULL, NULL, NULL, NULL, NULL); } /*****************************************************************************/ /* Generate an "Index of" directory listing by calling DirBegin() task. */ SsiDoDir (REQUEST_STRUCT *rqptr) { int status; char *cptr, *dptr; char DirSpec [256], RealPath [256], TagValue [256]; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoDir() !&Z", rqptr->SsiTaskPtr->StatementBeginPtr); if (rqptr->SsiTaskPtr->TraceState) SsiTraceStatement (rqptr); RealPath[0] = TagValue[0] = '\0'; dptr = rqptr->SsiTaskPtr->StatementBeginPtr; while (*dptr && !isspace(*dptr)) dptr++; while (*dptr) { if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z", dptr); if (rqptr->SsiTaskPtr->StopProcessing) break; while (*dptr && isspace(*dptr)) dptr++; if (!*dptr) break; if (strsame (dptr, "FILE=", 5) || strsame (dptr, "VIRTUAL=", 8)) dptr += SsiGetFileSpec (rqptr, dptr, DirSpec, sizeof(DirSpec)); else if (strsame (dptr, "PAR=", 4)) { strcpy (TagValue, "httpd=index&"); dptr += SsiGetTagValue (rqptr, dptr, TagValue+12, sizeof(TagValue)-12); } else { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_TAG_UNKNOWN), FI_LI); SsiEnd (rqptr); return; } } if (rqptr->SsiTaskPtr->StopProcessing) { SsiEnd (rqptr); return; } MapUrl_Map (RealPath, sizeof(RealPath), DirSpec, 0, NULL, 0, NULL, 0, NULL, 0, NULL, rqptr, NULL); DirBegin (rqptr, &SsiParse, DirSpec, TagValue, RealPath, true); } /*****************************************************************************/ /* Output a server or user-assigned variable. */ SsiDoEcho (REQUEST_STRUCT *rqptr) { int status; char String [SSI_STRING_SIZE], FormatString [SSI_STRING_SIZE]; char *cptr, *dptr, *sptr, *zptr, *VarPtr; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoEcho() !&Z", rqptr->SsiTaskPtr->StatementBeginPtr); tkptr = rqptr->SsiTaskPtr; if (tkptr->TraceState) SsiTraceStatement (rqptr); FormatString[0] = '\0'; dptr = rqptr->SsiTaskPtr->StatementBeginPtr; /* two formats: '<!--#"' and '<!--#echo' */ if (SAME2(dptr,'#\"')) dptr++; else { while (*dptr && !isspace(*dptr)) dptr++; while (*dptr && isspace(*dptr)) dptr++; } /* keep this so we can check for some OSU specific syntax */ VarPtr = dptr; while (*dptr) { if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z", dptr); if (rqptr->SsiTaskPtr->StopProcessing) break; while (*dptr && isspace(*dptr)) dptr++; if (!*dptr) break; if (*dptr == '\"') { /* format is <!--#echo "blah-blah" --> */ dptr += SsiGetTagValue (rqptr, dptr, cptr = String, sizeof(String)); } else if (strsame (dptr, "value=", 6) || strsame (dptr, "VAR=", 4)) { /* format is <!--#echo var="" --> */ dptr += SsiGetTagValue (rqptr, dptr, cptr = String, sizeof(String)); } else if (strsame (dptr, "HEADER=", 7)) { /* format is <!--#echo header="" --> append to response header */ dptr += SsiGetTagValue (rqptr, dptr, sptr = String, sizeof(String)-2); while (*sptr) sptr++; *sptr = '\0'; ResponseDictFromString (rqptr, String, sptr - String); /* this is a stand-alone function */ continue; } else { /* format is <!--#echo NAME[=format] --> */ zptr = (sptr = String) + sizeof(String); while (*dptr && !isspace(*dptr) && *dptr != '=' && sptr < zptr) *sptr++ = TOUP(*dptr++); if (sptr >= zptr) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_GENERAL_OVERFLOW), FI_LI); SsiEnd (rqptr); return; } *sptr = '\0'; if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z", String); if (*dptr == '=') dptr += SsiGetTagValue (rqptr, dptr, FormatString, sizeof(FormatString)); else FormatString[0] = '\0'; cptr = SsiGetVar (rqptr, String, FormatString, false); } if (rqptr->SsiTaskPtr->StopProcessing) break; if (TOUP(VarPtr[5]) == 'A') { /* OSU-compliant echoes */ if (strsame (VarPtr, "VAR=\"ACCESSES_ORDINAL", 21) && !isalpha(VarPtr[21])) { tkptr->AccessOrdinal = true; tkptr->AccessSinceText[0] = tkptr->FormatString[0] = '\0'; /* if the file's access count has been previously determined */ if (tkptr->AccessCount) SsiAccessesOutput (rqptr); else SsiAccessesOpen (rqptr); /* generated asynchronously */ return; } if (strsame (VarPtr, "VAR=\"ACCESSES", 13) && !isalpha(VarPtr[13])) { tkptr->AccessOrdinal = false; tkptr->AccessSinceText[0] = tkptr->FormatString[0] = '\0'; /* if the file's access count has been previously determined */ if (tkptr->AccessCount) SsiAccessesOutput (rqptr); else SsiAccessesOpen (rqptr); /* generated asynchronously */ return; } } if (tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex]) { NetWriteBuffered (rqptr, NULL, cptr, strlen(cptr)); tkptr->TraceOutput = true; } } if (rqptr->SsiTaskPtr->StopProcessing) { SsiEnd (rqptr); return; } SysDclAst (&SsiParse, rqptr); } /*****************************************************************************/ /* Flow control statement. If the current flow control is executing then it now disallowed. If the current flow control structure (including "#if"s, "#orif"s and other "#elif"s) have not allowed execution then evaluate the conditional and allow execution if true. */ SsiDoElif (REQUEST_STRUCT *rqptr) { SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoElif() !&Z", rqptr->SsiTaskPtr->StatementBeginPtr); tkptr = rqptr->SsiTaskPtr; tkptr->SuppressLine = true; /* if previous flow control was not an "#if" or "#elif" */ if (tkptr->FlowControlState[tkptr->FlowControlIndex] != SSI_STATE_IF && tkptr->FlowControlState[tkptr->FlowControlIndex] != SSI_STATE_ELIF) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_FLOW_CONTROL), FI_LI); SsiEnd (rqptr); return; } tkptr->FlowControlState[tkptr->FlowControlIndex] = SSI_STATE_ELIF; if (tkptr->FlowControlHasExecuted[rqptr->SsiTaskPtr->FlowControlIndex]) { /* if flow control has already output just continue parsing */ tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex] = false; SysDclAst (&SsiParse, rqptr); return; } /* if the parent level is executing and it evaluates true then execute */ if (tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex-1]) { if (tkptr->TraceState) SsiTraceStatement (rqptr); tkptr->FlowControlHasExecuted[tkptr->FlowControlIndex] = tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex] = SsiEvaluate (rqptr); } /* declare an AST to execute the next function */ SysDclAst (&SsiParse, rqptr); } /*****************************************************************************/ /* Flow control statement. If the current flow control structure (including "#if"s, "#orif"s and/or "#elif"s) have not allowed execution then this will. */ SsiDoElse (REQUEST_STRUCT *rqptr) { SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoElse() !&Z", rqptr->SsiTaskPtr->StatementBeginPtr); tkptr = rqptr->SsiTaskPtr; tkptr->SuppressLine = true; /* if previous flow control was not an "#if" or "#elif" */ if (tkptr->FlowControlState[tkptr->FlowControlIndex] != SSI_STATE_IF && tkptr->FlowControlState[tkptr->FlowControlIndex] != SSI_STATE_ELIF) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_FLOW_CONTROL), FI_LI); SsiEnd (rqptr); return; } tkptr->FlowControlState[tkptr->FlowControlIndex] = SSI_STATE_ELSE; /* if there has been no output so far */ if (tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex-1] && !tkptr->FlowControlHasExecuted[tkptr->FlowControlIndex]) { if (tkptr->TraceState) SsiTraceStatement (rqptr); tkptr->FlowControlHasExecuted[tkptr->FlowControlIndex] = tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex] = true; } else tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex] = false; /* declare an AST to execute the next function */ SysDclAst (&SsiParse, rqptr); } /*****************************************************************************/ /* OSU-compliant, marking the end of an #includable "part". */ SsiDoEnd (REQUEST_STRUCT *rqptr) { char String [SSI_INCLUDE_PART_MAX+1]; char *cptr, *dptr, *sptr, *zptr; SSI_TASK *tkptr, *ParentDocumentTaskPtr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoEnd() !&Z", rqptr->SsiTaskPtr->StatementBeginPtr); tkptr = rqptr->SsiTaskPtr; if (!tkptr->OsuCompliant || !tkptr->CurrentPart[0]) { /* declare an AST to execute the next function */ SysDclAst (&SsiParse, rqptr); return; } tkptr->SuppressLine = true; if (tkptr->TraceState) SsiTraceStatement (rqptr); dptr = tkptr->StatementBeginPtr; while (*dptr && !isspace(*dptr)) dptr++; while (*dptr && isspace(*dptr)) dptr++; while (*dptr) { while (*dptr && (*dptr == '\"' || isspace(*dptr))) dptr++; if (!*dptr) break; zptr = (sptr = String) + sizeof(String); while (*dptr && *dptr != '\"' && !isspace(*dptr) && sptr < zptr) *sptr++ = *dptr++; if (sptr >= zptr) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_GENERAL_OVERFLOW), FI_LI); SsiEnd (rqptr); return; } *sptr = '\0'; if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z !&Z", String, tkptr->CurrentPart); if (strcmp (String, tkptr->CurrentPart)) continue; /* end of the #included part */ tkptr->FlowControlHasExecuted[tkptr->FlowControlIndex] = tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex] = false; break; } tkptr->SuppressLine = true; if (tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex]) { /* not yet found, declare an AST to execute the next function */ SysDclAst (&SsiParse, rqptr); return; } /* if previous flow control was not a #begin */ if (tkptr->FlowControlState[tkptr->FlowControlIndex] != SSI_STATE_BEGIN) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_FLOW_CONTROL), FI_LI); SsiEnd (rqptr); return; } if (tkptr->FlowControlIndex) { tkptr->FlowControlIndex--; /* declare an AST to execute the next function */ SysDclAst (&SsiParse, rqptr); return; } SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_FLOW_CONTROL), FI_LI); SsiEnd (rqptr); } /*****************************************************************************/ /* Flow control statement. */ SsiDoEndif (REQUEST_STRUCT *rqptr) { SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoEndIf()"); tkptr = rqptr->SsiTaskPtr; if (tkptr->TraceState) SsiTraceStatement (rqptr); tkptr->SuppressLine = true; if (tkptr->FlowControlIndex) { tkptr->FlowControlIndex--; /* declare an AST to execute the next function */ SysDclAst (&SsiParse, rqptr); return; } SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_FLOW_CONTROL), FI_LI); SsiEnd (rqptr); } /*****************************************************************************/ /* Exit from current document processing here! */ SsiDoExit (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoExit()"); if (rqptr->SsiTaskPtr->TraceState) SsiTraceStatement (rqptr); SsiEnd (rqptr); } /*****************************************************************************/ /* Output the specified file's creation date and time. */ SsiDoFCreated (REQUEST_STRUCT *rqptr) { char *dptr; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoFCreated() !&Z", rqptr->SsiTaskPtr->StatementBeginPtr); tkptr = rqptr->SsiTaskPtr; if (tkptr->TraceState) SsiTraceStatement (rqptr); tkptr->ScratchFileName[0] = tkptr->FormatString[0] = '\0'; dptr = rqptr->SsiTaskPtr->StatementBeginPtr; while (*dptr && !isspace(*dptr)) dptr++; while (*dptr) { if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z", dptr); if (rqptr->SsiTaskPtr->StopProcessing) break; while (*dptr && isspace(*dptr)) dptr++; if (!*dptr) break; if (strsame (dptr, "FILE=", 5) || strsame (dptr, "VIRTUAL=", 8)) dptr += SsiGetFileSpec (rqptr, dptr, tkptr->ScratchFileName, sizeof(tkptr->ScratchFileName)); else if (strsame (dptr, "FMT=", 4)) dptr += SsiGetTagValue (rqptr, dptr, tkptr->FormatString, sizeof(tkptr->FormatString)); else { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_TAG_UNKNOWN), FI_LI); SsiEnd (rqptr); return; } } if (rqptr->SsiTaskPtr->StopProcessing) { SsiEnd (rqptr); return; } SsiFileDetails (rqptr, FILE_FCREATED); } /*****************************************************************************/ /* Output the specified file's last modification date and time. */ SsiDoFLastMod (REQUEST_STRUCT *rqptr) { char *dptr; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoFLastMod() !&Z", rqptr->SsiTaskPtr->StatementBeginPtr); tkptr = rqptr->SsiTaskPtr; if (tkptr->TraceState) SsiTraceStatement (rqptr); tkptr->ScratchFileName[0] = tkptr->FormatString[0] = '\0'; dptr = rqptr->SsiTaskPtr->StatementBeginPtr; while (*dptr && !isspace(*dptr)) dptr++; while (*dptr) { if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z", dptr); if (rqptr->SsiTaskPtr->StopProcessing) break; while (*dptr && isspace(*dptr)) dptr++; if (!*dptr) break; if (strsame (dptr, "FILE=", 5) || strsame (dptr, "VIRTUAL=", 8)) dptr += SsiGetFileSpec (rqptr, dptr, tkptr->ScratchFileName, sizeof(tkptr->ScratchFileName)); else if (strsame (dptr, "FMT=", 4)) dptr += SsiGetTagValue (rqptr, dptr, tkptr->FormatString, sizeof(tkptr->FormatString)); else { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_TAG_UNKNOWN), FI_LI); SsiEnd (rqptr); return; } } if (rqptr->SsiTaskPtr->StopProcessing) { SsiEnd (rqptr); return; } SsiFileDetails (rqptr, FILE_FLASTMOD); } /*****************************************************************************/ /* Output the specified file's size. */ SsiDoFSize (REQUEST_STRUCT *rqptr) { char *dptr; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoFSize() !&Z", rqptr->SsiTaskPtr->StatementBeginPtr); tkptr = rqptr->SsiTaskPtr; if (tkptr->TraceState) SsiTraceStatement (rqptr); tkptr->ScratchFileName[0] = tkptr->FormatString[0] = '\0'; dptr = rqptr->SsiTaskPtr->StatementBeginPtr; while (*dptr && !isspace(*dptr)) dptr++; while (*dptr) { if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z", dptr); if (rqptr->SsiTaskPtr->StopProcessing) break; while (*dptr && isspace(*dptr)) dptr++; if (!*dptr) break; if (strsame (dptr, "FILE=", 5) || strsame (dptr, "VIRTUAL=", 8)) dptr += SsiGetFileSpec (rqptr, dptr, tkptr->ScratchFileName, sizeof(tkptr->ScratchFileName)); else if (strsame (dptr, "FMT=", 4)) dptr += SsiGetTagValue (rqptr, dptr, tkptr->FormatString, sizeof(tkptr->FormatString)); else { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_TAG_UNKNOWN), FI_LI); SsiEnd (rqptr); return; } } if (rqptr->SsiTaskPtr->StopProcessing) { SsiEnd (rqptr); return; } SsiFileDetails (rqptr, FILE_FSIZE); } /*****************************************************************************/ /* Directive for changing/generating HTTP response headers and/or fields. */ SsiDoModified (REQUEST_STRUCT *rqptr) { static char Expires [64] = "Expires: ", LastModified [64] = "Last-Modified: "; /* point immediately after the hard-wired strings */ static char *ExpiresPtr = Expires + 9, *LastModifiedPtr = LastModified + 15; /* available == size - start - '\r' - '\n' - '\0' */ static int ExpiresAvailable = sizeof(Expires) - 12; BOOL DoIfModifiedSince, DoLastModified; int status; char *dptr; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoModified() !&Z", rqptr->SsiTaskPtr->StatementBeginPtr); tkptr = rqptr->SsiTaskPtr; if (tkptr->TraceState) SsiTraceStatement (rqptr); DoLastModified = DoIfModifiedSince = false; *ExpiresPtr = '\0'; tkptr->ScratchFileName[0] = '\0'; dptr = rqptr->SsiTaskPtr->StatementBeginPtr; while (*dptr && !isspace(*dptr)) dptr++; while (*dptr) { if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z", dptr); if (tkptr->StopProcessing) break; while (*dptr && isspace(*dptr)) dptr++; if (!*dptr) break; if (strsame (dptr, "EXPIRES=", 8)) dptr += SsiGetTagValue (rqptr, dptr, ExpiresPtr, ExpiresAvailable); else if (strsame (dptr, "FILE=", 5) || strsame (dptr, "VIRTUAL=", 8)) dptr += SsiGetFileSpec (rqptr, dptr, tkptr->ScratchFileName, sizeof(tkptr->ScratchFileName)); else if (strsame (dptr, "FMT=", 4)) dptr += SsiGetTagValue (rqptr, dptr, tkptr->FormatString, sizeof(tkptr->FormatString)); else if (strsame (dptr, "IF-MODIFIED-SINCE", 17)) { dptr += 17; DoLastModified = false; DoIfModifiedSince = true; } else if (strsame (dptr, "LAST-MODIFIED", 13)) { dptr += 13; DoLastModified = true; DoIfModifiedSince = false; } else { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_TAG_UNKNOWN), FI_LI); SsiEnd (rqptr); return; } } if (rqptr->SsiTaskPtr->StopProcessing) { SsiEnd (rqptr); return; } if (DoIfModifiedSince) { /******************************/ /* check "If-Modified-Since:" */ /******************************/ if (!tkptr->LastModifiedTime64) { /* no files' RDTs have been checked */ SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_TAG_VALUE), FI_LI); SsiEnd (rqptr); return; } if (!rqptr->NotFromCache) { if (!HttpIfModifiedSince (rqptr, &tkptr->LastModifiedTime64, -1)) { /****************/ /* not modified */ /****************/ SsiEnd (rqptr); return; } } /******************/ /* modified since */ /******************/ /* supply a "Last-Modified:" response header */ if (VMSnok (status = HttpGmTimeString (LastModifiedPtr, &tkptr->LastModifiedTime64))) { SsiProblem (rqptr, "!&m", status, FI_LI); SsiEnd (rqptr); return; } ResponseDictFromString (rqptr, LastModified, -1); /* now just continue on */ SysDclAst (&SsiParse, rqptr); return; } if (DoLastModified) { /*****************************/ /* explicit "Last-Modified:" */ /*****************************/ if (!tkptr->LastModifiedTime64) { /* no files' RDTs have been checked */ SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_TAG_VALUE), FI_LI); SsiEnd (rqptr); return; } if (VMSnok (status = HttpGmTimeString (LastModifiedPtr, &tkptr->LastModifiedTime64))) { SsiProblem (rqptr, "!&m", status, FI_LI); SsiEnd (rqptr); return; } ResponseDictFromString (rqptr, LastModified, -1); /* just continue on */ SysDclAst (&SsiParse, rqptr); return; } if (*ExpiresPtr) { /***********************/ /* explicit "Expires:" */ /***********************/ if (*ExpiresPtr == '0') { /* pre-expire, so let's try really hard! */ ResponseDictFromString (rqptr, "Expires: Fri, 13 Jan 1978 14:00:00 GMT\r\n\ Cache-Control: no-cache, no-store\r\n\ Pragma: no-cache\r\n", -1); } else ResponseDictFromString (rqptr, Expires, -1); /* just continue on */ SysDclAst (&SsiParse, rqptr); return; } if (tkptr->ScratchFileName[0]) { /******************/ /* get file's RDT */ /******************/ /* get the revision timestamp of the specified file */ SsiFileDetails (rqptr, FILE_LAST_MODIFIED); return; } /* hmmm, nothing specified, check the modification date of this file */ strcpy (tkptr->ScratchFileName, tkptr->FileContentPtr->FileName); SsiFileDetails (rqptr, FILE_LAST_MODIFIED); return; } /*****************************************************************************/ /* Flow control statement. If the parent level is executing evalutate the conditional and allow execution if true. */ SsiDoIf (REQUEST_STRUCT *rqptr) { SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoIf() !&Z", rqptr->SsiTaskPtr->StatementBeginPtr); tkptr = rqptr->SsiTaskPtr; tkptr->SuppressLine = true; tkptr->FlowControlIndex++; if (tkptr->FlowControlIndex > SSI_MAX_FLOW_CONTROL) { tkptr->FlowControlIndex = 0; SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_FLOW_CONTROL), FI_LI); SsiEnd (rqptr); return; } tkptr->FlowControlState[tkptr->FlowControlIndex] = SSI_STATE_IF; /* if the parent level is executing and it evaluates true then execute */ if (tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex-1]) { if (tkptr->TraceState) SsiTraceStatement (rqptr); tkptr->FlowControlHasExecuted[tkptr->FlowControlIndex] = tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex] = SsiEvaluate (rqptr); } else tkptr->FlowControlHasExecuted[tkptr->FlowControlIndex] = tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex] = false; /* declare an AST to execute the next function */ SysDclAst (&SsiParse, rqptr); } /*****************************************************************************/ /* Include the contents of the specified file by calling FileBegin() task. The tag content="text/..." makes any file extension acceptable as the text type an presents it as that ("text/html" is included unescaped, "text/plain" escaped). This makes a file with any extension accepted as text! The tag type="text/plain" (older variant) forces a "text/html" file to be escaped and presented as a plain text file. */ SsiDoInclude (REQUEST_STRUCT *rqptr) { BOOL ok; int status; char *cptr, *dptr, *sptr, *zptr; char FileContent [256], FileFormat [256], FileName [256]; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoInclude() !&Z", rqptr->SsiTaskPtr->StatementBeginPtr); tkptr = rqptr->SsiTaskPtr; if (tkptr->TraceState) SsiTraceStatement (rqptr); tkptr->IncludePart[0] = tkptr->TheFileNameVar[0] = '\0'; FileContent[0] = FileName[0] = FileFormat[0] = '\0'; dptr = tkptr->StatementBeginPtr; while (*dptr && !isspace(*dptr)) dptr++; while (*dptr) { if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z", dptr); if (tkptr->StopProcessing) break; while (*dptr && isspace(*dptr)) dptr++; if (!*dptr) break; if (strsame (dptr, "FILE=", 5) || strsame (dptr, "VIRTUAL=", 8)) dptr += SsiGetFileSpec (rqptr, dptr, FileName, sizeof(FileName)); else if (strsame (dptr, "type=", 5)) dptr += SsiGetTagValue (rqptr, dptr, FileFormat, sizeof(FileFormat)); else if (strsame (dptr, "content=", 8)) dptr += SsiGetTagValue (rqptr, dptr, FileContent, sizeof(FileContent)); else if (strsame (dptr, "FMT=", 4)) dptr += SsiGetTagValue (rqptr, dptr, tkptr->FormatString, sizeof(tkptr->FormatString)); else if (tkptr->OsuCompliant && strsame (dptr, "PART=", 5)) dptr += SsiGetTagValue (rqptr, dptr, tkptr->IncludePart, sizeof(tkptr->IncludePart)); else { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_TAG_UNKNOWN), FI_LI); SsiEnd (rqptr); return; } } if (tkptr->StopProcessing) { SsiEnd (rqptr); return; } if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z !&Z !&Z", FileName, FileContent, FileFormat); if (tkptr->OsuCompliant) { /* OSU only includes other SSI files */ cptr = ConfigContentTypeSsi; } else if (!(cptr = FileContent)[0]) { /* find the file extension and set the content type */ for (cptr = FileName; *cptr && *cptr != ']'; cptr++); while (*cptr && *cptr != '.') cptr++; cptr = ConfigContentType (NULL, cptr); } if (!strsame (cptr, "text/", 5)) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_INCLUDE_NOT_TEXT), FI_LI); SsiEnd (rqptr); return; } /* set the variable file name */ strcpy (tkptr->TheFileNameVar, FileName); if (FileFormat[0]) cptr = FileFormat; if (ConfigSameContentType (cptr, "text/plain", -1)) { /* it's plain text, so make it look like it, with escaped HTML */ FileSetPreTag (rqptr, true); FileSetEscapeHtml (rqptr, true); } if (ConfigSameContentType (cptr, ConfigContentTypeSsi, -1)) { /* buffer the file contents and pass them to the SSI engine */ FileSetContentHandler (rqptr, &SsiBegin, SsiSizeMax); } FileSetCacheAllowed (rqptr, true); FileSetAuthorizePath (rqptr, true); /* ultimately return to the SSI engine parser */ FileBegin (rqptr, &SsiParse, &SsiIncludeError, NULL, FileName, cptr); } /*****************************************************************************/ /* */ SsiIncludeError (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiIncludeError() !&F !&Z", &SsiIncludeError, rqptr->SsiTaskPtr->FormatString); if (rqptr->SsiTaskPtr->FormatString[0] == '?') { /* reset the file variable data to indicate it was not found */ rqptr->SsiTaskPtr->TheFileNameVar[0] = '\0'; /* declare an AST to continue processing */ SysDclAst (&SsiParse, rqptr); return; } else { /* report the error and stop processing */ SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_INCLUDE_ACCESS), FI_LI); SsiEnd (rqptr); return; } } /*****************************************************************************/ /* Flow control statement. If the preceding control statement was an "#if" or "#elif" and it did not evaluate true then this allows another chance to execute this segment. It is essentially an OR on the last evalution. The evaluation here is not performed if the previous allowed execution. */ SsiDoOrif (REQUEST_STRUCT *rqptr) { SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoOrIf() !&Z", rqptr->SsiTaskPtr->StatementBeginPtr); tkptr = rqptr->SsiTaskPtr; tkptr->SuppressLine = true; /* if previous flow control was not an "#if" or "#elif" */ if (tkptr->FlowControlState[tkptr->FlowControlIndex] != SSI_STATE_IF && tkptr->FlowControlState[tkptr->FlowControlIndex] != SSI_STATE_ELIF) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_FLOW_CONTROL), FI_LI); SsiEnd (rqptr); return; } /* if the parent level is executing and the current is not then evaluate */ if (tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex-1] && !tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex]) { if (tkptr->TraceState) SsiTraceStatement (rqptr); tkptr->FlowControlHasExecuted[tkptr->FlowControlIndex] = tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex] = SsiEvaluate (rqptr); } /* declare an AST to execute the next function */ SysDclAst (&SsiParse, rqptr); } /*****************************************************************************/ /* Print all server and user-assigned variables. Usually used for no more than document debugging. Variable names and values have HTML- forbidden characters escaped. */ SsiDoPrintEnv (REQUEST_STRUCT *rqptr) { BOOL EscapeHtmlBuffer; int length; char *cptr, *sptr; DICT_ENTRY_STRUCT *denptr; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoPrintEnv()"); tkptr = rqptr->SsiTaskPtr; if (tkptr->TraceState) SsiTraceStatement (rqptr); if (tkptr->TraceState) NetWriteBuffered (rqptr, NULL, "\n\n", 2); else NetWriteBuffered (rqptr, NULL, "<pre>\n\n", 6); EscapeHtmlBuffer = rqptr->NetWriteEscapeHtml; rqptr->NetWriteEscapeHtml = true; tkptr->TraceOutput = true; /* SSI variables */ cptr = SsiGetServerVar (rqptr, sptr = "COMPLIANCE", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); cptr = SsiGetServerVar (rqptr, sptr = "CREATED", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); cptr = SsiGetServerVar (rqptr, sptr = "DATE_GMT", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); cptr = SsiGetServerVar (rqptr, sptr = "DATE_LOCAL", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); cptr = SsiGetServerVar (rqptr, sptr = "DOCUMENT_DEPTH", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); cptr = SsiGetServerVar (rqptr, sptr = "DOCUMENT_NAME", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); cptr = SsiGetServerVar (rqptr, sptr = "DOCUMENT_ROOT", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); cptr = SsiGetServerVar (rqptr, sptr = "DOCUMENT_URI", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); if (rqptr->RedirectErrorStatusCode) { /* SSI variables that are only present during an error report */ cptr = SsiGetServerVar (rqptr, sptr = "ERROR_LINE", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); cptr = SsiGetServerVar (rqptr, sptr = "ERROR_MODULE", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); cptr = SsiGetServerVar (rqptr, sptr = "ERROR_REPORT", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); cptr = SsiGetServerVar (rqptr, sptr = "ERROR_REPORT2", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); cptr = SsiGetServerVar (rqptr, sptr = "ERROR_REPORT3", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); cptr = SsiGetServerVar (rqptr, sptr = "ERROR_STATUS_CLASS", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); cptr = SsiGetServerVar (rqptr, sptr = "ERROR_STATUS_CODE", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); cptr = SsiGetServerVar (rqptr, sptr = "ERROR_STATUS_EXPLANATION", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); cptr = SsiGetServerVar (rqptr, sptr = "ERROR_STATUS_TEXT", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); cptr = SsiGetServerVar (rqptr, sptr = "ERROR_TYPE", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); } cptr = SsiGetServerVar (rqptr, sptr = "FILE_NAME", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); /* OSU-compliant variable */ cptr = SsiGetServerVar (rqptr, sptr = "HW_NAME", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); cptr = SsiGetServerVar (rqptr, sptr = "LAST_MODIFIED", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); cptr = SsiGetServerVar (rqptr, sptr = "QUERY_STRING_UNESCAPED", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); cptr = SsiGetServerVar (rqptr, sptr = "PARENT_FILE_NAME", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); /* OSU-compliant variable */ cptr = SsiGetServerVar (rqptr, sptr = "SERVER_VERSION", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); cptr = SsiGetServerVar (rqptr, sptr = "THE_FILE_NAME", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); cptr = SsiGetServerVar (rqptr, sptr = "THIS_FILE_NAME", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); /* OSU-compliant variable */ cptr = SsiGetServerVar (rqptr, sptr = "VMS_VERSION", NULL); SsiPrintEnvVar (rqptr, sptr, cptr); /* CGI variables */ NetWriteBuffered (rqptr, NULL, "\n", 1); cptr = tkptr->CgiBufferPtr; for (;;) { if (!(length = *(USHORTPTR)cptr)) break; SsiPrintEnvVar (rqptr, cptr+sizeof(short)+DclCgiVariablePrefixLength, NULL); cptr += length + sizeof(short); } /* user variables */ DictIterate (tkptr->UserDictPtr, NULL); while (denptr = DictIterate (tkptr->UserDictPtr, "*")) SsiPrintEnvVar (rqptr, DICT_GET_KEY(denptr), DICT_GET_VALUE(denptr)); rqptr->NetWriteEscapeHtml = EscapeHtmlBuffer; if (!tkptr->TraceState) NetWriteBuffered (rqptr, NULL, "</pre>", 6); SysDclAst (&SsiParse, rqptr); } /*****************************************************************************/ /* Simply do the "printenv" of a single variable for SsiDoPrintEnv(). */ SsiPrintEnvVar ( REQUEST_STRUCT *rqptr, char* VarName, char* VarValue ) { /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiPrintEnvVar() !AZ=!AZ", VarName, VarValue); NetWriteBuffered (rqptr, NULL, VarName, strlen(VarName)); if (VarValue) { NetWriteBuffered (rqptr, NULL, "=", 1); NetWriteBuffered (rqptr, NULL, VarValue, strlen(VarValue)); } NetWriteBuffered (rqptr, NULL, "\n", 1); } /*****************************************************************************/ /* Set a user variable. Server variables cannot be set. Existing variable names are of course set to the new value. New variables are created ex nihlo and added to a simple linked list. */ SsiDoSet (REQUEST_STRUCT *rqptr) { BOOL PreTagFileContents; char *cptr, *dptr; char VarName [256], VarValue [SSI_STRING_SIZE]; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoSet() !&Z", rqptr->SsiTaskPtr->StatementBeginPtr); rqptr->SsiTaskPtr->SuppressLine = true; if (rqptr->SsiTaskPtr->TraceState) SsiTraceStatement (rqptr); dptr = rqptr->SsiTaskPtr->StatementBeginPtr; while (*dptr && !isspace(*dptr)) dptr++; VarName[0] = VarValue[0]= '\0'; while (*dptr) { if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z", dptr); if (rqptr->SsiTaskPtr->StopProcessing) { SsiEnd (rqptr); return; } while (*dptr && isspace(*dptr)) dptr++; if (!*dptr) break; if (strsame (dptr, "VAR=", 4)) { dptr += SsiGetTagValue (rqptr, dptr, VarName, sizeof(VarName)); continue; } if (strsame (dptr, "value=", 6)) dptr += SsiGetTagValue (rqptr, dptr, VarValue, sizeof(VarValue)); else { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_TAG_UNKNOWN), FI_LI); SsiEnd (rqptr); return; } if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z !&Z", VarName, VarValue); if (rqptr->SsiTaskPtr->StopProcessing) { SsiEnd (rqptr); return; } if (!VarName[0]) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_VARIABLE), FI_LI); SsiEnd (rqptr); return; } for (cptr = VarName; *cptr; cptr++) if (!isalnum(*cptr) && *cptr != '_' && *cptr != '$') break; if (*cptr) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_VARIABLE), FI_LI); SsiEnd (rqptr); return; } if (SsiGetServerVar (rqptr, VarName, NULL)) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_VARIABLE), FI_LI); SsiEnd (rqptr); return; } DictInsert (rqptr->SsiTaskPtr->UserDictPtr, DICT_TYPE_SSI, VarName, -1, VarValue, -1); if (rqptr->SsiTaskPtr->TraceState) SsiTraceSetVar (rqptr, VarName, VarValue); /* ready for the next round (if any) */ VarValue[0]= '\0'; } SysDclAst (&SsiParse, rqptr); } /*****************************************************************************/ /* Stop processing the document here! */ SsiDoStop (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoStop()"); if (rqptr->SsiTaskPtr->TraceState) SsiTraceStatement (rqptr); rqptr->SsiTaskPtr->StopProcessing = true; SsiEnd (rqptr); } /*****************************************************************************/ /* Turn the SSI trace on or off ("#config trace=1" is prefered, this is kept for backward compatibility). */ SsiDoTrace (REQUEST_STRUCT *rqptr) { char *cptr, *dptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiDoTrace() !&Z", rqptr->SsiTaskPtr->StatementBeginPtr); dptr = rqptr->SsiTaskPtr->StatementBeginPtr; while (*dptr && !isspace(*dptr)) dptr++; while (*dptr && isspace(*dptr)) dptr++; for (cptr = dptr; *cptr && !isspace(*cptr); cptr++); *cptr = '\0'; if ((TOUP(dptr[0]) == 'O' && TOUP(dptr[1]) == 'N') || TOUP(dptr[0]) == 'Y' || dptr[0] == '1') { if (!rqptr->SsiTaskPtr->TraceState) { NetWriteBuffered (rqptr, NULL, "<pre>", 5); rqptr->NetWriteEscapeHtml = true; rqptr->SsiTaskPtr->TraceState = true; } SsiTraceStatement (rqptr); } else if ((TOUP(dptr[0]) == 'O' && TOUP(dptr[1]) == 'F') || TOUP(dptr[0]) == 'N' || dptr[0] == '0') { if (rqptr->SsiTaskPtr->TraceState) { SsiTraceStatement (rqptr); rqptr->NetWriteEscapeHtml = false; NetWriteBuffered (rqptr, NULL, "</pre>", 6); rqptr->SsiTaskPtr->TraceState = false; } } else if (rqptr->SsiTaskPtr->TraceState) SsiTraceStatement (rqptr); SysDclAst (&SsiParse, rqptr); } /*****************************************************************************/ /* Display the current SSI file record. */ SsiTraceLine ( REQUEST_STRUCT *rqptr, char *cptr ) { int status; char ch; char *sptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiTraceLine()"); for (sptr = cptr; *sptr && *sptr != '\n'; sptr++); ch = *sptr; *sptr = '\0'; status = FaoToNet (rqptr, "!&?\n\r\r<font color=\"#0000cc\"><b>[!4ZL:!UL]!&;AZ</b></font>\n", rqptr->SsiTaskPtr->TraceOutput, rqptr->SsiTaskPtr->LineNumber, rqptr->SsiTaskPtr->FlowControlIndex, cptr); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); *sptr = ch; rqptr->SsiTaskPtr->TraceOutput = false; } /*****************************************************************************/ /* Display the current SSI statement. */ SsiTraceStatement (REQUEST_STRUCT *rqptr) { int status; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiTraceStatement()"); status = FaoToNet (rqptr, "<font color=\"#cc0000\"><b>[!&;AZ]</b></font>", rqptr->SsiTaskPtr->StatementBeginPtr); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); rqptr->SsiTaskPtr->TraceOutput = true; } /*****************************************************************************/ /* Display the name and value of a variable as it is retrieved. */ SsiTraceGetVar ( REQUEST_STRUCT *rqptr, char *VarName, char *VarValue ) { int status; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiTraceGetVar() !AZ=!AZ", VarName, VarValue); status = FaoToNet (rqptr, "<font color=\"#cc00cc\"><b>[!&;AZ:!&;AZ]</b></font>", VarName, VarValue); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); rqptr->SsiTaskPtr->TraceOutput = true; } /*****************************************************************************/ /* Display the name and value of a variable as it is stored. */ SsiTraceSetVar ( REQUEST_STRUCT *rqptr, char *VarName, char *VarValue ) { int status; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiTraceSetVar()"); status = FaoToNet (rqptr, "<font color=\"#cc00cc\"><b>[!&;AZ=!&;AZ]</b></font>", VarName, VarValue); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); rqptr->SsiTaskPtr->TraceOutput = true; } /*****************************************************************************/ /* First search the server-assigned variables, then the user-assigned variables. If found return a pointer to a symbol's value string. If no such symbol found return a NULL. */ char* SsiGetVar ( REQUEST_STRUCT *rqptr, char *VarName, char *Format, BOOL CheckOnly ) { char *ValuePtr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiGetVar() !AZ \'!AZ\' !UL", VarName, Format, CheckOnly); ValuePtr = SsiGetServerVar (rqptr, VarName, Format); if (rqptr->SsiTaskPtr->StopProcessing) return (ValuePtr); if (ValuePtr) goto SsiGetVarReturn; ValuePtr = SsiGetUserVar (rqptr, VarName); if (rqptr->SsiTaskPtr->StopProcessing) return (ValuePtr); if (ValuePtr) goto SsiGetVarReturn; if (CheckOnly) goto SsiGetVarReturn; /* variable not found */ ValuePtr = MsgFor(rqptr,MSG_SSI_VARIABLE_NOT_FOUND); SsiGetVarReturn: if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z", ValuePtr); return (ValuePtr); } /*****************************************************************************/ /* Return a pointer to the value of a server-assigned variable, NULL if no such variable. The calling routine might want to check for an error message being generated by bad format or time from SsiTimeString(). */ char* SsiGetServerVar ( REQUEST_STRUCT *rqptr, char *VarName, char *VarParam ) { #define SIZEOF_TIME_STRING 64 static $DESCRIPTOR (NumberFaoDsc, "!UL\0"); static $DESCRIPTOR (StringFaoDsc, "!AZ\0"); static $DESCRIPTOR (ReportFaoDsc, "!AZ ... <tt>!AZ</tt>\0"); static $DESCRIPTOR (Report2FaoDsc, "<!!-- !AZ \"!AZ\"-->\0"); static $DESCRIPTOR (StringDsc, ""); int status, DocumentDepth, StatusCode; int64 Time64; unsigned short Length; char *cptr, *sptr, *zptr; char String [SSI_STRING_SIZE], LoCaseName [256]; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiGetServerVar() !AZ \'!AZ\'", VarName, VarParam); tkptr = rqptr->SsiTaskPtr; zptr = (sptr = LoCaseName) + sizeof(LoCaseName)-1; for (cptr = VarName; *cptr && sptr < zptr; *sptr++ = tolower(*cptr++)); *sptr = '\0'; cptr = LoCaseName; switch (*cptr) { case 'c' : /* for all these also match the trailing null */ if (MATCH11 (cptr, "compliance")) { cptr = VmGetHeap (rqptr, 16); StringDsc.dsc$a_pointer = cptr; StringDsc.dsc$w_length = 15; sys$fao (&NumberFaoDsc, &Length, &StringDsc, tkptr->ComplianceLevel); return (cptr); } if (MATCH8 (cptr, "created")) { cptr = VmGetHeap (rqptr, SIZEOF_TIME_STRING); SsiTimeString (rqptr, &tkptr->RootDocumentTaskPtr->FileContentPtr->CdtTime64, VarParam, cptr, SIZEOF_TIME_STRING); return (cptr); } break; case 'd' : if (MATCH11 (cptr, "date_local")) { cptr = VmGetHeap (rqptr, SIZEOF_TIME_STRING); SsiTimeString (rqptr, NULL, VarParam, cptr, SIZEOF_TIME_STRING); return (cptr); } if (MATCH9 (cptr, "date_gmt")) { sys$gettim (&Time64); TimeAdjustToGMT (&Time64); cptr = VmGetHeap (rqptr, SIZEOF_TIME_STRING+5); SsiTimeString (rqptr, &Time64, VarParam, cptr, SIZEOF_TIME_STRING); strcat (cptr, " GMT"); return (cptr); } if (MATCH15 (cptr, "document_depth")) { if (tkptr->DocumentDepthPtr) return (tkptr->DocumentDepthPtr); StringDsc.dsc$a_pointer = String; StringDsc.dsc$w_length = sizeof(String)-1; DocumentDepth = LIST_GET_COUNT (&rqptr->SsiTaskList); sys$fao (&NumberFaoDsc, &Length, &StringDsc, DocumentDepth); tkptr->DocumentDepthPtr = cptr = VmGetHeap (rqptr, Length); strcpy (cptr, String); return (cptr); } if (MATCH14 (cptr, "document_name")) return (SsiGetCgiVar (rqptr, "PATH_TRANSLATED")); if (MATCH14 (cptr, "document_root")) { if (tkptr->DocumentRootPtr) return (tkptr->DocumentRootPtr); tkptr->DocumentRootPtr = sptr = VmGetHeap (rqptr, rqptr->rqHeader.PathInfoLength+1); zptr = NULL; cptr = rqptr->rqHeader.PathInfoPtr; while (*cptr) { if (*cptr == '/') zptr = sptr; *sptr++ = *cptr++; } if (zptr) *(zptr+1) = '\0'; else *tkptr->DocumentRootPtr = '\0'; return (tkptr->DocumentRootPtr); } if (MATCH13 (cptr, "document_uri")) return (rqptr->rqHeader.PathInfoPtr); break; case 'e' : if (!rqptr->RedirectErrorStatusCode) return (NULL); StringDsc.dsc$a_pointer = String; StringDsc.dsc$w_length = sizeof(String)-1; if (MATCH11 (cptr, "error_line")) return (SsiGetCgiVar (rqptr, "FORM_ERROR_LINE")); if (MATCH13 (cptr, "error_module")) return (SsiGetCgiVar (rqptr, "FORM_ERROR_MODULE")); if (MATCH13 (cptr, "error_report")) { if (!(cptr = SsiGetCgiVar (rqptr, "FORM_ERROR_TEXT"))) return (NULL); /* return if it's an ErrorGeneral() error */ if (!(sptr = SsiGetCgiVar (rqptr, "FORM_ERROR_ABOUT"))) return (cptr); if (!*sptr) return (cptr); sys$fao (&ReportFaoDsc, &Length, &StringDsc, cptr, sptr); cptr = VmGetHeap (rqptr, Length); strcpy (cptr, String); return (cptr); } if (MATCH14 (cptr, "error_report2")) { if (!(cptr = SsiGetCgiVar (rqptr, "FORM_ERROR_VMS"))) return (""); /* return if it's an ErrorGeneral() error */ if (!(sptr = SsiGetCgiVar (rqptr, "FORM_ERROR_ABOUT2"))) return (""); if (!*sptr) return (cptr); sys$fao (&Report2FaoDsc, &Length, &StringDsc, cptr, sptr); cptr = VmGetHeap (rqptr, Length); strcpy (cptr, String); return (cptr); } if (MATCH14 (cptr, "error_report3")) { if (!(cptr = SsiGetCgiVar (rqptr, "FORM_ERROR_TEXT2"))) return (""); return (cptr); } if (MATCH11 (cptr, "error_type")) return (SsiGetCgiVar (rqptr, "FORM_ERROR_TYPE")); if (MATCH16 (cptr, "error_status_code")) return (SsiGetCgiVar (rqptr, "FORM_ERROR_STATUS")); if (MATCH16 (cptr, "error_status_class")) { if (!(cptr = SsiGetCgiVar (rqptr, "FORM_ERROR_STATUS"))) return (NULL); StatusCode = atoi(cptr); sys$fao (&NumberFaoDsc, &Length, &StringDsc, StatusCode / 100); cptr = VmGetHeap (rqptr, Length); strcpy (cptr, String); return (cptr); } if (MATCH16 (cptr, "error_status_text")) return (SsiGetCgiVar (rqptr, "FORM_ERROR_STATUS_TEXT")); if (MATCH16 (cptr, "error_status_explanation")) return (SsiGetCgiVar (rqptr, "FORM_ERROR_STATUS_EXPLANATION")); if (MATCH11 (cptr, "error_type")) return (SsiGetCgiVar (rqptr, "FORM_ERROR_TYPE")); if (MATCH10 (cptr, "error_uri")) return (SsiGetCgiVar (rqptr, "FORM_ERROR_URI")); break; case 'f' : if (MATCH10 (cptr, "file_name")) return (SsiGetCgiVar (rqptr, "PATH_TRANSLATED")); break; case 'h' : /* an OSU-compliant variable */ if (MATCH8 (cptr, "hw_name")) return (SysInfo.HwName); break; case 'l' : if (MATCH13 (cptr, "last_modified")) { cptr = VmGetHeap (rqptr, SIZEOF_TIME_STRING); SsiTimeString (rqptr, &tkptr->RootDocumentTaskPtr->FileContentPtr->RdtTime64, VarParam, cptr, SIZEOF_TIME_STRING); return (cptr); } break; case 'g' : /* an OSU-compliant variable */ if (MATCH7 (cptr, "getenv")) { if (!(sptr = getenv (VarParam))) return (NULL); cptr = VmGetHeap (rqptr, Length = strlen(sptr)+1); memcpy (cptr, sptr, Length); return (cptr); } break; case 'q' : if (MATCH16 (cptr, "query_string_unescaped")) return (SsiGetCgiVar (rqptr, "QUERY_STRING")); break; case 'p' : if (MATCH16 (cptr, "parent_file_name")) { if (tkptr->ParentDocumentTaskPtr) return (tkptr->ParentDocumentTaskPtr->FileContentPtr->FileName); else return (""); } break; case 's' : /* an OSU-compliant variable */ if (MATCH15 (cptr, "server_version")) return (SsiGetCgiVar (rqptr, "SERVER_SOFTWARE")); break; case 't' : if (MATCH14 (cptr, "the_file_name")) return (tkptr->TheFileNameVar); if (MATCH15 (cptr, "this_file_name")) return (tkptr->FileContentPtr->FileName); break; case 'v' : /* an OSU-compliant variable */ if (MATCH12 (cptr, "vms_version")) return (SysInfo.Version); break; } return (SsiGetCgiVar (rqptr, cptr)); } /*****************************************************************************/ /* Search the CGI-assigned variables. If found return a pointer to a symbol's value string. If no such symbol found return a NULL. */ char* SsiGetCgiVar ( REQUEST_STRUCT *rqptr, char *VarName ) { unsigned short Length; char *cptr, *sptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiGetCgiVar() !&Z", VarName); cptr = rqptr->SsiTaskPtr->CgiBufferPtr; for (;;) { if (!(Length = *(USHORTPTR)cptr)) break; for (sptr = cptr+sizeof(short)+DclCgiVariablePrefixLength; *sptr && *sptr != '='; sptr++); *sptr = '\0'; if (strsame (cptr+sizeof(short)+DclCgiVariablePrefixLength, VarName, -1)) { *sptr = '='; break; } *sptr = '='; cptr += Length + sizeof(short); } if (Length) { /* found */ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z", sptr+1); return (sptr+1); } /* not found */ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z", NULL); return (NULL); } /*****************************************************************************/ /* Search the user-assigned variables. If found return a pointer to a symbol's value string. If no such symbol found return a NULL. */ char* SsiGetUserVar ( REQUEST_STRUCT *rqptr, char *VarName ) { DICT_STRUCT *dicptr; DICT_ENTRY_STRUCT *denptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiGetUserVar() !&Z", VarName); dicptr = rqptr->SsiTaskPtr->UserDictPtr; if (denptr = DictLookup (dicptr, DICT_TYPE_SSI, VarName, -1)) return (DICT_GET_VALUE(denptr)); return (NULL); } /*****************************************************************************/ /* Used by flow-control statements that do an evaluation to make a decision. */ BOOL SsiEvaluate (REQUEST_STRUCT *rqptr) { BOOL NegateResult, Result; int NumberOne, NumberTwo; char *dptr; char ValueOne [SSI_STRING_SIZE], ValueTwo [SSI_STRING_SIZE]; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiEvaluate() !&Z", rqptr->SsiTaskPtr->StatementBeginPtr); tkptr = rqptr->SsiTaskPtr; Result = false; dptr = rqptr->SsiTaskPtr->StatementBeginPtr; while (*dptr && !isspace(*dptr)) dptr++; while (*dptr) { if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z", dptr); if (rqptr->SsiTaskPtr->StopProcessing) return (false); while (*dptr && isspace(*dptr)) dptr++; if (!*dptr) break; if (*dptr == '!') { dptr++; NegateResult = true; } else NegateResult = false; if (strsame (dptr, "value=", 6) || strsame (dptr, "VAR=", 4) || strsame (dptr, "PAR=", 4)) { dptr += SsiGetTagValue (rqptr, dptr, ValueOne, sizeof(ValueOne)); if (tkptr->StopProcessing) { Result = false; break; } if (isdigit(ValueOne[0])) Result = atoi(ValueOne); else Result = ValueOne[0]; if (NegateResult) Result = !Result; continue; } if (strsame (dptr, "SRCH=", 5)) { dptr += SsiGetTagValue (rqptr, dptr, ValueTwo, sizeof(ValueTwo)); if (tkptr->StopProcessing) { Result = false; break; } Result = StringMatchGreedy (rqptr, ValueOne, ValueTwo); } else if (strsame (dptr, "EQS=", 4)) { dptr += SsiGetTagValue (rqptr, dptr, ValueTwo, sizeof(ValueTwo)); if (tkptr->StopProcessing) { Result = false; break; } Result = strsame (ValueOne, ValueTwo, -1); } else if (strsame (dptr, "EQ=", 3)) { dptr += SsiGetTagValue (rqptr, dptr, ValueTwo, sizeof(ValueTwo)); if (tkptr->StopProcessing) { Result = false; break; } NumberOne = NumberTwo = 0; NumberOne = atoi(ValueOne); NumberTwo = atoi(ValueTwo); Result = (NumberOne == NumberTwo); } else if (strsame (dptr, "GT=", 3)) { dptr += SsiGetTagValue (rqptr, dptr, ValueTwo, sizeof(ValueTwo)); if (tkptr->StopProcessing) { Result = false; break; } NumberOne = NumberTwo = 0; NumberOne = atoi(ValueOne); NumberTwo = atoi(ValueTwo); Result = (NumberOne > NumberTwo); } else if (strsame (dptr, "LT=", 3)) { dptr += SsiGetTagValue (rqptr, dptr, ValueTwo, sizeof(ValueTwo)); if (tkptr->StopProcessing) { Result = false; break; } NumberOne = NumberTwo = 0; NumberOne = atoi(ValueOne); NumberTwo = atoi(ValueTwo); Result = (NumberOne < NumberTwo); } else { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_TAG_UNKNOWN), FI_LI); Result = false; break; } if (NegateResult) Result = !Result; if (!Result) break; } if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&?TRUE\rFALSE\r", Result); return (Result); } /*****************************************************************************/ /* Using the locale formatting capabilities of function strftime(), output the time represented by the specified VMS quadword, binary time. If 'Time64Ptr' is NULL then default to the current time. Returns number of characters placed into 'TimeString', or zero if an error. */ int SsiTimeString ( REQUEST_STRUCT *rqptr, ulong *Time64Ptr, char *TimeFmtPtr, char *TimeString, int SizeOfTimeString ) { static BOOL InitDone; int Length; int64 Time64; struct tm UnixTime; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiTimeString() !%D \'!AZ\'", Time64Ptr, TimeFmtPtr); if (!TimeFmtPtr) TimeFmtPtr = rqptr->SsiTaskPtr->TimeFmtPtr; else if (!TimeFmtPtr[0]) TimeFmtPtr = rqptr->SsiTaskPtr->TimeFmtPtr; if (!Time64Ptr) sys$gettim (Time64Ptr = &Time64); TimeVmsToUnix (Time64Ptr, &UnixTime); if (!InitDone) { setlocale (LC_TIME, ""); InitDone = true; } if (!(Length = strftime (TimeString, SizeOfTimeString, TimeFmtPtr, &UnixTime))) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_DATE_TIME), FI_LI); return (0); } return (Length); } /*****************************************************************************/ /* Get the value of a tag parameter (e.g. tag_name="value"). Allows variable substitution into tag values, a la Apache. Tag values can have variable values substituted into them using a leading "{" and trailing '}' character sequence with the variable name between. Otherwise reserved characters may be escaped using a leading backslash. If comma-separated numbers are appended to a substitution variable these become starting and ending indices, extracting that range from the variable (a single number sets the count from zero). Returns the number of characters scanned to get the value; note that this is not necessarily the same as the number of characters in the variable value! */ int SsiGetTagValue ( REQUEST_STRUCT *rqptr, char *String, char *Value, int SizeOfValue ) { BOOL IsVarEquals, Negate, VarDidSubstitution, VarHadSpace, VarQuoted; int ExtractCount, StartIndex; char *cptr, *sptr, *vptr, *vzptr, *zptr, *ValueEqualsPtr, *ValueSpacePtr, *VarEqualsPtr; char FormatString [SSI_STRING_SIZE], VarName [256]; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiGetTagValue() !&Z", String); if (!*(cptr = String)) return (0); VarQuoted = VarDidSubstitution = IsVarEquals = VarHadSpace = false; ValueEqualsPtr = VarEqualsPtr = NULL; zptr = (sptr = Value) + SizeOfValue; if (String[0] == '=') { /* this is the second bite of a #echo var="name=fmt" */ VarQuoted = true; } else if (strsame (String, "VAR=", 4)) IsVarEquals = true; for (cptr = String; *cptr && *cptr != '=' && *cptr != '\"'; cptr++); if (*cptr == '=') cptr++; if (*cptr == '\"') { cptr++; VarQuoted = true; } while (((*cptr && VarQuoted && *cptr != '\"') || (*cptr && !VarQuoted && !isspace(*cptr))) && sptr < zptr) { if (*cptr != '{') { /*********************/ /* literal character */ /*********************/ if (*cptr == '=') { VarEqualsPtr = cptr; ValueEqualsPtr = sptr; } else if (!VarEqualsPtr && (*cptr == ' ' || *cptr == '\t')) VarHadSpace = true; /* escape character? */ if (*cptr == '\\') cptr++; if (*cptr && sptr < zptr) *sptr++ = *cptr++; continue; } /*************************/ /* variable substitution */ /*************************/ VarDidSubstitution = true; cptr++; vzptr = (vptr = VarName) + sizeof(VarName); while (*cptr && (isalnum(*cptr) || *cptr == '_' || *cptr == '$') && vptr < vzptr) *vptr++ = *cptr++; *vptr = '\0'; if (*cptr == ',') { cptr++; StartIndex = 0; ExtractCount = 999999999; if (isdigit(*cptr)) { /* substring */ StartIndex = atoi(cptr); while (isdigit(*cptr)) cptr++; if (*cptr == ',') cptr++; if (isdigit(*cptr)) { /* two numbers provide a start index and an extract count */ ExtractCount = atoi(cptr); while (isdigit(*cptr)) cptr++; if (*cptr == ',') cptr++; } else { /* one number extracts from the start of the string */ ExtractCount = StartIndex; StartIndex = 0; } } if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!UL !UL", StartIndex, ExtractCount); if (isalpha(*cptr)) { /* "function" on variable */ if (strsame (cptr, "length", 6)) { static $DESCRIPTOR (LengthFaoDsc, "!UL\0"); int Length; char String [32]; $DESCRIPTOR (StringDsc, String); vptr = SsiGetVar (rqptr, VarName, NULL, false); if (rqptr->SsiTaskPtr->StopProcessing) return (cptr - String); Length = 0; while (StartIndex-- && *vptr) vptr++; while (ExtractCount-- && *vptr) { vptr++; Length++; } sys$fao (&LengthFaoDsc, 0, &StringDsc, Length); vptr = String; while (*vptr && sptr < zptr) *sptr++ = *vptr++; while (isalpha(*cptr)) cptr++; } else if (strsame (cptr, "exists", 6)) { vptr = SsiGetVar (rqptr, VarName, NULL, true); if (vptr) vptr = "true"; else vptr = ""; while (*vptr && sptr < zptr) *sptr++ = *vptr++; while (isalpha(*cptr)) cptr++; } else { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_VARIABLE), FI_LI); return (cptr - String); } } else { vptr = SsiGetVar (rqptr, VarName, NULL, false); if (rqptr->SsiTaskPtr->StopProcessing) return (cptr - String); while (StartIndex-- && *vptr) vptr++; while (ExtractCount-- && *vptr && sptr < zptr) *sptr++ = *vptr++; } } else { /* get all of variable */ vptr = SsiGetVar (rqptr, VarName, NULL, false); if (rqptr->SsiTaskPtr->StopProcessing) return (cptr - String); while (*vptr && sptr < zptr) *sptr++ = *vptr++; } if (*cptr != '}') { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_SSI_VARIABLE), FI_LI); return (cptr - String); } cptr++; } if (sptr >= zptr) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_GENERAL_OVERFLOW), FI_LI); return (cptr - String); } *sptr = '\0'; if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z", Value); if (*cptr == '\"') cptr++; if (rqptr->SsiTaskPtr->ComplianceLevel >= SSI_VAR_FMT_COMPLIANCE_LEVEL && IsVarEquals && VarQuoted && !(VarDidSubstitution || VarHadSpace)) { if (ValueEqualsPtr) { /* terminate variable name at the equate symbol */ *(sptr = ValueEqualsPtr) = '\0'; /* get the format string from immediately following the equate */ cptr = VarEqualsPtr + 1; zptr = (sptr = FormatString) + sizeof(FormatString); while (*cptr && *cptr != '\"' && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_GENERAL_OVERFLOW), FI_LI); return (cptr - String); } *sptr = '\0'; vptr = SsiGetVar (rqptr, Value, FormatString, false); } else vptr = SsiGetVar (rqptr, Value, NULL, false); if (rqptr->SsiTaskPtr->StopProcessing) return (cptr - String); zptr = (sptr = Value) + SizeOfValue; while (*vptr && sptr < zptr) *sptr++ = *vptr++; if (sptr >= zptr) { SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_GENERAL_OVERFLOW), FI_LI); return (cptr - String); } *sptr = '\0'; } if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z", Value); return (cptr - String); } /*****************************************************************************/ /* Get a 'FILE="file_name"' or a 'VIRTUAL="file_name"'. Maximum number of characters allowed in value is 256. Returns the number of characters scanned to get the value. If the file name does not contain a device/directory (i.e. is specified as if in the current directory) then the device/directory or the current document is prepended to the file name. */ int SsiGetFileSpec ( REQUEST_STRUCT *rqptr, char *String, char *FileName, int SizeOfFileName ) { int len; char *cptr, *sptr, *zptr; char Scratch [256], VirtualFileName [256]; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiGetFileSpec() !&Z", String); tkptr = rqptr->SsiTaskPtr; len = SsiGetTagValue (rqptr, String, Scratch, sizeof(Scratch)); if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!UL !&Z", len, Scratch); if (TOUP(String[0]) == 'V') { MapUrl_VirtualPath (rqptr->rqHeader.PathInfoPtr, Scratch, VirtualFileName, sizeof(VirtualFileName)); if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z", VirtualFileName); FileName[0] = '\0'; cptr = MapUrl_Map (VirtualFileName, 0, FileName, SizeOfFileName, NULL, 0, NULL, 0, NULL, 0, NULL, rqptr, NULL); if (!cptr[0]) { FileName[0] = '\0'; SsiProblem (rqptr, "!AZ", cptr+1, FI_LI); return (len); } if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!UL !&Z", len, FileName); return (len); } else { zptr = (sptr = FileName) + SizeOfFileName; for (cptr = Scratch; *cptr && *cptr != ':' && *cptr != '[' && sptr < zptr; *sptr++ = *cptr++); if (*cptr) { /* looks like a full specification, just continue on */ while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) { FileName[0] = '\0'; SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_GENERAL_OVERFLOW), FI_LI); return (len); } *sptr = '\0'; return (len); } zptr = (sptr = FileName) + SizeOfFileName; if (isupper(Scratch[0])) { for (cptr = tkptr->FileContentPtr->FileName; *cptr && *cptr != ']' && !SAME2(cptr,'][') && sptr < zptr; *sptr++ = TOUP(*cptr++)); if (*cptr == ']' && sptr < zptr) *sptr++ = *cptr++; for (cptr = Scratch; *cptr && sptr < zptr; *sptr++ = TOUP(*cptr++)); } else { for (cptr = tkptr->FileContentPtr->FileName; *cptr && *cptr != ']' && !SAME2(cptr,'][') && sptr < zptr; *sptr++ = TOLO(*cptr++)); if (*cptr == ']' && sptr < zptr) *sptr++ = *cptr++; for (cptr = Scratch; *cptr && sptr < zptr; *sptr++ = TOLO(*cptr++)); } if (sptr >= zptr) { FileName[0] = '\0'; SsiProblem (rqptr, "!AZ", MsgFor(rqptr,MSG_GENERAL_OVERFLOW), FI_LI); return (len); } *sptr = '\0'; if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z", FileName); return (len); } } /*****************************************************************************/ /* Retrieve and display the specified file's specified attribute (size, modification time, etc.) This function uses the ACP-QIO interface detailed in the "OpenVMS I/O User's Reference Manual". */ SsiFileDetails ( REQUEST_STRUCT *rqptr, int FileDetailsItem ) { int status, FileNameLength; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiFileDetails() !AZ !UL \'!AZ\'", rqptr->SsiTaskPtr->ScratchFileName, FileDetailsItem, rqptr->SsiTaskPtr->FormatString); tkptr = rqptr->SsiTaskPtr; /* initialize file variable data */ tkptr->TheFileNameVar[0] = '\0'; tkptr->FileDetailsItem = FileDetailsItem; FileNameLength = strlen(tkptr->ScratchFileName); AuthAccessEnable (rqptr, tkptr->ScratchFileName, AUTH_ACCESS_READ); OdsParse (&tkptr->DetailsOds, tkptr->ScratchFileName, FileNameLength, NULL, 0, 0, &SsiFileDetailsParseAst, rqptr); AuthAccessEnable (rqptr, 0, 0); } /*****************************************************************************/ /* AST called from SsiFileDetails() when asynchronous parse completes. If status OK set up and queue an ACP QIO to get file size and revision date/time, ASTing to SsiFileDetailsAcpInfoAst(). */ SsiFileDetailsParseAst (REQUEST_STRUCT *rqptr) { static $DESCRIPTOR (DeviceDsc, ""); int status; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiFileDetailsParseAst() !&F sts:!&S stv:!&S", &SsiFileDetailsParseAst, rqptr->SsiTaskPtr->DetailsOds.Fab.fab$l_sts, rqptr->SsiTaskPtr->DetailsOds.Fab.fab$l_stv); tkptr = rqptr->SsiTaskPtr; if (VMSnok (status = tkptr->DetailsOds.Fab.fab$l_sts)) { SsiProblem (rqptr, "!&m", status, FI_LI); SsiEnd (rqptr); return; } /* get the variable file name */ strcpy (tkptr->TheFileNameVar, tkptr->DetailsOds.ExpFileName); if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&Z", tkptr->TheFileNameVar); if (tkptr->DetailsOds.Nam_fnb & NAM$M_SEARCH_LIST && !tkptr->SearchListCount++) { /*******************************/ /* search to get actual device */ /*******************************/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SEARCH-LIST"); AuthAccessEnable (rqptr, tkptr->DetailsOds.ExpFileName, AUTH_ACCESS_READ); OdsSearch (&tkptr->DetailsOds, &SsiFileDetailsParseAst, rqptr); AuthAccessEnable (rqptr, 0, 0); return; } /************/ /* ACP info */ /************/ AuthAccessEnable (rqptr, tkptr->DetailsOds.ExpFileName, AUTH_ACCESS_READ); OdsFileAcpInfo (&tkptr->DetailsOds, &SsiFileDetailsAcpInfoAst, rqptr); AuthAccessEnable (rqptr, 0, 0); } /****************************************************************************/ /* AST called from SsiFileDetailsParseAST() when ACP QIO completes. If status indicates no such file then call any file open error processing function originally supplied, otherwise report the error. If status OK provide the request file details. This function uses the ACP-QIO interface detailed in the "OpenVMS I/O User's Reference Manual", and is probably as fast as we can get for this type of file system functionality! */ SsiFileDetailsAcpInfoAst (REQUEST_STRUCT *rqptr) { static $DESCRIPTOR (AbbrevOneByteFaoDsc, "!UL byte"); static $DESCRIPTOR (AbbrevBytesFaoDsc, "!UL bytes"); static $DESCRIPTOR (AbbrevOnekByteFaoDsc, "!UL kbyte"); static $DESCRIPTOR (AbbrevkBytesFaoDsc, "!UL kbytes"); static $DESCRIPTOR (AbbrevOneMByteFaoDsc, "!UL Mbyte"); static $DESCRIPTOR (AbbrevMBytesFaoDsc, "!UL Mbytes"); static $DESCRIPTOR (OneBlockFaoDsc, "!UL block"); static $DESCRIPTOR (BlocksFaoDsc, "!UL blocks"); static $DESCRIPTOR (BytesFaoDsc, "!AZ bytes"); static $DESCRIPTOR (NumberFaoDsc, "!UL"); int status, NumBytes, SizeInBytes; int64 ScratchTime64; unsigned short Length; unsigned long EndOfFileVbn; char *cptr, *sptr, *zptr, *FormatPtr; char Scratch [256], String [256]; SSI_TASK *tkptr; $DESCRIPTOR (StringDsc, String); $DESCRIPTOR (ScratchDsc, Scratch); /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiFileDetailsAcpInfoAst() !&F !&S", &SsiFileDetailsAcpInfoAst, rqptr->SsiTaskPtr->DetailsOds.FileQio.IOsb.Status); tkptr = rqptr->SsiTaskPtr; /* first, deassign the channel allocated by OdsFileAcpInfo() */ sys$dassgn (tkptr->DetailsOds.FileQio.AcpChannel); if ((status = tkptr->DetailsOds.FileQio.IOsb.Status) == SS$_NOSUCHFILE) status = RMS$_FNF; if (VMSnok (status)) { if (status == RMS$_FNF && tkptr->FormatString[0] == '?') { /* ignore file not found, just continue */ tkptr->TheFileNameVar[0] = '\0'; SysDclAst (&SsiParse, rqptr); return; } else { SsiProblem (rqptr, "!&m", status, FI_LI); SsiEnd (rqptr); return; } } /*******************/ /* process details */ /*******************/ if (tkptr->FileDetailsItem == FILE_LAST_MODIFIED) { /*****************/ /* last-modified */ /*****************/ if (!tkptr->LastModifiedTime64) { /* first file */ tkptr->LastModifiedTime64 = tkptr->DetailsOds.FileQio.RdtTime64; SysDclAst (&SsiParse, rqptr); return; } if (tkptr->LastModifiedTime64 == tkptr->DetailsOds.FileQio.RdtTime64) { /* times are identical */ SysDclAst (&SsiParse, rqptr); return; } /* if a positive time results the file has been modified */ ScratchTime64 = tkptr->LastModifiedTime64 - tkptr->DetailsOds.FileQio.RdtTime64; if (ScratchTime64 >= 0) { /* positive time, current content is later than this file's RDT */ SysDclAst (&SsiParse, rqptr); return; } if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "!&S", status); if (ScratchTime64 < 0) { /* current contents are earlier than this file's RDT */ tkptr->LastModifiedTime64 = tkptr->DetailsOds.FileQio.RdtTime64; tkptr->LastModifiedTime64 = tkptr->DetailsOds.FileQio.RdtTime64; SysDclAst (&SsiParse, rqptr); return; } else { SsiProblem (rqptr, "!&m", status, FI_LI); SsiEnd (rqptr); return; } } if (tkptr->FormatString[0] == '?') { /* no output, just checking existance of file */ SysDclAst (&SsiParse, rqptr); return; } if (tkptr->FileDetailsItem == FILE_FCREATED) { /*********************/ /* date/time created */ /*********************/ /* output creation timestamp */ if (!SsiTimeString (rqptr, &tkptr->DetailsOds.FileQio.CdtTime64, tkptr->FormatString, String, sizeof(String))) { SsiEnd (rqptr); return; } Length = strlen(String); if (tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex]) { NetWriteBuffered (rqptr, &SsiParse, String, Length); tkptr->TraceOutput = true; } else SysDclAst (rqptr, &SsiParse); return; } if (tkptr->FileDetailsItem == FILE_FLASTMOD) { /*********************/ /* date/time revised */ /*********************/ /* output creation timestamp */ if (!SsiTimeString (rqptr, &tkptr->DetailsOds.FileQio.RdtTime64, tkptr->FormatString, String, sizeof(String))) { SsiEnd (rqptr); return; } Length = strlen(String); if (tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex]) { NetWriteBuffered (rqptr, &SsiParse, String, Length); tkptr->TraceOutput = true; } else SysDclAst (rqptr, &SsiParse); return; } if (tkptr->FileDetailsItem == FILE_FSIZE) { /*************/ /* file size */ /*************/ if (tkptr->FormatString[0]) FormatPtr = tkptr->FormatString; else FormatPtr = rqptr->SsiTaskPtr->SizeFmtPtr; EndOfFileVbn = ((tkptr->DetailsOds.FileQio.RecAttr.fat$l_efblk & 0xffff) << 16) | ((tkptr->DetailsOds.FileQio.RecAttr.fat$l_efblk & 0xffff0000) >> 16); if (EndOfFileVbn <= 1) SizeInBytes = tkptr->DetailsOds.FileQio.RecAttr.fat$w_ffbyte; else SizeInBytes = ((EndOfFileVbn-1) << 9) + tkptr->DetailsOds.FileQio.RecAttr.fat$w_ffbyte; if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "vbn:!UL ffb:!UL bytes:!UL", EndOfFileVbn, tkptr->DetailsOds.FileQio.RecAttr.fat$w_ffbyte, SizeInBytes); if (TOUP(FormatPtr[0]) == 'A') /* "abbrev" */ { if (SizeInBytes < 1024) { if (SizeInBytes == 1) sys$fao (&AbbrevOneByteFaoDsc, &Length, &StringDsc, SizeInBytes); else sys$fao (&AbbrevBytesFaoDsc, &Length, &StringDsc, SizeInBytes); } else if (SizeInBytes < 1048576) { if ((NumBytes = SizeInBytes / 1024) == 1) sys$fao (&AbbrevOnekByteFaoDsc, &Length, &StringDsc, NumBytes); else sys$fao (&AbbrevkBytesFaoDsc, &Length, &StringDsc, NumBytes); } else { if ((NumBytes = SizeInBytes / 1048576) == 1) sys$fao (&AbbrevOneMByteFaoDsc, &Length, &StringDsc, NumBytes); else sys$fao (&AbbrevMBytesFaoDsc, &Length, &StringDsc, NumBytes); } String[Length] = '\0'; } else if (TOUP(FormatPtr[0]) == 'B' && TOUP(FormatPtr[1]) == 'Y') /* "bytes" */ { sys$fao (&NumberFaoDsc, &Length, &ScratchDsc, SizeInBytes); Scratch[Length] = '\0'; sptr = String; cptr = Scratch; while (Length--) { *sptr++ = *cptr++; if (Length && !(Length % 3)) *sptr++ = ','; } for (cptr = " bytes"; *cptr; *sptr++ = *cptr++); *sptr = '\0'; Length = sptr - String; } else if (TOUP(FormatPtr[0]) == 'B' && TOUP(FormatPtr[1]) == 'L') /* "blocks" */ { if (EndOfFileVbn == 1) sys$fao (&OneBlockFaoDsc, &Length, &StringDsc, EndOfFileVbn); else sys$fao (&BlocksFaoDsc, &Length, &StringDsc, EndOfFileVbn); String[Length] = '\0'; } if (tkptr->FlowControlIsExecuting[tkptr->FlowControlIndex]) { NetWriteBuffered (rqptr, &SsiParse, String, Length); tkptr->TraceOutput = true; } else SysDclAst (rqptr, &SsiParse); return; } SsiProblem (rqptr, "!AZ", ErrorSanityCheck, FI_LI); SsiEnd (rqptr); return; } /*****************************************************************************/ /* Generate a general error, with explanation about the pre-processor error. */ SsiProblem ( REQUEST_STRUCT *rqptr, ... ) { static char ErrorMessageFao [] = "\n\ <h2><font color=\"#ff0000\"><u>!AZ</u></font></h2>\n\ !&@<!!-- module: !AZ line: !UL --> (!AZ !UL)!&@<!!-- !AZ -->\n"; int status, argcnt; unsigned long FaoVector [32]; unsigned long *vecptr; va_list argptr; SSI_TASK *tkptr; /*********/ /* begin */ /*********/ va_count (argcnt); if (WATCHMOD (rqptr, WATCH_MOD_SSI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_SSI, "SsiProblem() !UL", argcnt); if (argcnt > 32+1) { ErrorNoticed (rqptr, SS$_OVRMAXARG, NULL, FI_LI); return (SS$_OVRMAXARG); } tkptr = rqptr->SsiTaskPtr; tkptr->StopProcessing = true; vecptr = FaoVector; if (tkptr->ErrMsgPtr && tkptr->ErrMsgPtr[0]) *vecptr++ = tkptr->ErrMsgPtr; else *vecptr++ = MsgFor(rqptr,MSG_SSI_ERROR); va_start (argptr, rqptr); for (argcnt -= 1; argcnt; argcnt--) *vecptr++ = va_arg (argptr, unsigned long); va_end (argptr); *vecptr++ = MsgFor(rqptr,MSG_SSI_LINE); *vecptr++ = tkptr->StatementLineNumber; if (tkptr->StatementBeginPtr && tkptr->StatementBeginPtr[0]) { *vecptr++ = " ... <nobr>\\<tt>!&;AZ</tt>\\</nobr>"; *vecptr++ = tkptr->StatementBeginPtr; } else *vecptr++ = ""; *vecptr++ = tkptr->FileContentPtr->FileName; status = FaolToNet (rqptr, ErrorMessageFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); } /*****************************************************************************/