[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]
/*****************************************************************************/ /* CGIutl.c A CLI utility to assist at the DCL level with generating HTTP responses, returning content and with the handling of POSTed requests. Intended to be used within DCL procedures to manipulate the CGI environment. Assign a foreign verb, $ CGIUTL = "$HT_EXE:CGIUTL" All DCL symbols created by or supplied to this utility MUST be global symbols. It does not deal with local symbols at all. HAVING DCL PROBLEMS WITH SYMBOL SIZE? ------------------------------------- Although CLI symbols may have maximum capacities of 1023 (earlier than VMS V7.3-2) or 8191 (V7.3-2 and later) it can often be problematic accessing this full capacity from the DCL command line. If this is an issue try using the /MAXSYM=<integer> qualifier to set the maximum number of characters CGIUTL places into a single symbol. Perhaps /MAXSYM=900 (as ~980 seems to be a 'magic' number for DCL), or even /MAXSYM=255. Needless-to-say, there are also SYSGEN parameters controlling memory allocations to such purposes. CLISYMTBL is a dynamic parameter directly related to the symbol table size. CTLPAGES controls the number of pages allocated to the P1 (process') pool. See note against %LIB-F-INVARG described in main()! DECODING A REQUEST BODY TO DCL SYMBOLS -------------------------------------- The /URLDECODE qualifier results in a form-URL-encoded POSTed request body being decoded and the contents of each field being placed into DCL symbols named using that field. HTML form field names may contain characters not allowed within DCL symbol names, therefore when constructing the symbol name non-alpha-numeric characters in the field name have underscores substituted in the symbol name. Once the request body has been decoded the DCL procedure can manipulate the contents quite simply. Here's the command line: $ CGIUTL /URLDECODE [/SYMBOL|/OUTPUT] For example the following form input field <INPUT TYPE=text NAME=text_input_one SIZE=40> would have the corresponding DCL symbol name assigned CGIUTL_TEXT_INPUT_ONE == "blah-blah-blah-blah" Field contents greater than the capacity of a single DCL symbol (255 on <=6.n and 1023 on >=7.n) have the contents distributed across multiple symbol names. Each of these fields comprises the basic name with a dollar symbol and an ascending integer. A symbol with the integer zero contains the total count of the contents of all of the symbols. For example, the following text-area field may have had 900 characters entered. <TEXTAREA NAME=text_area_input ROWS=14 COLS=80> </TEXTAREA> Assuming a maximum symbol capacity of 255 characters the contents would be distributed across 4 symbols, with the additional count symbol, as follows: CGIUTL_TEXT_AREA_INPUT$0 == "900" CGIUTL_TEXT_AREA_INPUT$1 == "blah-blah-blah-blah...blah-blah" CGIUTL_TEXT_AREA_INPUT$2 == "blah-blah-blah-blah...blah-blah" CGIUTL_TEXT_AREA_INPUT$3 == "blah-blah-blah-blah...blah-blah" CGIUTL_TEXT_AREA_INPUT$4 == "blah-blah-blah" NOTE: There was a bug prior to version 1.5 which resulted in the $1, $2, etc. ~~~~~ symbol names being created as _1, _2, etc. This has been rectified with backward compatibility supplied via adding the /BUGSYM qualifier to scripts that require it. Apologies for any inconvenience. Where form field names are duplicated, as can be done with checkboxes, etc., CGUTL attempts to create multiple symbols representing these. For fields containing less than the maximum symbol value contents (255 or 1023 depending on the VMS version) this is quite successful, but can get complex to interpret where multiple symbols must be created to represent a single field name and should be avoided. The naming schema for such duplicate field names is as follows: For backward compatibility with pre-v1.5 CGIUTL a symbol corresponding to the field name always contains the value of the LAST such encountered field name. The symbol CGIUTL_<field_name>$$0 contains the number of symbols representing a duplicate field name. If this does not exist then there is only one instance of this field name in the request body and that value can be obtained from symbol CGIUTL_<field_name>. If it exists it will always have a value of at least 2 ... you need at least two for it to be duplicated!! The first instance of the field name will then be found in a symbol named CGIUTL_<field_name>$$1, the second in CGIUTL_<field_name>$$2, etc. This also applies to multi-symbol value representations (see above), but can get quite complex and having duplicate field names for large field values should be avoided when using CGIUTL. For example. The following form fields, <INPUT TYPE=checkbox NAME=example VALUE="one"> 1 <INPUT TYPE=checkbox NAME=example VALUE="two"> 2 <INPUT TYPE=checkbox NAME=example VALUE="three"> 3 <INPUT TYPE=checkbox NAME=example VALUE="four"> 4 if all checked, would result in the following symbols being generated CGIUTL_EXAMPLE == "four" CGIUTL_EXAMPLE$$0 == "4" CGIUTL_EXAMPLE$$1 == "one" CGIUTL_EXAMPLE$$2 == "two" CGIUTL_EXAMPLE$$3 == "three" CGIUTL_EXAMPLE$$4 == "four" When using the /DELIMITER qualifier the first character of the supplied string is used to split the data in the form fields. The delimiter character is discarded. An example form field datum of This contains, some simple comma-separated, values. and CGIUTL usage of $ CGIUTL /URLDECODE /SYMBOL /MAXSYM=20 /DELIMITER="," would result in symbols of CGIUTL_EXAMPLE$0 == "49" CGIUTL_EXAMPLE$0$ == "2" CGIUTL_EXAMPLE$1 == "This contains" CGIUTL_EXAMPLE$1$ == "," CGIUTL_EXAMPLE$2 == " some simple comma-s" CGIUTL_EXAMPLE$3 == "eparated" CGIUTL_EXAMPLE$3$ == "," CGIUTL_EXAMPLE$4 == " values." Note the symbols with names ending in the dollar symbol. These contain the delimiter character when it terminated the value parse (i.e. the symbol exists if the symbol value was delimited). In the case of symbol value overflow this dollar-delimited symbol does not exist and indicates the symbol number (and hence name) should be incremented for the remainder of the value (up until the dollar-delimited name is encounted). Of course delimited values can be empty (and will be with trailing delimiter characters). The symbol name plus dollar plus zero plus trailing dollar contains the count of the number of times the delimiter was hit (so the number of resulting symbols should be that plus one). Avoid having duplicate field names as it becomes very complex very quickly. The /SPLIT qualifier performs the same operation on an existing symbol. $ EXAMPLE == "one and two and three" $ CGIUTL EXAMPLE /SPLIT=" " $ SHOW SYMBOL EXAMPLE* EXAMPLE == "one and two and three" EXAMPLE$0 == "17" EXAMPLE$0$ == "4" EXAMPLE$1 == "one" EXAMPLE$1$ == " " EXAMPLE$2 == "and" EXAMPLE$2$ == " " EXAMPLE$3 == "two" EXAMPLE$3$ == " " EXAMPLE$4 == "and" EXAMPLE$4$ == " " EXAMPLE$5 == "three" This facility overwrites any existing symbol(s) of the same name(s)! In addition to all field-name related symbols produced during URL-decoding the following four special-purpose symbol provide some information about those DCL symbols created. CGIUTL$FIELDS .... the number of fields in the body (and hence <FORM>) CGIUTL$FIELDS_DUPLICATE .... the number of field names having multiple instances in the request body CGIUTL$MAXSYM .... the maximum number of characters possible in each symbol CGIUTL$SYMBOLS ... the number of field symbols created by the utility (this excludes these four special-purpose symbols) if a simple comparision between this and CGIUTL$FIELDS show a different count then at least one field had to be distributed across multiple symbols due to size DECODING A REQUEST BODY TO DCL SYMBOLS *PER LINE* ------------------------------------------------- Where a request field may have multiple lines (as with <TEXTAREA>s) the body may optionally have fields placed into symbols on a per-line basis. That is, each newline-delimited string in the field (i.e. a %0A occurs in the encoded data) has a DCL symbol created for it. If this string is larger than the CGI symbol value capacity (255 or 1023 characters) it "overflows" into the next symbol without warning. A <CR> (carriage-return) is always ignored. To enable this behaviour use the /SYMBOLS=LINES qualfiier and keyword. $ CGIUTL /URLDECODE /SYMBOLS=LINES For example if the following form field: <TEXTAREA NAME=text_area_input ROWS=14 COLS=80> </TEXTAREA> Had the following text entered into it: This is an example of a line. This is the next line. This is another line. ... and this is the last line! This text would be distributed across 4 symbols, with the additional count symbol, as follows: CGIUTL_TEXT_AREA_INPUT$0 == "102" CGIUTL_TEXT_AREA_INPUT$1 == "This is an example of a line." CGIUTL_TEXT_AREA_INPUT$2 == "This is the next line." CGIUTL_TEXT_AREA_INPUT$3 == "This is another line." CGIUTL_TEXT_AREA_INPUT$4 == "... and this is the last line!" The additional keyword NOCONTROL, removes all <HT> (tabs), <VT> (vertical-tabs) and <FF> (form-feeds), leaving only space characters. $ CGIUTL /URLDECODE /SYMBOLS=(LINES,NOCONTROL) SYMBOL NAME PREFIX ------------------ By default symbols names are created prefixed with the string "CGIUTL_". That is if there is a field name FREDDO in the request body the equivalent symbol is named "CGIUTL_FREDDO". This prefix may be user-specified with the /PREFIX qualifier. As an example; to make the symbol names much the same as those created by the server for GET requests use /PREFIX=WWW_FORM, which would result in the above form field having the symbol name "WWW_FORM_FREDDO". This could be made the default for a given script by making it part of the foreign verb assignment. $ CGIUTL = "$HT_EXE:CGIUTL/PREFIX=WWW_FORM" DECODING A REQUEST BODY TO A FILE --------------------------------- The /URLDECODE used with the /OUTPUT=filename qualifier results in a form-URL-encoded POSTed request body being written to the specified file. Two formats are possible controlled using the /FORMAT=NAMES (default) and /FORMAT=HEADINGS qualifier and keywords. When NAMES are used each field name of the format is followed by a colon, space and then the field value. With HEADINGS the field name is on a line by itself, underlined with hyphens and then in a block the field values (more suited to <TEXTAREA> input). The last possibility is /FORMAT=NONE which suppresses the output of field names completely. To write to an output file as well as create DCL symbols add the /SYMBOLS qualifier. The following examples show some of the variations. $ CGIUTL /URLDECODE /OUTPUT=WASD_ROOT:[LOG.SERVER]CGIUTL_FORM.TMP $ CGIUTL /URLDECODE /OUTPUT=WASD_ROOT:[LOG.SERVER]CGIUTL.TMP /FORMAT=HEADINGS $ CGIUTL /URLDECODE /OUTPUT=WASD_ROOT:[LOG.SERVER]CGIUTL_FORM.TMP /SYMBOLS WRITING A REQUEST BODY TO A FILE -------------------------------- Using the /BODY qualifier any POSTed request body (form-URL-encoded, text or other content) can simply be written to the specified /OUTPUT= file. It will be created in STREAM-LF format. For form-URL-encoded and text/.. bodies the file is written as text. For other content-type the file is written as binary. WRITING AN UPLOADED FILE TO A FILE ---------------------------------- The <INPUT TYPE=file NAME=name> HTML tag uploads a file from the browser-local system to the server as a POSTed "multipart/form-data" request. The /MULTIPART with the /FIELD= qualifiers allow the uploaded file form data field to be written to a file. For example, the following form input tag <INPUT TYPE=file NAME=file> could have it's upload contents written to the specified out file using $ CGIUTL /MULTIPART /FIELD=FILE /OUTPUT=WASD_ROOT:[LOG.SERVER]CGIUTL_FILE.TMP Additional information about this field is placed into two special variables created by CGIUTL, the file's original name/location and it's content-type. $ SHOW SYMBOL WWW_FORM_* WWW_FORM_MIME_CONTENT_TYPE == "image/jpeg" WWW_FORM_MIME_FILENAME == "C:\ftp\example.jpg" All other fields in the POSTed body that can be contained in a DCL symbol (i.e. <=255 or <=1023 depending on the VMS version) are created using the usual prefix of WWW_FORM_*. If you wish to change this, the /PREFIX qualifier allows the user to specify the form variable prefixes. It is possible to have more than one file uploaded per form (or a textarea that contains data to be written in addition to an uploaded form). In order to write more than one file, you must identify the field containing the data to be written and the file to write. This is done using the /FIELD and /OUTPUT qualifiers in pairs with /FIELD occurring first: $ CGIUTL /MULTIPART /FIELD=FILE1 /OUTPUT=FILE1.DAT - /FIELD=FILE2 /OUTPUT=FILE2.DAT This causes the multipart form to be processed and the contents of fields FILE1 and FILE2 to be written to FILE1.DAT and FILE2.DAT respectively. NOTE ON FORM FIELDS ------------------- Most form fields produce a name in the form data stream (URL-encoded request body) regardless of whether they are empty or not. Checkboxes and none-selected radio buttons fields do not. Unless checkboxes are checked or one radion button seelected the associated names and values do not appear in the data stream and will not have DCL symbols created for them. Three possible approaches: 1. Initialize symbols related to checkbox field names to empty values prior to using CGIUTL. This way the symbol can always be guaranteed to exist in subsequent processing, overwritten with the form value if checked. 2. After using CGIUTL use F$TYPE() to check if an associated symbol exists. 3. Include a HIDDEN INPUT field before checkboxes to ensure the relevant field name always exists in the data stream. Example: <INPUT TYPE=hidden NAME=testing VALUE=""> <INPUT TYPE=checkbox NAME=testing VALUE=1> Just Testing Incorrect and/or malicious form POSTings should also be allowed for, where ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ expected field names may be absent, disrupting script processing. Accomodating such possibilities suggests adopting a strategy of either initializing all expected symbols names BEFORE calling CGIUTL, or testing for the existance of all expected symbol names AFTER calling CGIUTL. Getting parameters from the CGI environment into the command-line qualifiers and/or parameter is best done without DCL (') substitution. The /SUBSTITUTE= qualifier allows the specification of a character that if present as the first character of a /QUALIFIER=<string> specification results in the rest of the string being used as an environment variable name (CGI and others). The getenv() C-RTL function is used to get a value against that name. An astersisk is the default substitution character. A substitution character may be escaped using a leading backslash. In the above example the literal string "*WWW_FORM_URL" (etc.) is read by the FETCH utility and then detecting the leading asterisk it resolves the remaining part of the string as an environment variable "WWW_FORM_URL" and uses the value of that (usually a symbol - see the C-RTL document for the description of the behaviour of the getenv() function). The contents of CGI variables should not be substituted into such a command-line (e.g. "''WWW_FORM_URL'"). Why an asterisk? Well, trying to find a character that doesn't have some very specific command-line interpreter, VMS or HTTP meaning (so as to avoid confusion) is quite difficult. The asterisk is slightly reminiscent of the C language pointer dereference operator. And anyway, it can be specified locally using /SUBSTITUTE=. MASSAGING DCL SYMBOLS --------------------- The utility will adjust the number of double-quotes in a DCL symbol making it suitable for substitution, parameter passing, etc., anywhere a single quote in a symbol would cause problems. This may be done multiple times, first doubling, then quadrupling, etc., the number of quotes. Buffer or symbol overflow is of course possible. These are errors. If the symbol does not exist or is a local symbol an error is reported. Example: $ SHOW SYMBOL THIS_GLOBAL_SYMBOL THIS_GLOBAL_SYMBOL == ""This" is quoted" $ CGIUTL THIS_GLOBAL_SYMBOL /2QUOTES $ SHOW SYMBOL THIS_GLOBAL_SYMBOL THIS_GLOBAL_SYMBOL == """This"" is quoted" The utility can also be used to split a symbol into multiple symbols according to the supplied delimiter character. Explained in detail above. Example: $ SHOW SYMBOL THIS_GLOBAL_SYMBOL THIS_GLOBAL_SYMBOL == "comma,separated,values" $ CGIUTL THIS_GLOBAL_SYMBOL /SPLIT="," $ SHOW SYMBOL THIS_GLOBAL_SYMBOL* THIS_GLOBAL_SYMBOL == "comma,separated,values" THIS_GLOBAL_SYMBOL$0 == "20" THIS_GLOBAL_SYMBOL$0$ == "2" THIS_GLOBAL_SYMBOL$1 == "comma" THIS_GLOBAL_SYMBOL$1$ == "," THIS_GLOBAL_SYMBOL$2 == "separated" THIS_GLOBAL_SYMBOL$2$ == "," THIS_GLOBAL_SYMBOL$3 == "values" And finally may be used to URL/percent-decode a symbol. Example: $ SHOW SYMBOL THIS_GLOBAL_SYMBOL THIS_GLOBAL_SYMBOL == "this%20is%20a%20test" $ CGIUTL THIS_GLOBAL_SYMBOL /UNPERSYM $ SHOW SYMBOL THIS_GLOBAL_SYMBOL THIS_GLOBAL_SYMBOL == "this is a test" GENERATING AN HTTP RESPONSE --------------------------- The utility can be used to provide a response header and/or body. The /RESPONSE qualifier generates a full HTTP response (NPH) while the /CGIRESPONSE qualifier generates a CGI response. Why use CGI? If the output is being generated by DCL this allows the server to check and adjust the carriage control of each output record. With NPH the script must supply it all. The two qualifiers are in all other respects the same. The /RESPONSE=[status-code] qualifier generates an "HTTP/1.0 status-code string" HTTP response. The default is "200 Success". If the /CONTENT=string qualifier is applied the specified MIME content-type header line is generated. Otherwise content-type defaults to "text/plain". If the /RESPONSE qualifier is used with a file name parameter, a response header is generated and then the contents of the file are transfered to the client as the request body. The file is either opened in record mode if the content-type is "text/..." (by default, without a /CONTENT) or in binary mode if any other content-type. For example: $ CGIUTL /RESPONSE=404 WEB:[ERRORS]NOTFOUND.HTML $ CGIUTL /RESPONSE WASD_ROOT:[000000]HTTPDWASD.GIF /CONTENT="image/gif" If /LENGTH is used with /RESPONSE when specifying a file name, as above, the file is first completely read to determine the actual content length (not fstat()ed, which does not work with variable-record-length files) and this added to the response header, before rewind()ing and transfering the file contents. For example: $ CGIUTL /RESPONSE /LENGTH WASD_ROOT:[000000]README.TXT If /COPY is used _without_ /RESPONSE against a parameter file name the content is copied to the client as above _without_ first sending a response header (i.e. the script must provide it's own). This is one way to transfer a binary file to a client from DCL. Note that the /CONTENT= is required so that it can be determined whether it is text or binary content. Example: $ SAY "HTTP/1.0 200 Success" + LF $ SAY "Content-Type: image/gif" + LF $ SAY LF $ CGIUTL /COPY WASD_ROOT:[000000]HTTPDWASD.GIF /CONTENT="image/gif" Redirection responses may be generated using the /LOCATION=url-path qualifier. If this is supplied the status code is forced to 302. An example: $ CGIUTL /LOCATION="http://www.openvms.digital.com/" WASD DECnet CGI --------------- When a WASD CGI script is executed via the DECnet script manager it is necessary to read the request body in record mode via NET$LINK stream. An empty record terminates the request body. This utility checks for the NET$LINK environment variable and adjusts behaviour if detected. QUALIFIERS ---------- /2QUOTE double up the quotes in the parameter symbol /BODY output the request body /BUGSYM keeps pre-v1.5 buggy symbol naming compatibility /CGIRESPONSE[=integer] send CGI header with this status code (default 200) /CHARSET=string explicitly specify a "text/" response character set (to suppress just supply an empty string) /CONTENT=string response content-type (default "text/plain") /COPY copy parameter file to client /DBUG turns on all "if (Debug)" statements /DELIMITER=char splits symbol content at the specified character /EXPIRED response header has an "Expired:" pre-expired time added /FIELD= just output the value of this particular field /FORMAT= when writing form-URL-encoded body as file "HEADINGS" as underlined headings "NAMES" as "field-name: field-value" "NONE" suppresses field names completely /GLOBAL work with global symbols (default) /HEADER=string A repeatable field that, when used inconjunction with the /RESPONSE qualifier allows additional headers to be sent with the response. /LOCAL work with local symbols /LOCATION=string send a 302 HTTP header with this as the location /MAXSYM=integer maximum number of characters per symbol value /MULTIPART body is multipart/form-data (file upload) /OUTPUT=file output to specified file /PLUS CGIplus interface for DCL (see [SRC.CGIPLUS]COMRTEXE.COM) /PREFIX=string prefix to symbol name (e.g. "WWW", defaults to "CGIUTL") /[NO]QUIETLY when an error occurs exit with the status inhibited (allows the procedure to do it's own error recovery) /RESPONSE[=integer] send NPH header with this status code (default 200) /SOFTWAREID (and /VERSION) display CGIUTL and CGILIB versions /SPLIT=char split symbol into multiple char-delimited symbols /SUBSTITUTE=char specify the character for parameter substitution /SYMBOLS[=LINES,NOCONTROL] put into DCL symbols, optionally one line per symbol (<LF> delimited) optionally strip control characters (e.g. <HT>) /UNPERSYM URL/Percent decode a DCL symbol back into itself /URLDECODE decode a form-URL-encoded, POSTed body (use with /SYMBOL and/or /OUTPUT=) LOGICAL NAMES ------------- CGIUTL$DBUG turns on all "if (Debug)" statements CGIUTL$PARAM[_1..255] equivalent to (overrides) the command line parameters/qualifiers (define as a system-wide logical) may also be assigned as a local/global symbol. Long command lines may be emitted by creating additional logical names by adding _and a digit. These must be created sequentially, e.g., _1 _2 _3. Once a hole is found, processing stops. BUILD DETAILS ------------- See BUILD_CGIUTL.COM procedure. COPYRIGHT --------- Copyright (C) 1999-2021 Mark G.Daniel Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. VERSION HISTORY (update SOFTWAREVN as well) --------------- 13-JAN-2012 MGD v1.14.0, add /UNPERSYM 06-NOV-2011 MGD v1.13.0, add /PLUS bugfix; redundant "WWW_" += 4 after CgiLibVar("*") 16-OCT-2010 MGD v1.12.0, add /DELIMITER and /SPLIT 26-AUG-2008 MGD v1.11.4, bugfix; process /BODY 15-OCT-2007 MGD v1.11.3, workaround for %LIB-F-INVARG described in main() 20-JAN-2006 MGD v1.11.2, bugfix; when URL-encoded decoding use unsigned char to prevent sign bit issues with the likes of %FC 20-APR-2005 MGD v1.11.1, bugfix; UrlDecodeBodyWrite() ensure a full buffer does not split a URL-encoding sequence 24-DEC-2003 MGD v1.11.0, minor conditional mods to support IA64 allow it to work with local symbols (for CSWS), maximum symbol size for VMS 7.3-2ff now 8192, bugfix; symbol name creation in POSTed requests 20-APR-2003 DM v1.10.1, (munroe@csworks.com) CGIRESPONSE isn't generating headers correctly. 27-FEB-2003 DM v1.10.0, (munroe@csworks.com) Add /HEADER qualifier that can be used to add additional headers to /RESPONSE streams. Not all exit messages are protected by ExitQuietly. Allow multiple fields to be extracted and written to files from URLENCODED forms. Allow CGIUTL$PARAM to be enumerated thus allowing very long command lines. Allow multipart form processing to define symbols specifically in addition to processing for files. Allow more than one field per multipart form to be written. A maximum of 20 are allowed. Bugfix: Make the usage of /PREFIX consistent in Multipart processing (Don't require a trailing "-"). Make /PREFIX work in multipart upload file processing. 01-OCT-2002 MGD v1.9.0, modify command-line parsing, bugfix; CgiLibReadRequestBody() returns NULL body 22-JUL-2001 MGD v1.8.3, refine /CHARSET= so an empty string suppresses 08-JUN-2001 MGD v1.8.2, add /SOFTWAREID (/VERSION) 20-DEC-2000 MGD v1.8.1, minor update, add /FORMAT=NONE 28-OCT-2000 MGD v1.8.0, use CGILIB object module, support RELAXED_ANSI compilation, /CGIRESPONSE= to generate non-NPH response, bugfix; limit length of FORM_URLENCODED string comparison (Kurt.Schumacher@schumi.ch) 11-AUG-2000 MGD v1.7.0, /SYMBOLS=LINES,NOCONTROL 19-APR-2000 MGD v1.6.0, /FIELD= outputs single form field only, minor changes for CGILIB 1.4 29-JAN-2000 MGD v1.5.0, multiple symbol names for fields with same name, bugfix; multi-symbol value names containing '$' 20-OCT-1999 MGD v1.4.0, /QUIETLY qualifier 28-SEP-1999 MGD v1.3.1, bugfix; /PREFIX length 24-APR-1999 MGD v1.3.0, use CGILIB.C 14-MAR-1999 MGD v1.2.0, allow for DECnet CGI request body from NET$LINK 13-FEB-1999 MGD v1.1.0, response generating functionality 06-FEB-1999 MGD v1.0.0, initial development */ /*****************************************************************************/ #define SOFTWAREVN "1.14.0" #define SOFTWARENM "CGIUTL" #ifdef __ALPHA # define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN #endif #ifdef __ia64 # define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN #endif #ifdef __VAX # define SOFTWAREID SOFTWARENM " VAX-" SOFTWAREVN #endif #ifdef __x86_64 # define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN #endif #ifndef __VAX # pragma nomember_alignment #endif /* C header files */ #include <ctype.h> #include <errno.h> #include <math.h> #include <stdio.h> #include <stdlib.h> #include <string.h> /* VMS related header files */ #include <descrip.h> #include <libdef.h> #include <libclidef.h> #include <syidef.h> #include <ssdef.h> #include <stsdef.h> /* application related header file */ #include <cgilib.h> #define VMSok(x) ((x) & STS$M_SUCCESS) #define VMSnok(x) !(((x) & STS$M_SUCCESS)) #define boolean int #define true 1 #define false 0 #define DEFAULT_PARAM_SUBS_CHAR '*' #define FORM_URLENCODED "application/x-www-form-urlencoded" #define FORM_URLENCODED_LENGTH sizeof(FORM_URLENCODED)-1 #define FORM_URLENCODED_FIELD_MAX 8192 #define FORM_URLENCODED_FIELD_NAME_MAX 256 #define MULTIPART_FORMDATA "multipart/form-data;" char CopyrightInfo [] = "Copyright (C) 1999-2012 Mark G.Daniel.\n\ \n\ Licensed under the Apache License, Version 2.0 (the \"License\");\n\ you may not use this file except in compliance with the License.\n\ You may obtain a copy of the License at\n\ \n\ http://www.apache.org/licenses/LICENSE-2.0\n\ \n\ Unless required by applicable law or agreed to in writing, software\n\ distributed under the License is distributed on an \"AS IS\" BASIS,\n\ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n\ See the License for the specific language governing permissions and\n\ limitations under the License.\n"; char Utility [] = "CGIUTL"; boolean BugSymbolNaming, Debug, DoCgiResponse, DoContentLength, DoCopy, DoCgiPlus, DoCliSymbols, DoCliSymbolsLines, DoCliSymbolsNoControl, DoDecodeSymbolPercent, DoEscapeSymbolQuotes, DoSplitSymbol, DoFormatHeadings, DoFormatNames = true, DoMultipartFormData, DoPreExpired, DoRequestBody, DoResponse, DoUrlDecode, ResponseContentTypeText; int BufferCount, BufferSize, CliSymbolType, DelimCharHitCount, ExitQuietly, FileContentLength, ReadCount, SymbolValueMax, SymbolPrefixLength; char DelimChar [2]; char *BufferPtr, *CgiContentTypePtr, *CgiRequestMethodPtr, *CgiRequestTimeGmtPtr, *CgiServerSoftwarePtr, *CharsetPtr, *CliCharsetPtr, *CliContentTypePtr, *CliLocationPtr, *CliOutputPtr, *CliParameterPtr, *CliResponsePtr, *FieldOnlyPtr, *SymbolPrefixPtr; typedef struct { char* FieldNamePtr ; char* FileNamePtr ; } FieldToFileMap_t; #define MAX_FIELD_TO_FILE 20 FieldToFileMap_t FieldToFileMap[MAX_FIELD_TO_FILE] ; int FieldToFileMapIndex = -1 ; #define MAX_ADDITIONAL_HEADERS 20 typedef char* AdditionalHeader_t ; AdditionalHeader_t AdditionalHeaders[MAX_ADDITIONAL_HEADERS]; int AdditionalHeadersIndex = -1 ; char ParamSubsChar = DEFAULT_PARAM_SUBS_CHAR; char CharsetString [256], SoftwareID [64]; /* required prototypes */ void ExitCgiUtl (int, int); char* HttpStatusCodeText (int); char* GetParameterString (char*); /*****************************************************************************/ /* 'argc' and 'argv' are only required to support CgiLibEnvironmentInit(), in particular the OSU environment. */ main ( int argc, char *argv[] ) { boolean Done; char *cptr, *sptr; /*********/ /* begin */ /*********/ sprintf (SoftwareID, "%s (%s)", SOFTWAREID, CGILIB_SOFTWAREID); if (getenv ("CGIUTL$DBUG")) Debug = true; CgiLibEnvironmentSetDebug (Debug); GetParameters (); if (Debug) fprintf (stdout, "Content-Type: text/plain\n\n%s\n", SoftwareID); CgiLibEnvironmentInit (argc, argv, false); CgiLibResponseSetSoftwareID (SoftwareID); CgiLibResponseSetErrorMessage ("Reported by CGIutl"); if (!CliSymbolType) if (CgiLibEnvironmentIsApache()) CliSymbolType = LIB$K_CLI_LOCAL_SYM; else CliSymbolType = LIB$K_CLI_GLOBAL_SYM; if (CliOutputPtr) if (!CliOutputPtr[0]) CliOutputPtr = CliParameterPtr; if (VmsVersion() >= 732) { /* A '%LIB-F-INVARG, invalid argument(s)' error reported on a V7.3-2 Alpha but also reproducable on my V8.3 Alpha seems to limit symbol size to something below the documented 8191 of 7.3-2 and later. Pragmatically determined at about the value in the conditional below. Doesn't seem to be any ECOs against this so let's just live with it for now. Don't forget about the /MAXSYM=<integer> qualifier either! */ #if 0 if (SymbolValueMax < 1 || SymbolValueMax > 8191) SymbolValueMax = 8191; #else if (SymbolValueMax < 1 || SymbolValueMax > 4095) SymbolValueMax = 4095; #endif } else if (VmsVersion() >= 700) { if (SymbolValueMax < 1 || SymbolValueMax > 1023) SymbolValueMax = 1023; } else { if (SymbolValueMax < 1 || SymbolValueMax > 255) SymbolValueMax = 255; } if (Debug) fprintf (stdout, "SymbolValueMax: %d\n", SymbolValueMax); if (!SymbolPrefixPtr) SymbolPrefixPtr = Utility; SymbolPrefixLength = strlen(SymbolPrefixPtr); if (Debug) fprintf (stdout, "SymbolPrefix: %s %d\n", SymbolPrefixPtr, SymbolPrefixLength); Done = false; if (DoDecodeSymbolPercent) { /*******************************/ /* decode a URL-encoded symbol */ /*******************************/ DecodeSymbolPercent (CliParameterPtr); ExitCgiUtl (SS$_NORMAL, __LINE__); } if (DoEscapeSymbolQuotes) { /****************************/ /* escape a symbol's quotes */ /****************************/ EscapeSymbolQuotes (CliParameterPtr); ExitCgiUtl (SS$_NORMAL, __LINE__); } if (DoSplitSymbol) { /******************/ /* split a symbol */ /******************/ SplitSymbol (CliParameterPtr); ExitCgiUtl (SS$_NORMAL, __LINE__); } if (DoCgiPlus) { /*********/ /* /plus */ /*********/ if (!CgiLibEnvironmentIsCgiPlus()) ExitCgiUtl (SS$_BUGCHECK, __LINE__); CgiLibVar (""); while ((cptr = sptr = CgiLibVar ("*"))) { if (Debug) fprintf (stdout, "|%s|\n", cptr); if (!memcmp (cptr, "WWW_", 4)) cptr += 4; while (*sptr && *sptr != '=') sptr++; if (*sptr == '=') { *sptr = '\0'; if (strlen(sptr+1) <= SymbolValueMax) { if (SymbolPrefixPtr) { char theName[FORM_URLENCODED_FIELD_NAME_MAX]; strcpy (theName, SymbolPrefixPtr); strcat (theName, cptr); SetCliSymbol(theName, sptr+1, 0); } else SetCliSymbol (cptr+1, sptr+1, 0); } *sptr = '='; } } ExitCgiUtl (SS$_NORMAL, __LINE__); } if (DoCgiResponse || DoResponse || CliParameterPtr) { /*********************/ /* generate response */ /*********************/ CgiServerSoftwarePtr = CgiLibVar ("WWW_SERVER_SOFTWARE"); if (!CgiServerSoftwarePtr[0]) CgiServerSoftwarePtr = SoftwareID; CgiRequestTimeGmtPtr = CgiLibVar ("WWW_REQUEST_TIME_GMT"); if (!CliContentTypePtr) { CliContentTypePtr = "text/plain"; ResponseContentTypeText = true; } else { /* ensure it's lower case */ for (cptr = CliContentTypePtr; *cptr; cptr++) *cptr = tolower(*cptr); ResponseContentTypeText = !memcmp (CliContentTypePtr, "text/", 5); } if (ResponseContentTypeText) { if (!(CharsetPtr = CliCharsetPtr)) { CharsetPtr = CgiLibVar ("WWW_SERVER_CHARSET"); if (!CharsetPtr || !CharsetPtr[0]) CharsetPtr = "ISO-8859-1"; } if (CharsetPtr[0]) { sprintf (CharsetString, "; charset=%s", CharsetPtr); CharsetPtr = CharsetString; } } else CharsetPtr = ""; if (!CliResponsePtr) CliResponsePtr = "200"; FileContentLength = -1; if (DoCopy && !CliParameterPtr) { if (!ExitQuietly) fprintf (stdout, "%%%s-E-COPY, file not specified\n", Utility); ExitCgiUtl (STS$K_ERROR, __LINE__); } if (!CliParameterPtr) { if (DoCgiResponse) SendCgiResponse (); else SendResponse (); } else SendFile (); ExitCgiUtl (SS$_NORMAL, __LINE__); } /**************************/ /* process POSTed request */ /**************************/ if (DoUrlDecode && DoCliSymbols) { /* form-URL-decode the body and create corresponding DCL symbols */ UrlDecodeBodyCliSymbols (); Done = true; } if (DoUrlDecode && FieldOnlyPtr && CliOutputPtr) { /* form-URL-decode the body and write to file as field name/values */ UrlDecodeFieldWrite (); Done = true; } else if (DoUrlDecode && CliOutputPtr) { /* form-URL-decode the body and write to file as field name/values */ UrlDecodeBodyWrite (); Done = true; } else if (DoMultipartFormData && FieldOnlyPtr && CliOutputPtr && (FieldToFileMapIndex == 0)) { /* form-URL-decode the body and write to file as field name/values */ MultipartFormDataFieldWrite (); Done = true; } else if (DoMultipartFormData && ((DoCliSymbols) || (FieldOnlyPtr && CliOutputPtr && (FieldToFileMapIndex > 0)))) { /* form-URL-decode the body and write to file as field name/values */ Multipart2FormDataFieldWrite (); Done = true; } if (CliOutputPtr && (DoRequestBody || (!DoUrlDecode && !DoMultipartFormData))) { /* write the request body to file as plain text */ RequestBodyWrite (); Done = true; } if (!Done) { if (!ExitQuietly) fprintf (stdout, "%%%s-E-HUH, did nothing!\n", Utility); ExitCgiUtl (STS$K_ERROR, __LINE__); } } /*****************************************************************************/ /* Generate and send to the client an NPH (full HTTP) response header. Various global variables feed into the generated header. */ SendResponse () { int i, StatusCode; char *cptr, *PreExpiredPtr; char ContentLengthHeader [32]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "SendResponse()\n"); if (!Debug) { /* reopen output stream so that the '\r' and '\n' are not filtered */ if (!(stdout = freopen ("SYS$OUTPUT", "w", stdout, "ctx=bin"))) ExitCgiUtl (vaxc$errno, __LINE__); } if (CliLocationPtr) { fprintf (stdout, "HTTP/1.0 302 %s\r\n\ Server: %s\r\n\ Date: %s\r\n\ Location: %s\r\n\ \r\n", HttpStatusCodeText(302), CgiServerSoftwarePtr, CgiRequestTimeGmtPtr, CliLocationPtr); return; } if (FileContentLength >= 0) sprintf (ContentLengthHeader, "Content-Length: %d\r\n", FileContentLength); else ContentLengthHeader[0] = '\0'; if (DoPreExpired) PreExpiredPtr = "Expires: Fri, 13 Jan 1978 14:00:00 GMT\r\n"; else PreExpiredPtr = ""; StatusCode = atoi (CliResponsePtr); if (StatusCode < 200 || StatusCode > 599) StatusCode = 0; fprintf (stdout, "HTTP/1.0 %d %s\r\n\ Server: %s\r\n\ Date: %s\r\n\ Content-Type: %s%s\r\n\ %s\ %s\ ", StatusCode, HttpStatusCodeText(StatusCode), CgiServerSoftwarePtr, CgiRequestTimeGmtPtr, CliContentTypePtr, CharsetPtr, ContentLengthHeader, PreExpiredPtr); for (i = 0; i <= AdditionalHeadersIndex; i++) { fprintf (stdout, "%s\r\n", AdditionalHeaders[i]) ; } fprintf (stdout, "\r\n") ; } /*****************************************************************************/ /* Generate and send to the client a CGI response header. Various global variables feed into the generated header. */ SendCgiResponse () { int i, StatusCode; char *cptr, *PreExpiredPtr; char ContentLengthHeader [32]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "SendCgiResponse()\n"); if (!Debug) { /* reopen output stream so that the '\r' and '\n' are not filtered */ if (!(stdout = freopen ("SYS$OUTPUT", "w", stdout, "ctx=bin"))) ExitCgiUtl (vaxc$errno, __LINE__); } if (CliLocationPtr) { fprintf (stdout, "Location: %s\r\n\ \r\n", CliLocationPtr); return; } if (FileContentLength >= 0) sprintf (ContentLengthHeader, "Content-Length: %d\r\n", FileContentLength); else ContentLengthHeader[0] = '\0'; if (DoPreExpired) PreExpiredPtr = "Expires: Fri, 13 Jan 1978 14:00:00 GMT\r\n"; else PreExpiredPtr = ""; StatusCode = atoi (CliResponsePtr); if (StatusCode < 200 || StatusCode > 599) StatusCode = 0; fprintf (stdout, "Content-Type: %s%s\r\n\ %s\ %s\ ", CliContentTypePtr, CharsetPtr, ContentLengthHeader, PreExpiredPtr); for (i = 0; i <= AdditionalHeadersIndex; i++) { fprintf (stdout, "%s\r\n", AdditionalHeaders[i]) ; } fprintf (stdout, "\r\n") ; } /*****************************************************************************/ /* Send a file to the client. If the file cannot be opened for any reason exit with error. If the content-type is text the open it in record-mode, if not text then in binary mode (this way it should work for files of all characteristics). If the content-length has been requested count the content bytes by reading the whole file and rewinding (expensive, but it returns a valid result even with variable-record-length files). */ SendFile () { int ReadCount; char Buffer [4096]; FILE *InputFile; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "SendFile() |%s|\n", CliParameterPtr); if (ResponseContentTypeText) InputFile = fopen (CliParameterPtr, "r", "shr=get"); else InputFile = fopen (CliParameterPtr, "r", "ctx=bin", "shr=get"); if (!InputFile) ExitCgiUtl (vaxc$errno, __LINE__); if (DoContentLength) { FileContentLength = 0; if (ResponseContentTypeText) while (fgets (Buffer, sizeof(Buffer), InputFile)) FileContentLength += strlen(Buffer); else while (ReadCount = fread (Buffer, 1, sizeof(Buffer), InputFile)) FileContentLength += ReadCount; rewind (InputFile); } if (DoCgiResponse) SendCgiResponse (); else if (DoResponse) SendResponse (); else { /* reopen output stream so that the '\r' and '\n' are not filtered */ if (!(stdout = freopen ("SYS$OUTPUT", "w", stdout, "ctx=bin"))) ExitCgiUtl (vaxc$errno, __LINE__); } if (ResponseContentTypeText) while (fgets (Buffer, sizeof(Buffer), InputFile)) fputs (Buffer, stdout); else while (ReadCount = fread (Buffer, 1, sizeof(Buffer), InputFile)) fwrite (Buffer, ReadCount, 1, stdout); fclose (InputFile); } /*****************************************************************************/ /* Return the a pointer to abbreviated meaning of the supplied HTTP status code. These are typical of those included on the response header status line. */ char *HttpStatusCodeText (int StatusCode) { /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "HttpStatusCodeText() %d\n", StatusCode); switch (StatusCode) { case 000 : return ("Script Error!"); case 200 : return ("Success"); case 201 : return ("Created"); case 202 : return ("Accepted"); case 203 : return ("No content"); case 301 : return ("Moved permanently"); case 302 : return ("Moved temporarily"); case 304 : return ("Not modified"); case 400 : return ("Bad request"); case 401 : return ("Authorization required"); case 403 : return ("Forbidden"); case 404 : return ("Not found"); case 500 : return ("Internal error"); case 501 : return ("Not implemented"); case 502 : return ("Bad gateway"); case 503 : return ("Service unavailable"); default : return ("Unknown code!"); } } /*****************************************************************************/ /* Write the request body to to the file specified by 'CliOutputPtr'. Write as text if form-URL-encoded or text, binary if anything else. */ RequestBodyWrite () { int Length; FILE *OutputFile; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "RequestBodyWrite()\n"); if (!BufferPtr) { CgiLibReadRequestBody (&BufferPtr, &BufferCount); if (!BufferPtr) BufferPtr = ""; } if (!CliOutputPtr) OutputFile = stdout; else if (!(OutputFile = fopen (CliOutputPtr, "w"))) ExitCgiUtl (vaxc$errno, __LINE__); CgiContentTypePtr = CgiLibVar ("WWW_CONTENT_TYPE"); if (strsame (CgiContentTypePtr, FORM_URLENCODED, FORM_URLENCODED_LENGTH)) { Length = CgiLibUrlDecode (BufferPtr); /* write as text file */ if ((int)fwrite (BufferPtr, Length, 1, OutputFile) == EOF) ExitCgiUtl (vaxc$errno, __LINE__); } else if (strsame (CgiContentTypePtr, "text/", 5)) { /* write as text file */ if ((int)fwrite (BufferPtr, BufferCount, 1, OutputFile) == EOF) ExitCgiUtl (vaxc$errno, __LINE__); } else { /* write as binary */ if ((int)fwrite (BufferPtr, BufferCount, 1, OutputFile) == EOF) ExitCgiUtl (vaxc$errno, __LINE__); } fclose (OutputFile); } /*****************************************************************************/ /* Write the form-URL-decoded contents of the request body into an output file. Two formats for the output document are supported, names and headings. */ UrlDecodeBodyWrite () { int cnt, FieldNameLength; char *cptr, *sptr, *zptr; char FieldName [FORM_URLENCODED_FIELD_NAME_MAX], FieldValue [FORM_URLENCODED_FIELD_MAX]; FILE *OutputFile; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "UrlDecodeBodyWrite()\n"); CgiContentTypePtr = CgiLibVar ("WWW_CONTENT_TYPE"); if (!strsame (CgiContentTypePtr, FORM_URLENCODED, FORM_URLENCODED_LENGTH)) { if (!ExitQuietly) fprintf (stdout, "%%%s-E-CONTENT, is not form-URL-encoded\n \\%s\\\n", Utility, CgiContentTypePtr); ExitCgiUtl (STS$K_ERROR, __LINE__); } if (!BufferPtr) { CgiLibReadRequestBody (&BufferPtr, &BufferCount); if (!BufferPtr) BufferPtr = ""; } if (!CliOutputPtr) OutputFile = stdout; else if (!(OutputFile = fopen (CliOutputPtr, "w"))) ExitCgiUtl (vaxc$errno, __LINE__); cptr = BufferPtr; while (*cptr) { FieldNameLength = 0; while (*cptr && *cptr != '=') { zptr = (sptr = FieldName) + sizeof(FieldName)-1; while (*cptr && *cptr != '=' && sptr < zptr) { if (isprint(*cptr)) *sptr++ = *cptr++; else cptr++; } /* if field name is too large it's just truncated and rest ignored */ while (*cptr && *cptr != '=') cptr++; *sptr = '\0'; if (Debug) fprintf (stdout, "FieldName: |%s|\n", FieldName); CgiLibUrlDecode (FieldName); if (DoFormatHeadings) fprintf (OutputFile, "%s\n", FieldName); else if (DoFormatNames) fprintf (OutputFile, "%s: ", FieldName); /* else no field name */ FieldNameLength += sptr - FieldName; } if (*cptr == '=') cptr++; if (DoFormatHeadings) { /* underline the field name */ for (cnt = 0; cnt < FieldNameLength; cnt++) fputc ('_', OutputFile); fputs ("\n", OutputFile); } while (*cptr && *cptr != '&') { zptr = (sptr = FieldValue) + sizeof(FieldValue)-1; while (*cptr && *cptr != '&' && sptr < zptr) { if (isprint(*cptr)) *sptr++ = *cptr++; else cptr++; } if (sptr >= zptr) { /* hmmm, full buffer - ensure we've got complete URL-encoding */ if (sptr > FieldValue && sptr[-1] == '%') { sptr--; cptr--; } else if (sptr+1 > FieldValue && sptr[-2] == '%') { sptr -= 2; cptr -= 2; } if (sptr == FieldValue) ExitCgiUtl (SS$_BUGCHECK, __LINE__); } *sptr = '\0'; if (Debug) fprintf (stdout, "FieldValue: %d |%s|\n", sptr-FieldValue, FieldValue); if (CgiLibUrlDecode (FieldValue) < 0) ExitCgiUtl (SS$_BUGCHECK, __LINE__); StripCarRet (FieldValue); fputs (FieldValue, OutputFile); } if (*cptr == '&') cptr++; /* blank line between this field and the next */ fputs ("\n\n", OutputFile); } fclose (OutputFile); } /*****************************************************************************/ /* Write the form-URL-decoded contents of form field into an output file. */ UrlDecodeFieldWrite () { int i, cnt; char *cptr, *sptr, *zptr; char FieldName [FORM_URLENCODED_FIELD_NAME_MAX]; FILE *OutputFile; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "UrlDecodeFieldWrite()\n"); CgiContentTypePtr = CgiLibVar ("WWW_CONTENT_TYPE"); if (!strsame (CgiContentTypePtr, FORM_URLENCODED, FORM_URLENCODED_LENGTH)) { if (!ExitQuietly) fprintf (stdout, "%%%s-E-CONTENT, is not form-URL-encoded\n \\%s\\\n", Utility, CgiContentTypePtr); ExitCgiUtl (STS$K_ERROR, __LINE__); } if (!BufferPtr) { CgiLibReadRequestBody (&BufferPtr, &BufferCount); if (!BufferPtr) BufferPtr = ""; } for (i = 0; i <= FieldToFileMapIndex; i++) { if (!FieldToFileMap[i].FileNamePtr) OutputFile = stdout; else if (!(OutputFile = fopen (FieldToFileMap[i].FileNamePtr, "w"))) ExitCgiUtl (vaxc$errno, __LINE__); zptr = (sptr = FieldName) + sizeof(FieldName)-1; for (cptr = "WWW_"; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = FieldToFileMap[i].FieldNamePtr; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; cptr = CgiLibVar (FieldName); cnt = strlen(cptr); if ((int)fwrite (cptr, cnt, 1, OutputFile) == EOF) ExitCgiUtl (vaxc$errno, __LINE__); fclose (OutputFile); } } /*****************************************************************************/ /* Write the multipart/form-data contents of a file upload form field into an output file. */ MultipartFormDataFieldWrite () { int cnt; char *cptr, *sptr; char FieldName [FORM_URLENCODED_FIELD_NAME_MAX]; FILE *OutputFile; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MultipartFormDataFieldWrite()\n"); CgiContentTypePtr = CgiLibVar ("WWW_CONTENT_TYPE"); if (!strsame (CgiContentTypePtr, MULTIPART_FORMDATA, strlen(MULTIPART_FORMDATA))) { if (!ExitQuietly) fprintf (stdout, "%%%s-E-CONTENT, is not multipart/form-data\n \\%s\\\n", Utility, CgiContentTypePtr); ExitCgiUtl (STS$K_ERROR, __LINE__); } if (!BufferPtr) { CgiLibReadRequestBody (&BufferPtr, &BufferCount); if (!BufferPtr) BufferPtr = ""; } CgiLibFormRequestBody (BufferPtr, BufferCount); while ((cptr = sptr = CgiLibVar ("*"))) { if (Debug) fprintf (stdout, "|%s|\n", cptr); if (!memcmp (cptr, "WWW_", 4)) cptr += 4; while (*sptr && *sptr != '=') sptr++; if (*sptr == '=') { *sptr = '\0'; if (strlen(sptr+1) <= SymbolValueMax) { if (SymbolPrefixPtr) { char theName[FORM_URLENCODED_FIELD_NAME_MAX]; strcpy (theName, SymbolPrefixPtr); strcat (theName, cptr); SetCliSymbol(theName, sptr+1, 0); } else SetCliSymbol (cptr+1, sptr+1, 0); } *sptr = '='; } } while ((cptr = sptr = CgiLibVar ("*"))) if (Debug) fprintf (stdout, "|%s|\n", cptr); if (!CliOutputPtr) OutputFile = stdout; else if (!(OutputFile = fopen (CliOutputPtr, "w"))) ExitCgiUtl (vaxc$errno, __LINE__); sprintf (FieldName, "WWW_FORM_%s", FieldOnlyPtr); cptr = CgiLibVar (FieldName); cnt = CgiLibHtmlDeEntify (cptr); if (cnt == -1) { if (!ExitQuietly) fprintf (stdout, "%%%s-E-DEENTIFY, error de-entifying multipart\n", Utility); ExitCgiUtl (STS$K_ERROR, __LINE__); } if ((int)fwrite (cptr, cnt, 1, OutputFile) == EOF) ExitCgiUtl (vaxc$errno, __LINE__); fclose (OutputFile); } /*****************************************************************************/ /* Write the multipart/form-data contents of more than one file upload form field into an output file. To get this right, the command line has to have two or more /FIELD ... /OUTPUT pairs (in this order) to identify the fields and associate file names with them. */ Multipart2FormDataFieldWrite () { int cnt, i; char *cptr, *nptr, *sptr; char FieldName [FORM_URLENCODED_FIELD_NAME_MAX]; FILE *OutputFile; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "Multipart2FormDataFieldWrite()\n"); CgiContentTypePtr = CgiLibVar ("WWW_CONTENT_TYPE"); if (!strsame (CgiContentTypePtr, MULTIPART_FORMDATA, strlen(MULTIPART_FORMDATA))) { if (!ExitQuietly) fprintf (stdout, "%%%s-E-CONTENT, is not multipart/form-data\n \\%s\\\n", Utility, CgiContentTypePtr); ExitCgiUtl (STS$K_ERROR, __LINE__); } if (!BufferPtr) { CgiLibReadRequestBody (&BufferPtr, &BufferCount); if (!BufferPtr) BufferPtr = ""; } CgiLibFormRequestBody (BufferPtr, BufferCount); while ((cptr = sptr = CgiLibVar ("*"))) { if (Debug) fprintf (stdout, "|%s|\n", cptr); if (!memcmp (cptr, "WWW_", 4)) cptr += 4; while (*sptr && *sptr != '=') sptr++; if (*sptr == '=') { *sptr = '\0'; if (strlen(sptr+1) <= SymbolValueMax) { if (SymbolPrefixPtr) { char theName[FORM_URLENCODED_FIELD_NAME_MAX]; strcpy (theName, SymbolPrefixPtr); strcat (theName, cptr); SetCliSymbol(theName, sptr+1, 0); } else SetCliSymbol (cptr+1, sptr+1, 0); } *sptr = '='; } } while ((cptr = sptr = CgiLibVar ("*"))) if (Debug) fprintf (stdout, "|%s|\n", cptr); for (i = 0; i <= FieldToFileMapIndex; i++) { if (!FieldToFileMap[i].FileNamePtr) OutputFile = stdout; else if (!(OutputFile = fopen (FieldToFileMap[i].FileNamePtr, "w"))) ExitCgiUtl (vaxc$errno, __LINE__); sprintf (FieldName, "WWW_FORM_%s", FieldToFileMap[i].FieldNamePtr); cptr = CgiLibVar (FieldName); cnt = CgiLibHtmlDeEntify (cptr); if (cnt == -1) { if (!ExitQuietly) fprintf (stdout, "%%%s-E-DEENTIFY, error de-entifying multipart\n", Utility); ExitCgiUtl (STS$K_ERROR, __LINE__); } if ((int)fwrite (cptr, cnt, 1, OutputFile) == EOF) ExitCgiUtl (vaxc$errno, __LINE__); fclose (OutputFile); } } /*****************************************************************************/ /* Decode the form-URL-encoded POSTed request body with the contents of each field being placed into DCL symbols named using that field name. Field contents greater than the capacity of a single DCL symbol (255 on <=6.n and 1023 on >=v7.n) have the contents distributed across multiple symbol names. Each of these fields comprises the basic name with a dollar symbol and an ascending integer. Informational symbols are also created. */ UrlDecodeBodyCliSymbols () { boolean FieldHasMultipleSymbols; int status, DuplicateFieldCount, DuplicateSymbolCount, FieldCount, SymbolCount, SymbolNameLength, SymbolValueCount, SymbolValueLength, SymbolValuePart, SymbolValuePartLength; unsigned char ch; char *cptr, *sptr, *zptr; char Scratch [FORM_URLENCODED_FIELD_MAX], SymbolName [FORM_URLENCODED_FIELD_NAME_MAX], SymbolValue [FORM_URLENCODED_FIELD_MAX], SymbolValueCountName [FORM_URLENCODED_FIELD_NAME_MAX]; FILE *OutputFile; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "UrlDecodeBodyCliSymbols()\n"); CgiContentTypePtr = CgiLibVar ("WWW_CONTENT_TYPE"); if (!strsame (CgiContentTypePtr, FORM_URLENCODED, FORM_URLENCODED_LENGTH)) { if (!ExitQuietly) fprintf (stdout, "%%%s-E-CONTENT, is not form-URL-encoded\n \\%s\\\n", Utility, CgiContentTypePtr); ExitCgiUtl (STS$K_ERROR, __LINE__); } if (!BufferPtr) { CgiLibReadRequestBody (&BufferPtr, &BufferCount); if (!BufferPtr) BufferPtr = ""; } if (Debug) fprintf (stdout, "|%s|\n", BufferPtr); DuplicateFieldCount = DuplicateSymbolCount = FieldCount = SymbolCount = SymbolNameLength = SymbolValueCount = SymbolValueLength = SymbolValuePart = 0; SymbolName[0] = SymbolValue[0] = SymbolValueCountName[0] = '\0'; /**************************/ /* parse URL-encoded body */ /**************************/ cptr = BufferPtr; while (*cptr) { FieldHasMultipleSymbols = false; SymbolValuePartLength = 0; if (!SymbolNameLength) { /*******************************/ /* get the symbol (field) name */ /*******************************/ FieldCount++; /* allow 16 for appending some dollar-counts later on! */ zptr = (sptr = SymbolName) + sizeof(SymbolName) - 16; memcpy (sptr, SymbolPrefixPtr, SymbolPrefixLength); sptr += SymbolPrefixLength; if (sptr < zptr) *sptr++ = '_'; while (*cptr && *cptr != '=' && sptr < zptr) { if (isprint(*cptr)) *sptr++ = toupper(*cptr++); else cptr++; } if (sptr >= zptr) { if (!ExitQuietly) fprintf (stdout, "%%%s-E-SYMBOLNAME, too big\n \\%s\\\n", Utility, SymbolName); ExitCgiUtl (STS$K_ERROR, __LINE__); } *sptr = '\0'; if (Debug) fprintf (stdout, "SymbolName: |%s|\n", SymbolName); /* substitute underscores for unacceptable symbol name characters */ SymbolNameLength = CgiLibUrlDecode(SymbolName); for (sptr = SymbolName; *sptr; sptr++) if (!isalnum(*sptr)) *sptr = '_'; if (*cptr == '=') cptr++; } if (!SymbolValueLength) { /************************************/ /* get the (next part) symbol value */ /************************************/ zptr = (sptr = SymbolValue) + sizeof(SymbolValue)-1; DelimChar[1] = '\0'; while (*cptr && *cptr != '&' && sptr < zptr && sptr - SymbolValue < SymbolValueMax) { /* URL decode */ while (*cptr == '\r' || *cptr == '\n') cptr++; if (*cptr == '&') break; if (*cptr == '%') { cptr++; while (*cptr == '\r' || *cptr == '\n') cptr++; if (*cptr == '&') break; ch = 0; if (*cptr >= '0' && *cptr <= '9') ch = (*cptr - (int)'0') << 4; else if (toupper(*cptr) >= 'A' && toupper(*cptr) <= 'F') ch = (toupper(*cptr) - (int)'A' + 10) << 4; else ch = 0; if (*cptr) cptr++; while (*cptr == '\r' || *cptr == '\n') cptr++; if (*cptr == '&') break; if (*cptr >= '0' && *cptr <= '9') ch += (*cptr - (int)'0'); else if (toupper(*cptr) >= 'A' && toupper(*cptr) <= 'F') ch += (toupper(*cptr) - (int)'A' + 10); else ch = 0; if (*cptr) cptr++; } else if (*cptr == '+') { ch = ' '; cptr++; } else ch = *cptr++; if (DelimChar[0] && (DelimChar[0] == (DelimChar[1] = ch))) { DelimCharHitCount++; break; } if (DoCliSymbolsLines && ch == '\r') continue; else if (DoCliSymbolsLines && ch == '\n') break; else if (DoCliSymbolsNoControl && iscntrl(ch)) continue; else if (isprint(ch) || isspace(ch)) *sptr++ = ch; } *sptr = '\0'; SymbolValueLength = sptr - SymbolValue; if (Debug) fprintf (stdout, "SymbolValue: |%s|\n", SymbolValue); if (DelimChar[0] && DelimChar[0] == DelimChar[1]) FieldHasMultipleSymbols = true; else { if (*cptr == '&') cptr++; else if (*cptr) FieldHasMultipleSymbols = true; else FieldHasMultipleSymbols = false; } if (FieldHasMultipleSymbols || SymbolValueCount) { SymbolValueCount += SymbolValueLength; sprintf (SymbolName+SymbolNameLength, "%c%d", BugSymbolNaming ? '_' : '$', ++SymbolValuePart); if (Debug) fprintf (stdout, "SymbolName: |%s|\n", SymbolName); SymbolValuePartLength = strlen(SymbolName+SymbolNameLength); } } if (Debug) fprintf (stdout, "|%s=%s|\n", SymbolName, SymbolValue); if (DuplicateSymbolCount) { if (DuplicateSymbolCount == 2) { if (VMSok (status = GetCliSymbol (SymbolName, Scratch, NULL))) { /* first instance, move it to a "$$1" name */ strcpy (SymbolName+SymbolNameLength+SymbolValuePartLength, "$$1"); SetCliSymbol (SymbolName, Scratch, 0); SymbolCount++; } } sprintf (SymbolName+SymbolNameLength+SymbolValuePartLength, "$$%d", DuplicateSymbolCount); } /*******************************/ /* check for duplicate symbols */ /*******************************/ while (VMSok (status = GetCliSymbol (SymbolName, Scratch, NULL))) { /* found an existing instance of this symbol name, try another */ sprintf (SymbolName+SymbolNameLength+SymbolValuePartLength, "$$%d", ++DuplicateSymbolCount); } if (VMSnok (status) && status != LIB$_NOSUCHSYM) ExitCgiUtl (status, __LINE__); if (DuplicateSymbolCount == 1) { DuplicateFieldCount++; /* first duplicate, move origninal to a "$$1" name */ SymbolName[SymbolNameLength+SymbolValuePartLength] = '\0'; if (VMSok (status = GetCliSymbol (SymbolName, Scratch, NULL))) { strcpy (SymbolName+SymbolNameLength+SymbolValuePartLength, "$$1"); SetCliSymbol (SymbolName, Scratch, 0); SymbolCount++; } /* move any related multi-symbol value into a "$$1" also */ sprintf (SymbolValueCountName, "%*.*s$0", SymbolNameLength+SymbolValuePartLength, SymbolNameLength+SymbolValuePartLength, SymbolName); if (VMSok (status = GetCliSymbol (SymbolValueCountName, Scratch, NULL))) { strcpy (SymbolValueCountName+ SymbolNameLength+ SymbolValuePartLength, "$$1"); SetCliSymbol (SymbolValueCountName, Scratch, 0); SymbolCount++; } SymbolValueCountName[0] = '\0'; /* next one will be instance two */ DuplicateSymbolCount++; strcpy (SymbolName+SymbolNameLength+SymbolValuePartLength, "$$2"); } /***************************/ /* assign the symbol value */ /***************************/ SetCliSymbol (SymbolName, SymbolValue, 0); SymbolCount++; if (DuplicateSymbolCount && !SymbolValueCount) { /* set again, the last value encountered (backward-compatiblity) */ SymbolName[SymbolNameLength] = '\0'; SetCliSymbol (SymbolName, SymbolValue, 0); } if (DelimChar[0] && DelimChar[0] == DelimChar[1]) { sprintf (Scratch, "%s$", SymbolName); DelimChar[1] = '\0'; SetCliSymbol (Scratch, DelimChar, 0); } if (FieldHasMultipleSymbols) { /* per-line symbols, or symbol value reached max size before end */ if (!SymbolValueCountName[0]) { /* generate a symbol name for total size of multi-symbol value */ if (DuplicateSymbolCount) sprintf (SymbolValueCountName, "%*.*s$0$$%d", SymbolNameLength, SymbolNameLength, SymbolName, DuplicateSymbolCount); else sprintf (SymbolValueCountName, "%*.*s$0", SymbolNameLength, SymbolNameLength, SymbolName); } SymbolValueLength = 0; SymbolValue[0] = '\0'; } else { if (SymbolValueCountName[0]) { /* assign symbol for total size of multi-symbol value */ SetCliSymbol (SymbolValueCountName, NULL, SymbolValueCount); SymbolCount++; if (DuplicateSymbolCount) { /* chop "name$0$$n" off at "name$0" (backward compatibility) */ SymbolValueCountName[SymbolNameLength+2] = '\0'; SetCliSymbol (SymbolValueCountName, NULL, SymbolValueCount); } } if (DuplicateSymbolCount) { /* create a symbol containing the count of these duplicate names */ strcpy (SymbolName+SymbolNameLength, "$$0"); SetCliSymbol (SymbolName, NULL, DuplicateSymbolCount); if (DuplicateSymbolCount <= 2) SymbolCount++; } DuplicateSymbolCount = SymbolValueCount = SymbolValueLength = SymbolValuePart = 0; SymbolValue[0] = SymbolValueCountName[0] = '\0'; /* if delimiter and hit do not reset name, otherwise ... */ if (!DelimChar[0] || DelimChar[0] != DelimChar[1]) { /* symbol containing number of delimiters encountered */ strcpy (SymbolName+SymbolNameLength, "$0$"); SetCliSymbol (SymbolName, NULL, DelimCharHitCount); DelimCharHitCount = SymbolNameLength = 0; SymbolName[0] = SymbolValueCountName[0] = '\0'; } } } /***************/ /* end of body */ /***************/ sprintf (SymbolName, "%s$FIELDS", SymbolPrefixPtr); SetCliSymbol (SymbolName, NULL, FieldCount); sprintf (SymbolName, "%s$FIELDS_DUPLICATE", SymbolPrefixPtr); SetCliSymbol (SymbolName, NULL, DuplicateFieldCount); sprintf (SymbolName, "%s$MAXSYM", SymbolPrefixPtr); SetCliSymbol (SymbolName, NULL, SymbolValueMax); sprintf (SymbolName, "%s$SYMBOLS", SymbolPrefixPtr); SetCliSymbol (SymbolName, NULL, SymbolCount); } /*****************************************************************************/ /* Strip carriage-returns, leaving only newlines (for text files). */ int StripCarRet (char *String) { char *cptr, *sptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "StripCarRet() |%s|\n", String); /* don't start actually copying unless we really have to! */ cptr = String; while (*cptr && *cptr != '\r') cptr++; sptr = cptr; while (*cptr) { if (*cptr == '\r') cptr++; else *sptr++ = *cptr++; } *sptr = '\0'; if (Debug) fprintf (stdout, "|%s|\n", String); return (sptr-String); } /*****************************************************************************/ /* Get the contents of the specified DCL symbol, URL-decode, reset original symbol name with the decoded value. */ DecodeSymbolPercent (char *SymbolName) { int status, SymbolLength; char SymbolValue [8192]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "EscapeSymbolQuotes() |%s|\n", SymbolName); if (!CliParameterPtr) { if (!ExitQuietly) fprintf (stdout, "%%%s-E-UNPERSYM, symbol name not specified\n", Utility); ExitCgiUtl (STS$K_ERROR, __LINE__); } if (VMSnok (status = GetCliSymbol (SymbolName, SymbolValue, &SymbolLength))) ExitCgiUtl (status, __LINE__); SymbolValue[SymbolLength] = '\0'; CgiLibUrlDecode(SymbolValue); if (Debug) fprintf (stdout, "|%s|\n", SymbolValue); SetCliSymbol (SymbolName, SymbolValue, 0); } /*****************************************************************************/ /* Get the contents of the specified DCL symbol. The copy to scratch storage, adding an extra double-quote for each found in the source symbol. Reset original symbol name with the value containing doubled-up quotes. */ EscapeSymbolQuotes (char *SymbolName) { int status, SymbolLength; char *cptr, *eptr, *sptr, *zptr; char SymbolValue [8192], DquoteValue [8192+256]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "EscapeSymbolQuotes() |%s|\n", SymbolName); if (!CliParameterPtr) { if (!ExitQuietly) fprintf (stdout, "%%%s-E-2QUOTE, symbol name not specified\n", Utility); ExitCgiUtl (STS$K_ERROR, __LINE__); } if (VMSnok (status = GetCliSymbol (SymbolName, SymbolValue, &SymbolLength))) ExitCgiUtl (status, __LINE__); zptr = (sptr = DquoteValue) + SymbolValueMax; eptr = (cptr = SymbolValue) + SymbolLength; while (cptr < eptr && sptr < zptr) { if (*cptr == '\"' && sptr < zptr) *sptr++ = '\"'; if (sptr < zptr) *sptr++ = *cptr++; } /* +1 makes it an error */ if (sptr >= zptr) ExitCgiUtl (SS$_BUFFEROVF+1, __LINE__); *sptr = '\0'; if (Debug) fprintf (stdout, "|%s|\n", DquoteValue); SetCliSymbol (SymbolName, DquoteValue, NULL); } /*****************************************************************************/ /* Get the contents of the specified DCL symbol. Split it into one or more strings (symbols) according the the delimiter supplied. */ SplitSymbol (char *SplitSymbolName) { int status, DelimCharHitCount, SymbolCount, SymbolLength, SymbolValueLength, SymbolValueTotal; char *cptr, *eptr, *sptr, *zptr; char SymbolName [256], SymbolValue [8192], ParseValue [8192]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "SplitSymbol() |%s|\n", SymbolName); if (!CliParameterPtr) { if (!ExitQuietly) fprintf (stdout, "%%%s-E-SPLIT, symbol name not specified\n", Utility); ExitCgiUtl (STS$K_ERROR, __LINE__); } if (!DelimChar[0]) { if (!ExitQuietly) fprintf (stdout, "%%%s-E-SPLIT, delimiter not specified\n", Utility); ExitCgiUtl (STS$K_ERROR, __LINE__); } if (VMSnok (status = GetCliSymbol (SplitSymbolName, SymbolValue, &SymbolLength))) ExitCgiUtl (status, __LINE__); /* allow 16 for appending some dollar-counts later on! */ if (strlen(SplitSymbolName) > sizeof(SymbolName)-16) { if (!ExitQuietly) fprintf (stdout, "%%%s-E-SYMBOLNAME, too big\n \\%s\\\n", Utility, SymbolName); ExitCgiUtl (STS$K_ERROR, __LINE__); } DelimCharHitCount = SymbolCount = SymbolValueTotal = 0; cptr = SymbolValue; while (*cptr) { zptr = (sptr = SymbolValue) + sizeof(SymbolValue)-1; while (*cptr && sptr < zptr && sptr - SymbolValue < SymbolValueMax) { if (DelimChar[0] && (DelimChar[0] == (DelimChar[1] = *cptr))) { DelimCharHitCount++; cptr++; break; } *sptr++ = *cptr++; } *sptr = '\0'; SymbolValueLength = sptr - SymbolValue; SymbolValueTotal += SymbolValueLength; sprintf (SymbolName, "%s$%d", SplitSymbolName, ++SymbolCount); SetCliSymbol (SymbolName, SymbolValue, 0); if (DelimChar[0] && DelimChar[0] == DelimChar[1]) { DelimChar[1] = '\0'; sprintf (SymbolName, "%s$%d$", SplitSymbolName, SymbolCount); SetCliSymbol (SymbolName, DelimChar, 0); if (!*cptr) { /* trailing empty delimiter */ sprintf (SymbolName, "%s$%d", SplitSymbolName, ++SymbolCount); SetCliSymbol (SymbolName, "", 0); } } } /* symbol containing total characters in all symbols */ sprintf (SymbolName, "%s$0", SplitSymbolName); SetCliSymbol (SymbolName, NULL, SymbolValueTotal); /* symbol containing number of delimiters encountered */ sprintf (SymbolName, "%s$0$", SplitSymbolName); SetCliSymbol (SymbolName, NULL, DelimCharHitCount); } /****************************************************************************/ /* Assign a global symbol. If the string pointer is null the numeric value is used. Symbol lengths are adjusted according to the maximum allowed for the version of the operating system. */ SetCliSymbol ( char *Name, char *String, int Value ) { static char ValueString [32]; static $DESCRIPTOR (NameDsc, ""); static $DESCRIPTOR (ValueDsc, ""); static $DESCRIPTOR (ValueFaoDsc, "!UL"); static $DESCRIPTOR (ValueStringDsc, ValueString); int len, status; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "SetCliSymbol() |%s|%s| %d\n", Name, String, Value); NameDsc.dsc$a_pointer = Name; NameDsc.dsc$w_length = strlen(Name); if (!String) { ValueDsc.dsc$a_pointer = ValueString; sys$fao (&ValueFaoDsc, &ValueDsc.dsc$w_length, &ValueStringDsc, Value); ValueString[ValueDsc.dsc$w_length] = '\0'; } else { ValueDsc.dsc$a_pointer = String; len = strlen(String); if (len > SymbolValueMax) ValueDsc.dsc$w_length = SymbolValueMax; else ValueDsc.dsc$w_length = len; } if (Debug) fprintf (stdout, "|%s| %d\n", Name, ValueDsc.dsc$w_length); if (VMSnok (status = lib$set_symbol (&NameDsc, &ValueDsc, &CliSymbolType))) ExitCgiUtl (status, __LINE__); } /*****************************************************************************/ /* Gte a global symbol name's value. Value storage must provide at least 1024 bytes storage for >=7.0 and 256 bytes for <=6.2. */ int GetCliSymbol ( char *Name, char *Value, int *LengthPtr ) { static $DESCRIPTOR (NameDsc, ""); static $DESCRIPTOR (ValueDsc, ""); int status; unsigned short Length; unsigned long SymbolTable; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GetCliSymbol() |%s|\n", Name); NameDsc.dsc$a_pointer = Name; NameDsc.dsc$w_length = strlen(Name); ValueDsc.dsc$a_pointer = Value; if (VmsVersion() < 700) ValueDsc.dsc$w_length = 255; else if (VmsVersion() < 732) ValueDsc.dsc$w_length = 1023; else ValueDsc.dsc$w_length = 8191; status = lib$get_symbol (&NameDsc, &ValueDsc, &Length, &SymbolTable); if (Debug) fprintf (stdout, "lib$get_symbol() %%X%08.08X\n", status); /* if it's not a global symbol then we're not interested! */ if (SymbolTable != CliSymbolType) status = LIB$_NOSUCHSYM; if (VMSnok (status)) { Value[0] = '\0'; return (status); } if (LengthPtr) *LengthPtr = Length; Value[Length] = '\0'; if (Debug) fprintf (stdout, "%d |%s|\n", Length, Value); return (status); } /****************************************************************************/ /* Return an integer reflecting the major, minor and other VMS version number. For example, return 600 for "V6.0", 730 for "V7.3" and 732 for "V7.3-2". */ int VmsVersion () { static int VersionInteger; static char SyiVersion [8+1]; static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } SyiItems [] = { { 8, SYI$_VERSION, &SyiVersion, 0 }, { 0,0,0,0 } }; int status; char *cptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "VmsVersion() %d\n", VersionInteger); if (VersionInteger) return (VersionInteger); if (VMSnok (status = sys$getsyiw (0, 0, 0, &SyiItems, 0, 0, 0))) ExitCgiUtl (status, __LINE__); if (cptr = getenv("WASD_VMS_VERSION")) strncpy (SyiVersion, cptr, sizeof(SyiVersion)); SyiVersion[sizeof(SyiVersion)-1] = '\0'; if (Debug) fprintf (stdout, "SyiVersion |%s|\n", SyiVersion); if (SyiVersion[0] == 'V' && isdigit(SyiVersion[1]) && SyiVersion[2] == '.' && isdigit(SyiVersion[3])) { /* e.g. "V7.3" */ VersionInteger = ((SyiVersion[1]-48) * 100) + ((SyiVersion[3]-48) * 10); /* if something like "V7.3-2" */ if (SyiVersion[4] == '-') VersionInteger += SyiVersion[5]-48; } else { if (!ExitQuietly); fprintf (stdout, "%%%s-E-VMS, cannot understand VMS version string \"%s\"\n\ -%s-I-KLUDGE, continue by using WASD_VMS_VERSION environment variable\n", Utility, SyiVersion, Utility); ExitCgiUtl (SS$_BUGCHECK, __LINE__); } if (Debug) fprintf (stdout, "%d\n", VersionInteger); return (VersionInteger); } /*****************************************************************************/ /* Just to get a line number when debugging! */ void ExitCgiUtl ( int StatusValue, int LineNumber ) { /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ExitCgiUtl() status:%%X%08.08X line:%d\n", StatusValue, LineNumber); if (StatusValue == STS$K_ERROR) StatusValue |= STS$M_INHIB_MSG; exit (StatusValue | ExitQuietly); } /*****************************************************************************/ /* Get "command-line" parameters, whether from the command-line or from a configuration symbol or logical containing the equivalent. */ ProcessParameters (char* clptr) { static char CommandLine [256]; static unsigned long Flags = 0; int status; unsigned short Length; char ch; char *aptr, *cptr, *sptr; $DESCRIPTOR (CommandLineDsc, CommandLine); /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ProcessParameters()\n"); aptr = NULL; ch = *clptr; for (;;) { if (aptr && *aptr == '/') *aptr = '\0'; if (!ch) break; *clptr = ch; if (Debug) fprintf (stdout, "clptr |%s|\n", clptr); while (*clptr && isspace(*clptr)) *clptr++ = '\0'; aptr = clptr; if (*clptr == '/') clptr++; while (*clptr && !isspace (*clptr) && *clptr != '/') { if (*clptr != '\"') { clptr++; continue; } cptr = clptr; clptr++; while (*clptr) { if (*clptr == '\"') if (*(clptr+1) == '\"') clptr++; else break; *cptr++ = *clptr++; } *cptr = '\0'; if (*clptr) clptr++; } ch = *clptr; if (*clptr) *clptr = '\0'; if (Debug) fprintf (stdout, "aptr |%s|\n", aptr); if (!*aptr) continue; if (strsame (aptr, "/2QUOTE", 3)) { DoEscapeSymbolQuotes = true; continue; } if (strsame (aptr, "/BODY", 4)) { DoRequestBody = true; continue; } if (strsame (aptr, "/BUGSYM", -1)) { BugSymbolNaming = true; continue; } if (strsame (aptr, "/CGIRESPONSE=", 4)) { DoCgiResponse = true; DoResponse = false; cptr = GetParameterString (aptr); if (!cptr || !*cptr) continue; CliResponsePtr = cptr; continue; } if (strsame (aptr, "/CHARSET=", 4)) { cptr = GetParameterString (aptr); if (!cptr) continue; /* allow an empty string */ CliCharsetPtr = cptr; continue; } if (strsame (aptr, "/CONTENT=", 4)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); cptr = GetParameterString (aptr); if (!cptr || !*cptr) continue; CliContentTypePtr = cptr; continue; } if (strsame (aptr, "/COPY", 4)) { DoCopy = true; continue; } if (strsame (aptr, "/DBUG", -1)) { Debug = true; continue; } if (strsame (aptr, "/DELIMITER=", 4)) { cptr = GetParameterString (aptr); if (!cptr || !*cptr) continue; DelimChar[0] = cptr[0]; continue; } if (strsame (aptr, "/EXPIRED", 4)) { DoPreExpired = true; continue; } if (strsame (aptr, "/FIELD=", 4)) { cptr = GetParameterString (aptr); if (!cptr || !*cptr) continue; FieldOnlyPtr = cptr; FieldToFileMapIndex++ ; if (FieldToFileMapIndex == MAX_FIELD_TO_FILE) { if (!ExitQuietly) fprintf (stdout, "%%%s-E-TOOMANY, Too many FIELD qualifiers\n \\%s\\\n", Utility, cptr); ExitCgiUtl (STS$K_ERROR, __LINE__) ; } FieldToFileMap[FieldToFileMapIndex].FieldNamePtr = cptr ; continue; } if (strsame (aptr, "/FORMAT=", 4)) { cptr = GetParameterString (aptr); if (!cptr || !*cptr) continue; if (strsame (cptr, "HEADINGS", 4)) { DoFormatHeadings = true; DoFormatNames = false; } else if (strsame (cptr, "NAMES", 4)) { DoFormatHeadings = false; DoFormatNames = true; } else if (strsame (cptr, "NONE", 4)) { DoFormatHeadings = false; DoFormatNames = false; } else { if (!ExitQuietly) fprintf (stdout, "%%%s-E-IVKEYW, invalid keyword\n \\%s\\\n", Utility, cptr); ExitCgiUtl (STS$K_ERROR, __LINE__); } continue; } if (strsame (aptr, "/GLOBAL", -1)) { CliSymbolType = LIB$K_CLI_GLOBAL_SYM; continue; } if (strsame (aptr, "/HEADER=", 4)) { cptr = GetParameterString (aptr); if (!cptr || !*cptr) continue; AdditionalHeadersIndex++ ; if (AdditionalHeadersIndex == MAX_ADDITIONAL_HEADERS) { if (!ExitQuietly) fprintf (stdout, "%%%s-E-TOOMANY, Too many HEADER qualifiers\n \\%s\\\n", Utility, cptr); ExitCgiUtl (STS$K_ERROR, __LINE__) ; } AdditionalHeaders[AdditionalHeadersIndex] = cptr ; continue; } if (strsame (aptr, "/LENGTH", 4)) { DoContentLength = true; continue; } if (strsame (aptr, "/LOCAL", -1)) { CliSymbolType = LIB$K_CLI_LOCAL_SYM; continue; } if (strsame (aptr, "/LOCATION=", 4)) { DoResponse = true; cptr = GetParameterString (aptr); if (!cptr || !*cptr) continue; CliLocationPtr = cptr; continue; } if (strsame (aptr, "/MAXSYM=", 7)) { cptr = GetParameterString (aptr); if (!cptr || !*cptr) continue; SymbolValueMax = atoi(cptr); continue; } if (strsame (aptr, "/MULTIPART", 4)) { DoMultipartFormData = true; continue; } if (strsame (aptr, "/OUTPUT=", 4)) { if (FieldToFileMapIndex != -1) { FieldToFileMap[FieldToFileMapIndex].FileNamePtr = NULL ; } cptr = GetParameterString (aptr); if (!cptr || !*cptr) continue; CliOutputPtr = cptr; if (FieldToFileMapIndex != -1) { FieldToFileMap[FieldToFileMapIndex].FileNamePtr = cptr ; } continue; } if (strsame (aptr, "/UNPERSYM", -1)) { DoDecodeSymbolPercent = true; continue; } if (strsame (aptr, "/PLUS", -1)) { DoCgiPlus = true; continue; } if (strsame (aptr, "/PREFIX=", 4)) { cptr = GetParameterString (aptr); if (!cptr || !*cptr) continue; SymbolPrefixPtr = cptr; continue; } if (strsame (aptr, "/QUIETLY", 4)) { ExitQuietly = STS$M_INHIB_MSG; continue; } if (strsame (aptr, "/NOQUIETLY", 4)) { ExitQuietly = 0; continue; } if (strsame (aptr, "/RESPONSE=", 4)) { DoResponse = true; DoCgiResponse = false; cptr = GetParameterString (aptr); if (!cptr || !*cptr) continue; CliResponsePtr = cptr; continue; } if (strsame (aptr, "/SPLIT=", 4)) { DoSplitSymbol = true; cptr = GetParameterString (aptr); if (!cptr || !*cptr) continue; DelimChar[0] = cptr[0]; continue; } if (strsame (aptr, "/SUBSTITUTE=", 4)) { /* get this one by hand :^) */ for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (*cptr) cptr++; if (*cptr) ParamSubsChar = *cptr; continue; } if (strsame (aptr, "/SOFTWAREID", 4) || strsame (aptr, "/VERSION", 4)) { fprintf (stdout, "%%%s-I-SOFTWAREID, %s\n%s\n", Utility, SoftwareID, CopyrightInfo); exit (SS$_NORMAL); } if (strsame (aptr, "/SYMBOLS=", 4)) { DoCliSymbols = true; cptr = GetParameterString (aptr); if (!cptr || !*cptr) continue; while (*cptr) { if (*cptr == '(' || *cptr == ',' || *cptr == ')') { cptr++; continue; } if (strsame (cptr, "LINES", 4)) DoCliSymbolsLines = true; else if (strsame (cptr, "NOCONTROL", 6)) DoCliSymbolsNoControl = true; else { if (!ExitQuietly) fprintf (stdout, "%%%s-E-IVKEYW, invalid keyword\n \\%s\\\n", Utility, cptr); ExitCgiUtl (STS$K_ERROR, __LINE__); } while (*cptr && *cptr != ',') cptr++; } continue; } if (strsame (aptr, "/URLDECODE", 4)) { DoUrlDecode = true; continue; } if (*aptr == '/') { if (!ExitQuietly) fprintf (stdout, "%%%s-E-IVQUAL, unrecognized qualifier\n \\%s\\\n", Utility, aptr+1); ExitCgiUtl (STS$K_ERROR, __LINE__); } if (!CliParameterPtr) { cptr = GetParameterString (aptr); if (!cptr || !*cptr) continue; CliParameterPtr = aptr; continue; } if (!ExitQuietly) fprintf (stdout, "%%%s-E-MAXPARM, too many parameters\n \\%s\\\n", Utility, aptr); ExitCgiUtl (STS$K_ERROR, __LINE__); } } /*****************************************************************************/ /* */ GetParameters () { static char CommandLine [256]; static unsigned long Flags = 0; int status; unsigned short Length; char ch; char *aptr, *cptr, *clptr, *sptr; $DESCRIPTOR (CommandLineDsc, CommandLine); /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GetParameters()\n"); if (!(clptr = getenv ("CGIUTL$PARAM"))) { /* get the entire command line following the verb */ status = lib$get_foreign (&CommandLineDsc, 0, &Length, &Flags); if (VMSnok (status)) ExitCgiUtl (status, __LINE__); (clptr = CommandLine)[Length] = '\0'; ProcessParameters(clptr) ; } else { char theLogicalName[32] ; int i ; ProcessParameters(clptr) ; for (i = 1;;i++) { sprintf(theLogicalName, "CGIUTL$PARAM_%d", i) ; clptr = getenv(theLogicalName) ; if (!clptr) { return ; } ProcessParameters(clptr) ; } } } /*****************************************************************************/ /* Get a string from the command-line. It can be a qualifier specified string (e.g. /QUALIFIER=<string>) or just a string supplied as a parameter. If a qualifier then it must have a string following the '=' otherwise a NULL is returned. If the string begins with the character specified by global variable 'ParamSubsChar' (which in turn can be specified by the /SUBSTITUTE= qualifier) and the parameter string begins with this as the first character the remainder of the string is used as a C-RTL getenv() function argument and it's value is attempted to be resolved. If it does not exist the function returns a NULL. If it does exist the a pointer to it's value is returned. If the string does not begin with the substitution character a pointer to it is returned. The substitution character may be escaped using a leading backslash. */ char* GetParameterString (char *aptr) { char *cptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GetParameterString() %c |%s|\n", ParamSubsChar, aptr); if (!aptr) return (NULL); if (*aptr == '/') { for (cptr = aptr; *cptr && *cptr != '=' && !isspace(*cptr); cptr++); if (*cptr != '=') return (NULL); cptr++; } else cptr = aptr; if (*cptr == ParamSubsChar) cptr = getenv(cptr+1); else if (*cptr == '\\' && *(cptr+1) == ParamSubsChar) cptr++; if (Debug) fprintf (stdout, "|%s|\n", cptr ? cptr : "(null)"); return (cptr); } /****************************************************************************/ /* Does a case-insensitive, character-by-character string compare and returns true if two strings are the same, or false if not. If a maximum number of characters are specified only those will be compared, if the entire strings should be compared then specify the number of characters as 0. */ boolean strsame ( char *sptr1, char *sptr2, int count ) { /*********/ /* begin */ /*********/ /** if (Debug) fprintf (stdout, "strsame() |%s|%s|\n", sptr1, sptr2); **/ while (*sptr1 && *sptr2) { if (toupper (*sptr1++) != toupper (*sptr2++)) return (false); if (count) if (!--count) return (true); } if (*sptr1 || *sptr2) return (false); else return (true); } /*****************************************************************************/