1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
|
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/*
* mod_negotiation.c: keeps track of MIME types the client is willing to
* accept, and contains code to handle type arbitration.
*
* rst
*/
#include "apr.h"
#include "apr_strings.h"
#include "apr_file_io.h"
#include "apr_lib.h"
#define APR_WANT_STRFUNC
#include "apr_want.h"
#include "ap_config.h"
#include "httpd.h"
#include "http_config.h"
#include "http_request.h"
#include "http_protocol.h"
#include "http_core.h"
#include "http_log.h"
#include "util_script.h"
#define MAP_FILE_MAGIC_TYPE "application/x-type-map"
/* Commands --- configuring document caching on a per (virtual?)
* server basis...
*/
typedef struct {
int forcelangpriority;
apr_array_header_t *language_priority;
} neg_dir_config;
/* forcelangpriority flags
*/
#define FLP_UNDEF 0 /* Same as FLP_DEFAULT, but base overrides */
#define FLP_NONE 1 /* Return 406, HTTP_NOT_ACCEPTABLE */
#define FLP_PREFER 2 /* Use language_priority rather than MC */
#define FLP_FALLBACK 4 /* Use language_priority rather than NA */
#define FLP_DEFAULT FLP_PREFER
module AP_MODULE_DECLARE_DATA negotiation_module;
static void *create_neg_dir_config(apr_pool_t *p, char *dummy)
{
neg_dir_config *new = (neg_dir_config *) apr_palloc(p,
sizeof(neg_dir_config));
new->forcelangpriority = FLP_UNDEF;
new->language_priority = NULL;
return new;
}
static void *merge_neg_dir_configs(apr_pool_t *p, void *basev, void *addv)
{
neg_dir_config *base = (neg_dir_config *) basev;
neg_dir_config *add = (neg_dir_config *) addv;
neg_dir_config *new = (neg_dir_config *) apr_palloc(p,
sizeof(neg_dir_config));
/* give priority to the config in the subdirectory */
new->forcelangpriority = (add->forcelangpriority != FLP_UNDEF)
? add->forcelangpriority
: base->forcelangpriority;
new->language_priority = add->language_priority
? add->language_priority
: base->language_priority;
return new;
}
static const char *set_language_priority(cmd_parms *cmd, void *n_,
const char *lang)
{
neg_dir_config *n = n_;
const char **langp;
if (!n->language_priority)
n->language_priority = apr_array_make(cmd->pool, 4, sizeof(char *));
langp = (const char **) apr_array_push(n->language_priority);
*langp = lang;
return NULL;
}
static const char *set_force_priority(cmd_parms *cmd, void *n_, const char *w)
{
neg_dir_config *n = n_;
if (!strcasecmp(w, "None")) {
if (n->forcelangpriority & ~FLP_NONE) {
return "Cannot combine ForceLanguagePriority options with None";
}
n->forcelangpriority = FLP_NONE;
}
else if (!strcasecmp(w, "Prefer")) {
if (n->forcelangpriority & FLP_NONE) {
return "Cannot combine ForceLanguagePriority options None and "
"Prefer";
}
n->forcelangpriority |= FLP_PREFER;
}
else if (!strcasecmp(w, "Fallback")) {
if (n->forcelangpriority & FLP_NONE) {
return "Cannot combine ForceLanguagePriority options None and "
"Fallback";
}
n->forcelangpriority |= FLP_FALLBACK;
}
else {
return apr_pstrcat(cmd->pool, "Invalid ForceLanguagePriority option ",
w, NULL);
}
return NULL;
}
static const char *cache_negotiated_docs(cmd_parms *cmd, void *dummy,
int arg)
{
ap_set_module_config(cmd->server->module_config, &negotiation_module,
(arg ? "Cache" : NULL));
return NULL;
}
static int do_cache_negotiated_docs(server_rec *s)
{
return (ap_get_module_config(s->module_config,
&negotiation_module) != NULL);
}
static const command_rec negotiation_cmds[] =
{
AP_INIT_FLAG("CacheNegotiatedDocs", cache_negotiated_docs, NULL, RSRC_CONF,
"Either 'on' or 'off' (default)"),
AP_INIT_ITERATE("LanguagePriority", set_language_priority, NULL,
OR_FILEINFO,
"space-delimited list of MIME language abbreviations"),
AP_INIT_ITERATE("ForceLanguagePriority", set_force_priority, NULL,
OR_FILEINFO,
"Force LanguagePriority elections, either None, or "
"Fallback and/or Prefer"),
{NULL}
};
/*
* Record of available info on a media type specified by the client
* (we also use 'em for encodings and languages)
*/
typedef struct accept_rec {
char *name; /* MUST be lowercase */
float quality;
float level;
char *charset; /* for content-type only */
} accept_rec;
/*
* Record of available info on a particular variant
*
* Note that a few of these fields are updated by the actual negotiation
* code. These are:
*
* level_matched --- initialized to zero. Set to the value of level
* if the client actually accepts this media type at that
* level (and *not* if it got in on a wildcard). See level_cmp
* below.
* mime_stars -- initialized to zero. Set to the number of stars
* present in the best matching Accept header element.
* 1 for star/star, 2 for type/star and 3 for
* type/subtype.
*
* definite -- initialized to 1. Set to 0 if there is a match which
* makes the variant non-definite according to the rules
* in rfc2296.
*/
typedef struct var_rec {
request_rec *sub_req; /* May be NULL (is, for map files) */
const char *mime_type; /* MUST be lowercase */
const char *file_name; /* Set to 'this' (for map file body content) */
apr_off_t body; /* Only for map file body content */
const char *content_encoding;
apr_array_header_t *content_languages; /* list of lang. for this variant */
const char *content_charset;
const char *description;
/* The next five items give the quality values for the dimensions
* of negotiation for this variant. They are obtained from the
* appropriate header lines, except for source_quality, which
* is obtained from the variant itself (the 'qs' parameter value
* from the variant's mime-type). Apart from source_quality,
* these values are set when we find the quality for each variant
* (see best_match()). source_quality is set from the 'qs' parameter
* of the variant description or mime type: see set_mime_fields().
*/
float lang_quality; /* quality of this variant's language */
float encoding_quality; /* ditto encoding */
float charset_quality; /* ditto charset */
float mime_type_quality; /* ditto media type */
float source_quality; /* source quality for this variant */
/* Now some special values */
float level; /* Auxiliary to content-type... */
apr_off_t bytes; /* content length, if known */
int lang_index; /* Index into LanguagePriority list */
int is_pseudo_html; /* text/html, *or* the INCLUDES_MAGIC_TYPEs */
/* Above are all written-once properties of the variant. The
* three fields below are changed during negotiation:
*/
float level_matched;
int mime_stars;
int definite;
} var_rec;
/* Something to carry around the state of negotiation (and to keep
* all of this thread-safe)...
*/
typedef struct {
apr_pool_t *pool;
request_rec *r;
neg_dir_config *conf;
char *dir_name;
int accept_q; /* 1 if an Accept item has a q= param */
float default_lang_quality; /* fiddle lang q for variants with no lang */
/* the array pointers below are NULL if the corresponding accept
* headers are not present
*/
apr_array_header_t *accepts; /* accept_recs */
apr_array_header_t *accept_encodings; /* accept_recs */
apr_array_header_t *accept_charsets; /* accept_recs */
apr_array_header_t *accept_langs; /* accept_recs */
apr_array_header_t *avail_vars; /* available variants */
int count_multiviews_variants; /* number of variants found on disk */
int is_transparent; /* 1 if this resource is trans. negotiable */
int dont_fiddle_headers; /* 1 if we may not fiddle with accept hdrs */
int ua_supports_trans; /* 1 if ua supports trans negotiation */
int send_alternates; /* 1 if we want to send an Alternates header */
int may_choose; /* 1 if we may choose a variant for the client */
int use_rvsa; /* 1 if we must use RVSA/1.0 negotiation algo */
} negotiation_state;
/* A few functions to manipulate var_recs.
* Cleaning out the fields...
*/
static void clean_var_rec(var_rec *mime_info)
{
mime_info->sub_req = NULL;
mime_info->mime_type = "";
mime_info->file_name = "";
mime_info->body = 0;
mime_info->content_encoding = NULL;
mime_info->content_languages = NULL;
mime_info->content_charset = "";
mime_info->description = "";
mime_info->is_pseudo_html = 0;
mime_info->level = 0.0f;
mime_info->level_matched = 0.0f;
mime_info->bytes = -1;
mime_info->lang_index = -1;
mime_info->mime_stars = 0;
mime_info->definite = 1;
mime_info->charset_quality = 1.0f;
mime_info->encoding_quality = 1.0f;
mime_info->lang_quality = 1.0f;
mime_info->mime_type_quality = 1.0f;
mime_info->source_quality = 0.0f;
}
/* Initializing the relevant fields of a variant record from the
* accept_info read out of its content-type, one way or another.
*/
static void set_mime_fields(var_rec *var, accept_rec *mime_info)
{
var->mime_type = mime_info->name;
var->source_quality = mime_info->quality;
var->level = mime_info->level;
var->content_charset = mime_info->charset;
var->is_pseudo_html = (!strcmp(var->mime_type, "text/html")
|| !strcmp(var->mime_type, INCLUDES_MAGIC_TYPE)
|| !strcmp(var->mime_type, INCLUDES_MAGIC_TYPE3));
}
/* Create a variant list validator in r using info from vlistr. */
static void set_vlist_validator(request_rec *r, request_rec *vlistr)
{
/* Calculating the variant list validator is similar to
* calculating an etag for the source of the variant list
* information, so we use ap_make_etag(). Note that this
* validator can be 'weak' in extreme case.
*/
ap_update_mtime(vlistr, vlistr->finfo.mtime);
r->vlist_validator = ap_make_etag(vlistr, 0);
/* ap_set_etag will later take r->vlist_validator into account
* when creating the etag header
*/
}
/*****************************************************************
*
* Parsing (lists of) media types and their parameters, as seen in
* HTTPD header lines and elsewhere.
*/
/*
* Get a single mime type entry --- one media type and parameters;
* enter the values we recognize into the argument accept_rec
*/
static const char *get_entry(apr_pool_t *p, accept_rec *result,
const char *accept_line)
{
result->quality = 1.0f;
result->level = 0.0f;
result->charset = "";
/*
* Note that this handles what I gather is the "old format",
*
* Accept: text/html text/plain moo/zot
*
* without any compatibility kludges --- if the token after the
* MIME type begins with a semicolon, we know we're looking at parms,
* otherwise, we know we aren't. (So why all the pissing and moaning
* in the CERN server code? I must be missing something).
*/
result->name = ap_get_token(p, &accept_line, 0);
ap_str_tolower(result->name); /* You want case insensitive,
* you'll *get* case insensitive.
*/
/* KLUDGE!!! Default HTML to level 2.0 unless the browser
* *explicitly* says something else.
*/
if (!strcmp(result->name, "text/html") && (result->level == 0.0)) {
result->level = 2.0f;
}
else if (!strcmp(result->name, INCLUDES_MAGIC_TYPE)) {
result->level = 2.0f;
}
else if (!strcmp(result->name, INCLUDES_MAGIC_TYPE3)) {
result->level = 3.0f;
}
while (*accept_line == ';') {
/* Parameters ... */
char *parm;
char *cp;
char *end;
++accept_line;
parm = ap_get_token(p, &accept_line, 1);
/* Look for 'var = value' --- and make sure the var is in lcase. */
for (cp = parm; (*cp && !apr_isspace(*cp) && *cp != '='); ++cp) {
*cp = apr_tolower(*cp);
}
if (!*cp) {
continue; /* No '='; just ignore it. */
}
*cp++ = '\0'; /* Delimit var */
while (*cp && (apr_isspace(*cp) || *cp == '=')) {
++cp;
}
if (*cp == '"') {
++cp;
for (end = cp;
(*end && *end != '\n' && *end != '\r' && *end != '\"');
end++);
}
else {
for (end = cp; (*end && !apr_isspace(*end)); end++);
}
if (*end) {
*end = '\0'; /* strip ending quote or return */
}
ap_str_tolower(cp);
if (parm[0] == 'q'
&& (parm[1] == '\0' || (parm[1] == 's' && parm[2] == '\0'))) {
result->quality = (float)atof(cp);
}
else if (parm[0] == 'l' && !strcmp(&parm[1], "evel")) {
result->level = (float)atof(cp);
}
else if (!strcmp(parm, "charset")) {
result->charset = cp;
}
}
if (*accept_line == ',') {
++accept_line;
}
return accept_line;
}
/*****************************************************************
*
* Dealing with header lines ...
*
* Accept, Accept-Charset, Accept-Language and Accept-Encoding
* are handled by do_header_line() - they all have the same
* basic structure of a list of items of the format
* name; q=N; charset=TEXT
*
* where charset is only valid in Accept.
*/
static apr_array_header_t *do_header_line(apr_pool_t *p,
const char *accept_line)
{
apr_array_header_t *accept_recs;
if (!accept_line) {
return NULL;
}
accept_recs = apr_array_make(p, 40, sizeof(accept_rec));
while (*accept_line) {
accept_rec *new = (accept_rec *) apr_array_push(accept_recs);
accept_line = get_entry(p, new, accept_line);
}
return accept_recs;
}
/* Given the text of the Content-Languages: line from the var map file,
* return an array containing the languages of this variant
*/
static apr_array_header_t *do_languages_line(apr_pool_t *p,
const char **lang_line)
{
apr_array_header_t *lang_recs = apr_array_make(p, 2, sizeof(char *));
if (!lang_line) {
return lang_recs;
}
while (**lang_line) {
char **new = (char **) apr_array_push(lang_recs);
*new = ap_get_token(p, lang_line, 0);
ap_str_tolower(*new);
if (**lang_line == ',' || **lang_line == ';') {
++(*lang_line);
}
}
return lang_recs;
}
/*****************************************************************
*
* Handling header lines from clients...
*/
static negotiation_state *parse_accept_headers(request_rec *r)
{
negotiation_state *new =
(negotiation_state *) apr_pcalloc(r->pool, sizeof(negotiation_state));
accept_rec *elts;
apr_table_t *hdrs = r->headers_in;
int i;
new->pool = r->pool;
new->r = r;
new->conf = (neg_dir_config *)ap_get_module_config(r->per_dir_config,
&negotiation_module);
new->dir_name = ap_make_dirstr_parent(r->pool, r->filename);
new->accepts = do_header_line(r->pool, apr_table_get(hdrs, "Accept"));
/* calculate new->accept_q value */
if (new->accepts) {
elts = (accept_rec *) new->accepts->elts;
for (i = 0; i < new->accepts->nelts; ++i) {
if (elts[i].quality < 1.0) {
new->accept_q = 1;
}
}
}
new->accept_encodings =
do_header_line(r->pool, apr_table_get(hdrs, "Accept-Encoding"));
new->accept_langs =
do_header_line(r->pool, apr_table_get(hdrs, "Accept-Language"));
new->accept_charsets =
do_header_line(r->pool, apr_table_get(hdrs, "Accept-Charset"));
/* This is possibly overkill for some servers, heck, we have
* only 33 index.html variants in docs/docroot (today).
* Make this configurable?
*/
new->avail_vars = apr_array_make(r->pool, 40, sizeof(var_rec));
return new;
}
static void parse_negotiate_header(request_rec *r, negotiation_state *neg)
{
const char *negotiate = apr_table_get(r->headers_in, "Negotiate");
char *tok;
/* First, default to no TCN, no Alternates, and the original Apache
* negotiation algorithm with fiddles for broken browser configs.
*
* To save network bandwidth, we do not configure to send an
* Alternates header to the user agent by default. User
* agents that want an Alternates header for agent-driven
* negotiation will have to request it by sending an
* appropriate Negotiate header.
*/
neg->ua_supports_trans = 0;
neg->send_alternates = 0;
neg->may_choose = 1;
neg->use_rvsa = 0;
neg->dont_fiddle_headers = 0;
if (!negotiate)
return;
if (strcmp(negotiate, "trans") == 0) {
/* Lynx 2.7 and 2.8 send 'negotiate: trans' even though they
* do not support transparent content negotiation, so for Lynx we
* ignore the negotiate header when its contents are exactly "trans".
* If future versions of Lynx ever need to say 'negotiate: trans',
* they can send the equivalent 'negotiate: trans, trans' instead
* to avoid triggering the workaround below.
*/
const char *ua = apr_table_get(r->headers_in, "User-Agent");
if (ua && (strncmp(ua, "Lynx", 4) == 0))
return;
}
neg->may_choose = 0; /* An empty Negotiate would require 300 response */
while ((tok = ap_get_list_item(neg->pool, &negotiate)) != NULL) {
if (strcmp(tok, "trans") == 0 ||
strcmp(tok, "vlist") == 0 ||
strcmp(tok, "guess-small") == 0 ||
apr_isdigit(tok[0]) ||
strcmp(tok, "*") == 0) {
/* The user agent supports transparent negotiation */
neg->ua_supports_trans = 1;
/* Send-alternates could be configurable, but note
* that it must be 1 if we have 'vlist' in the
* negotiate header.
*/
neg->send_alternates = 1;
if (strcmp(tok, "1.0") == 0) {
/* we may use the RVSA/1.0 algorithm, configure for it */
neg->may_choose = 1;
neg->use_rvsa = 1;
neg->dont_fiddle_headers = 1;
}
else if (tok[0] == '*') {
/* we may use any variant selection algorithm, configure
* to use the Apache algorithm
*/
neg->may_choose = 1;
/* We disable header fiddles on the assumption that a
* client sending Negotiate knows how to send correct
* headers which don't need fiddling.
*/
neg->dont_fiddle_headers = 1;
}
}
}
#ifdef NEG_DEBUG
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
"dont_fiddle_headers=%d use_rvsa=%d ua_supports_trans=%d "
"send_alternates=%d, may_choose=%d",
neg->dont_fiddle_headers, neg->use_rvsa,
neg->ua_supports_trans, neg->send_alternates, neg->may_choose);
#endif
}
/* Sometimes clients will give us no Accept info at all; this routine sets
* up the standard default for that case, and also arranges for us to be
* willing to run a CGI script if we find one. (In fact, we set up to
* dramatically prefer CGI scripts in cases where that's appropriate,
* e.g., POST or when URI includes query args or extra path info).
*/
static void maybe_add_default_accepts(negotiation_state *neg,
int prefer_scripts)
{
accept_rec *new_accept;
if (!neg->accepts) {
neg->accepts = apr_array_make(neg->pool, 4, sizeof(accept_rec));
new_accept = (accept_rec *) apr_array_push(neg->accepts);
new_accept->name = "*/*";
new_accept->quality = 1.0f;
new_accept->level = 0.0f;
}
new_accept = (accept_rec *) apr_array_push(neg->accepts);
new_accept->name = CGI_MAGIC_TYPE;
if (neg->use_rvsa) {
new_accept->quality = 0;
}
else {
new_accept->quality = prefer_scripts ? 2.0f : 0.001f;
}
new_accept->level = 0.0f;
}
/*****************************************************************
*
* Parsing type-map files, in Roy's meta/http format augmented with
* #-comments.
*/
/* Reading RFC822-style header lines, ignoring #-comments and
* handling continuations.
*/
enum header_state {
header_eof, header_seen, header_sep
};
static enum header_state get_header_line(char *buffer, int len, apr_file_t *map)
{
char *buf_end = buffer + len;
char *cp;
char c;
/* Get a noncommented line */
do {
if (apr_file_gets(buffer, MAX_STRING_LEN, map) != APR_SUCCESS) {
return header_eof;
}
} while (buffer[0] == '#');
/* If blank, just return it --- this ends information on this variant */
for (cp = buffer; (*cp && apr_isspace(*cp)); ++cp) {
continue;
}
if (*cp == '\0') {
return header_sep;
}
/* If non-blank, go looking for header lines, but note that we still
* have to treat comments specially...
*/
cp += strlen(cp);
/* We need to shortcut the rest of this block following the Body:
* tag - we will not look for continutation after this line.
*/
if (!strncasecmp(buffer, "Body:", 5))
return header_seen;
while (apr_file_getc(&c, map) != APR_EOF) {
if (c == '#') {
/* Comment line */
while (apr_file_getc(&c, map) != APR_EOF && c != '\n') {
continue;
}
}
else if (apr_isspace(c)) {
/* Leading whitespace. POSSIBLE continuation line
* Also, possibly blank --- if so, we ungetc() the final newline
* so that we will pick up the blank line the next time 'round.
*/
while (c != '\n' && apr_isspace(c)) {
if(apr_file_getc(&c, map) != APR_SUCCESS)
break;
}
apr_file_ungetc(c, map);
if (c == '\n') {
return header_seen; /* Blank line */
}
/* Continuation */
while ( cp < buf_end - 2
&& (apr_file_getc(&c, map)) != APR_EOF
&& c != '\n') {
*cp++ = c;
}
*cp++ = '\n';
*cp = '\0';
}
else {
/* Line beginning with something other than whitespace */
apr_file_ungetc(c, map);
return header_seen;
}
}
return header_seen;
}
static apr_off_t get_body(char *buffer, apr_size_t *len, const char *tag,
apr_file_t *map)
{
char *endbody;
int bodylen;
int taglen;
apr_off_t pos;
taglen = strlen(tag);
*len -= taglen;
/* We are at the first character following a body:tag\n entry
* Suck in the body, then backspace to the first char after the
* closing tag entry. If we fail to read, find the tag or back
* up then we have a hosed file, so give up already
*/
if (apr_file_read(map, buffer, len) != APR_SUCCESS) {
return -1;
}
/* put a copy of the tag *after* the data read from the file
* so that strstr() will find something with no reliance on
* terminating '\0'
*/
memcpy(buffer + *len, tag, taglen);
endbody = strstr(buffer, tag);
if (endbody == buffer + *len) {
return -1;
}
bodylen = endbody - buffer;
endbody += strlen(tag);
/* Skip all the trailing cruft after the end tag to the next line */
while (*endbody) {
if (*endbody == '\n') {
++endbody;
break;
}
++endbody;
}
pos = -(apr_off_t)(*len - (endbody - buffer));
if (apr_file_seek(map, APR_CUR, &pos) != APR_SUCCESS) {
return -1;
}
/* Give the caller back the actual body's file offset and length */
*len = bodylen;
return pos - (endbody - buffer);
}
/* Stripping out RFC822 comments */
static void strip_paren_comments(char *hdr)
{
/* Hmmm... is this correct? In Roy's latest draft, (comments) can nest! */
/* Nope, it isn't correct. Fails to handle backslash escape as well. */
while (*hdr) {
if (*hdr == '"') {
hdr = strchr(hdr, '"');
if (hdr == NULL) {
return;
}
++hdr;
}
else if (*hdr == '(') {
while (*hdr && *hdr != ')') {
*hdr++ = ' ';
}
if (*hdr) {
*hdr++ = ' ';
}
}
else {
++hdr;
}
}
}
/* Getting to a header body from the header */
static char *lcase_header_name_return_body(char *header, request_rec *r)
{
char *cp = header;
for ( ; *cp && *cp != ':' ; ++cp) {
*cp = apr_tolower(*cp);
}
if (!*cp) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Syntax error in type map, no ':' in %s for header %s",
r->filename, header);
return NULL;
}
do {
++cp;
} while (*cp && apr_isspace(*cp));
if (!*cp) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Syntax error in type map --- no header body: %s for %s",
r->filename, header);
return NULL;
}
return cp;
}
static int read_type_map(apr_file_t **map, negotiation_state *neg,
request_rec *rr)
{
request_rec *r = neg->r;
apr_file_t *map_ = NULL;
apr_status_t status;
char buffer[MAX_STRING_LEN];
enum header_state hstate;
struct var_rec mime_info;
int has_content;
if (!map)
map = &map_;
/* We are not using multiviews */
neg->count_multiviews_variants = 0;
if ((status = apr_file_open(map, rr->filename, APR_READ | APR_BUFFERED,
APR_OS_DEFAULT, neg->pool)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r,
"cannot access type map file: %s", rr->filename);
return HTTP_FORBIDDEN;
}
clean_var_rec(&mime_info);
has_content = 0;
do {
hstate = get_header_line(buffer, MAX_STRING_LEN, *map);
if (hstate == header_seen) {
char *body1 = lcase_header_name_return_body(buffer, neg->r);
const char *body;
if (body1 == NULL) {
return HTTP_INTERNAL_SERVER_ERROR;
}
strip_paren_comments(body1);
body = body1;
if (!strncmp(buffer, "uri:", 4)) {
mime_info.file_name = ap_get_token(neg->pool, &body, 0);
}
else if (!strncmp(buffer, "content-type:", 13)) {
struct accept_rec accept_info;
get_entry(neg->pool, &accept_info, body);
set_mime_fields(&mime_info, &accept_info);
has_content = 1;
}
else if (!strncmp(buffer, "content-length:", 15)) {
mime_info.bytes = apr_atoi64((char *)body);
has_content = 1;
}
else if (!strncmp(buffer, "content-language:", 17)) {
mime_info.content_languages = do_languages_line(neg->pool,
&body);
has_content = 1;
}
else if (!strncmp(buffer, "content-encoding:", 17)) {
mime_info.content_encoding = ap_get_token(neg->pool, &body, 0);
has_content = 1;
}
else if (!strncmp(buffer, "description:", 12)) {
char *desc = apr_pstrdup(neg->pool, body);
char *cp;
for (cp = desc; *cp; ++cp) {
if (*cp=='\n') *cp=' ';
}
if (cp>desc) *(cp-1)=0;
mime_info.description = desc;
}
else if (!strncmp(buffer, "body:", 5)) {
char *tag = apr_pstrdup(neg->pool, body);
char *eol = strchr(tag, '\0');
apr_size_t len = MAX_STRING_LEN;
while (--eol >= tag && apr_isspace(*eol))
*eol = '\0';
if ((mime_info.body = get_body(buffer, &len, tag, *map)) < 0) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Syntax error in type map, no end tag '%s'"
"found in %s for Body: content.",
tag, r->filename);
break;
}
mime_info.bytes = len;
mime_info.file_name = apr_filename_of_pathname(rr->filename);
}
}
else {
if (*mime_info.file_name && has_content) {
void *new_var = apr_array_push(neg->avail_vars);
memcpy(new_var, (void *) &mime_info, sizeof(var_rec));
}
clean_var_rec(&mime_info);
has_content = 0;
}
} while (hstate != header_eof);
if (map_)
apr_file_close(map_);
set_vlist_validator(r, rr);
return OK;
}
/* Sort function used by read_types_multi. */
static int variantsortf(var_rec *a, var_rec *b) {
/* First key is the source quality, sort in descending order. */
/* XXX: note that we currently implement no method of setting the
* source quality for multiviews variants, so we are always comparing
* 1.0 to 1.0 for now
*/
if (a->source_quality < b->source_quality)
return 1;
if (a->source_quality > b->source_quality)
return -1;
/* Second key is the variant name */
return strcmp(a->file_name, b->file_name);
}
/*****************************************************************
*
* Same as read_type_map, except we use a filtered directory listing
* as the map...
*/
static int read_types_multi(negotiation_state *neg)
{
request_rec *r = neg->r;
char *filp;
int prefix_len;
apr_dir_t *dirp;
apr_finfo_t dirent;
apr_status_t status;
struct var_rec mime_info;
struct accept_rec accept_info;
void *new_var;
int anymatch = 0;
clean_var_rec(&mime_info);
if (r->proxyreq || !r->filename
|| !ap_os_is_path_absolute(neg->pool, r->filename)) {
return DECLINED;
}
/* Only absolute paths here */
if (!(filp = strrchr(r->filename, '/'))) {
return DECLINED;
}
++filp;
prefix_len = strlen(filp);
if ((status = apr_dir_open(&dirp, neg->dir_name,
neg->pool)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r,
"cannot read directory for multi: %s", neg->dir_name);
return HTTP_FORBIDDEN;
}
while (apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp) == APR_SUCCESS) {
apr_array_header_t *exception_list;
request_rec *sub_req;
/* Do we have a match? */
#ifdef CASE_BLIND_FILESYSTEM
if (strncasecmp(dirent.name, filp, prefix_len)) {
#else
if (strncmp(dirent.name, filp, prefix_len)) {
#endif
continue;
}
if (dirent.name[prefix_len] != '.') {
continue;
}
/* Don't negotiate directories and other unusual files
* Really shouldn't see anything but DIR/LNK/REG here,
* and we aught to discover if the LNK was interesting.
*
* Of course, this only helps platforms that capture the
* the filetype in apr_dir_read(), which most can once
* they are optimized with some magic [it's known to the
* dirent, not associated to the inode, on most FS's.]
*/
if ((dirent.valid & APR_FINFO_TYPE) && (dirent.filetype == APR_DIR))
continue;
/* Ok, something's here. Maybe nothing useful. Remember that
* we tried, if we completely fail, so we can reject the request!
*/
anymatch = 1;
/* See if it's something which we have access to, and which
* has a known type and encoding (as opposed to something
* which we'll be slapping default_type on later).
*/
sub_req = ap_sub_req_lookup_dirent(&dirent, r, AP_SUBREQ_MERGE_ARGS,
NULL);
/* Double check, we still don't multi-resolve non-ordinary files
*/
if (sub_req->finfo.filetype != APR_REG)
continue;
/* If it has a handler, we'll pretend it's a CGI script,
* since that's a good indication of the sort of thing it
* might be doing.
*/
if (sub_req->handler && !sub_req->content_type) {
ap_set_content_type(sub_req, CGI_MAGIC_TYPE);
}
/*
* mod_mime will _always_ provide us the base name in the
* ap-mime-exception-list, if it processed anything. If
* this list is empty, give up immediately, there was
* nothing interesting. For example, looking at the files
* readme.txt and readme.foo, we will throw away .foo if
* it's an insignificant file (e.g. did not identify a
* language, charset, encoding, content type or handler,)
*/
exception_list =
(apr_array_header_t *)apr_table_get(sub_req->notes,
"ap-mime-exceptions-list");
if (!exception_list) {
ap_destroy_sub_req(sub_req);
continue;
}
/* Each unregonized bit better match our base name, in sequence.
* A test of index.html.foo will match index.foo or index.html.foo,
* but it will never transpose the segments and allow index.foo.html
* because that would introduce too much CPU consumption. Better that
* we don't attempt a many-to-many match here.
*/
{
int nexcept = exception_list->nelts;
char **cur_except = (char**)exception_list->elts;
char *segstart = filp, *segend, saveend;
while (*segstart && nexcept) {
if (!(segend = strchr(segstart, '.')))
segend = strchr(segstart, '\0');
saveend = *segend;
*segend = '\0';
#ifdef CASE_BLIND_FILESYSTEM
if (strcasecmp(segstart, *cur_except) == 0) {
#else
if (strcmp(segstart, *cur_except) == 0) {
#endif
--nexcept;
++cur_except;
}
if (!saveend)
break;
*segend = saveend;
segstart = segend + 1;
}
if (nexcept) {
/* Something you don't know is, something you don't know...
*/
ap_destroy_sub_req(sub_req);
continue;
}
}
/*
* ###: be warned, the _default_ content type is already
* picked up here! If we failed the subrequest, or don't
* know what we are serving, then continue.
*/
if (sub_req->status != HTTP_OK || (!sub_req->content_type)) {
ap_destroy_sub_req(sub_req);
continue;
}
/* If it's a map file, we use that instead of the map
* we're building...
*/
if (((sub_req->content_type) &&
!strcmp(sub_req->content_type, MAP_FILE_MAGIC_TYPE)) ||
((sub_req->handler) &&
!strcmp(sub_req->handler, "type-map"))) {
apr_dir_close(dirp);
neg->avail_vars->nelts = 0;
if (sub_req->status != HTTP_OK) {
return sub_req->status;
}
return read_type_map(NULL, neg, sub_req);
}
/* Have reasonable variant --- gather notes. */
mime_info.sub_req = sub_req;
mime_info.file_name = apr_pstrdup(neg->pool, dirent.name);
if (sub_req->content_encoding) {
mime_info.content_encoding = sub_req->content_encoding;
}
if (sub_req->content_languages) {
mime_info.content_languages = sub_req->content_languages;
}
get_entry(neg->pool, &accept_info, sub_req->content_type);
set_mime_fields(&mime_info, &accept_info);
new_var = apr_array_push(neg->avail_vars);
memcpy(new_var, (void *) &mime_info, sizeof(var_rec));
neg->count_multiviews_variants++;
clean_var_rec(&mime_info);
}
apr_dir_close(dirp);
/* We found some file names that matched. None could be served.
* Rather than fall out to autoindex or some other mapper, this
* request must die.
*/
if (anymatch && !neg->avail_vars->nelts) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Negotiation: discovered file(s) matching request: %s"
" (None could be negotiated).",
r->filename);
return HTTP_NOT_FOUND;
}
set_vlist_validator(r, r);
/* Sort the variants into a canonical order. The negotiation
* result sometimes depends on the order of the variants. By
* sorting the variants into a canonical order, rather than using
* the order in which readdir() happens to return them, we ensure
* that the negotiation result will be consistent over filesystem
* backup/restores and over all mirror sites.
*/
qsort((void *) neg->avail_vars->elts, neg->avail_vars->nelts,
sizeof(var_rec), (int (*)(const void *, const void *)) variantsortf);
return OK;
}
/*****************************************************************
* And now for the code you've been waiting for... actually
* finding a match to the client's requirements.
*/
/* Matching MIME types ... the star/star and foo/star commenting conventions
* are implemented here. (You know what I mean by star/star, but just
* try mentioning those three characters in a C comment). Using strcmp()
* is legit, because everything has already been smashed to lowercase.
*
* Note also that if we get an exact match on the media type, we update
* level_matched for use in level_cmp below...
*
* We also give a value for mime_stars, which is used later. It should
* be 1 for star/star, 2 for type/star and 3 for type/subtype.
*/
static int mime_match(accept_rec *accept_r, var_rec *avail)
{
const char *accept_type = accept_r->name;
const char *avail_type = avail->mime_type;
int len = strlen(accept_type);
if (accept_type[0] == '*') { /* Anything matches star/star */
if (avail->mime_stars < 1) {
avail->mime_stars = 1;
}
return 1;
}
else if ((accept_type[len - 1] == '*') &&
!strncmp(accept_type, avail_type, len - 2)) {
if (avail->mime_stars < 2) {
avail->mime_stars = 2;
}
return 1;
}
else if (!strcmp(accept_type, avail_type)
|| (!strcmp(accept_type, "text/html")
&& (!strcmp(avail_type, INCLUDES_MAGIC_TYPE)
|| !strcmp(avail_type, INCLUDES_MAGIC_TYPE3)))) {
if (accept_r->level >= avail->level) {
avail->level_matched = avail->level;
avail->mime_stars = 3;
return 1;
}
}
return OK;
}
/* This code implements a piece of the tie-breaking algorithm between
* variants of equal quality. This piece is the treatment of variants
* of the same base media type, but different levels. What we want to
* return is the variant at the highest level that the client explicitly
* claimed to accept.
*
* If all the variants available are at a higher level than that, or if
* the client didn't say anything specific about this media type at all
* and these variants just got in on a wildcard, we prefer the lowest
* level, on grounds that that's the one that the client is least likely
* to choke on.
*
* (This is all motivated by treatment of levels in HTML --- we only
* want to give level 3 to browsers that explicitly ask for it; browsers
* that don't, including HTTP/0.9 browsers that only get the implicit
* "Accept: * / *" [space added to avoid confusing cpp --- no, that
* syntax doesn't really work] should get HTML2 if available).
*
* (Note that this code only comes into play when we are choosing among
* variants of equal quality, where the draft standard gives us a fair
* bit of leeway about what to do. It ain't specified by the standard;
* rather, it is a choice made by this server about what to do in cases
* where the standard does not specify a unique course of action).
*/
static int level_cmp(var_rec *var1, var_rec *var2)
{
/* Levels are only comparable between matching media types */
if (var1->is_pseudo_html && !var2->is_pseudo_html) {
return 0;
}
if (!var1->is_pseudo_html && strcmp(var1->mime_type, var2->mime_type)) {
return 0;
}
/* The result of the above if statements is that, if we get to
* here, both variants have the same mime_type or both are
* pseudo-html.
*/
/* Take highest level that matched, if either did match. */
if (var1->level_matched > var2->level_matched) {
return 1;
}
if (var1->level_matched < var2->level_matched) {
return -1;
}
/* Neither matched. Take lowest level, if there's a difference. */
if (var1->level < var2->level) {
return 1;
}
if (var1->level > var2->level) {
return -1;
}
/* Tied */
return 0;
}
/* Finding languages. The main entry point is set_language_quality()
* which is called for each variant. It sets two elements in the
* variant record:
* language_quality - the 'q' value of the 'best' matching language
* from Accept-Language: header (HTTP/1.1)
* lang_index - Non-negotiated language priority, using
* position of language on the Accept-Language:
* header, if present, else LanguagePriority
* directive order.
*
* When we do the variant checking for best variant, we use language
* quality first, and if a tie, language_index next (this only applies
* when _not_ using the RVSA/1.0 algorithm). If using the RVSA/1.0
* algorithm, lang_index is never used.
*
* set_language_quality() calls find_lang_index() and find_default_index()
* to set lang_index.
*/
static int find_lang_index(apr_array_header_t *accept_langs, char *lang)
{
const char **alang;
int i;
if (!lang || !accept_langs) {
return -1;
}
alang = (const char **) accept_langs->elts;
for (i = 0; i < accept_langs->nelts; ++i) {
if (!strncmp(lang, *alang, strlen(*alang))) {
return i;
}
alang += (accept_langs->elt_size / sizeof(char*));
}
return -1;
}
/* set_default_lang_quality() sets the quality we apply to variants
* which have no language assigned to them. If none of the variants
* have a language, we are not negotiating on language, so all are
* acceptable, and we set the default q value to 1.0. However if
* some of the variants have languages, we set this default to 0.0001.
* The value of this default will be applied to all variants with
* no explicit language -- which will have the effect of making them
* acceptable, but only if no variants with an explicit language
* are acceptable. The default q value set here is assigned to variants
* with no language type in set_language_quality().
*
* Note that if using the RVSA/1.0 algorithm, we don't use this
* fiddle.
*/
static void set_default_lang_quality(negotiation_state *neg)
{
var_rec *avail_recs = (var_rec *) neg->avail_vars->elts;
int j;
if (!neg->dont_fiddle_headers) {
for (j = 0; j < neg->avail_vars->nelts; ++j) {
var_rec *variant = &avail_recs[j];
if (variant->content_languages &&
variant->content_languages->nelts) {
neg->default_lang_quality = 0.0001f;
return;
}
}
}
neg->default_lang_quality = 1.0f;
}
/* Set the language_quality value in the variant record. Also
* assigns lang_index for ForceLanguagePriority.
*
* To find the language_quality value, we look for the 'q' value
* of the 'best' matching language on the Accept-Language
* header. The 'best' match is the language on Accept-Language
* header which matches the language of this variant either fully,
* or as far as the prefix marker (-). If two or more languages
* match, use the longest string from the Accept-Language header
* (see HTTP/1.1 [14.4])
*
* When a variant has multiple languages, we find the 'best'
* match for each variant language tag as above, then select the
* one with the highest q value. Because both the accept-header
* and variant can have multiple languages, we now have a hairy
* loop-within-a-loop here.
*
* If the variant has no language and we have no Accept-Language
* items, leave the quality at 1.0 and return.
*
* If the variant has no language, we use the default as set by
* set_default_lang_quality() (1.0 if we are not negotiating on
* language, 0.001 if we are).
*
* Following the setting of the language quality, we drop through to
* set the old 'lang_index'. This is set based on either the order
* of the languages on the Accept-Language header, or the
* order on the LanguagePriority directive. This is only used
* in the negotiation if the language qualities tie.
*/
static void set_language_quality(negotiation_state *neg, var_rec *variant)
{
int forcepriority = neg->conf->forcelangpriority;
if (forcepriority == FLP_UNDEF) {
forcepriority = FLP_DEFAULT;
}
if (!variant->content_languages || !variant->content_languages->nelts) {
/* This variant has no content-language, so use the default
* quality factor for variants with no content-language
* (previously set by set_default_lang_quality()).
* Leave the factor alone (it remains at 1.0) when we may not fiddle
* with the headers.
*/
if (!neg->dont_fiddle_headers) {
variant->lang_quality = neg->default_lang_quality;
}
if (!neg->accept_langs) {
return; /* no accept-language header */
}
return;
}
else {
/* Variant has one (or more) languages. Look for the best
* match. We do this by going through each language on the
* variant description looking for a match on the
* Accept-Language header. The best match is the longest
* matching language on the header. The final result is the
* best q value from all the languages on the variant
* description.
*/
if (!neg->accept_langs) {
/* no accept-language header makes the variant indefinite */
variant->definite = 0;
}
else { /* There is an accept-language with 0 or more items */
accept_rec *accs = (accept_rec *) neg->accept_langs->elts;
accept_rec *best = NULL, *star = NULL;
accept_rec *bestthistag;
char *lang, *p;
float fiddle_q = 0.0f;
int any_match_on_star = 0;
int i, j;
apr_size_t alen, longest_lang_range_len;
for (j = 0; j < variant->content_languages->nelts; ++j) {
p = NULL;
bestthistag = NULL;
longest_lang_range_len = 0;
alen = 0;
/* lang is the variant's language-tag, which is the one
* we are allowed to use the prefix of in HTTP/1.1
*/
lang = ((char **) (variant->content_languages->elts))[j];
/* now find the best (i.e. longest) matching
* Accept-Language header language. We put the best match
* for this tag in bestthistag. We cannot update the
* overall best (based on q value) because the best match
* for this tag is the longest language item on the accept
* header, not necessarily the highest q.
*/
for (i = 0; i < neg->accept_langs->nelts; ++i) {
if (!strcmp(accs[i].name, "*")) {
if (!star) {
star = &accs[i];
}
continue;
}
/* Find language. We match if either the variant
* language tag exactly matches the language range
* from the accept header, or a prefix of the variant
* language tag up to a '-' character matches the
* whole of the language range in the Accept-Language
* header. Note that HTTP/1.x allows any number of
* '-' characters in a tag or range, currently only
* tags with zero or one '-' characters are defined
* for general use (see rfc1766).
*
* We only use language range in the Accept-Language
* header the best match for the variant language tag
* if it is longer than the previous best match.
*/
alen = strlen(accs[i].name);
if ((strlen(lang) >= alen) &&
!strncmp(lang, accs[i].name, alen) &&
((lang[alen] == 0) || (lang[alen] == '-')) ) {
if (alen > longest_lang_range_len) {
longest_lang_range_len = alen;
bestthistag = &accs[i];
}
}
if (!bestthistag && !neg->dont_fiddle_headers) {
/* The next bit is a fiddle. Some browsers might
* be configured to send more specific language
* ranges than desirable. For example, an
* Accept-Language of en-US should never match
* variants with languages en or en-GB. But US
* English speakers might pick en-US as their
* language choice. So this fiddle checks if the
* language range has a prefix, and if so, it
* matches variants which match that prefix with a
* priority of 0.001. So a request for en-US would
* match variants of types en and en-GB, but at
* much lower priority than matches of en-US
* directly, or of any other language listed on
* the Accept-Language header. Note that this
* fiddle does not handle multi-level prefixes.
*/
if ((p = strchr(accs[i].name, '-'))) {
int plen = p - accs[i].name;
if (!strncmp(lang, accs[i].name, plen)) {
fiddle_q = 0.001f;
}
}
}
}
/* Finished looking at Accept-Language headers, the best
* (longest) match is in bestthistag, or NULL if no match
*/
if (!best ||
(bestthistag && bestthistag->quality > best->quality)) {
best = bestthistag;
}
/* See if the tag matches on a * in the Accept-Language
* header. If so, record this fact for later use
*/
if (!bestthistag && star) {
any_match_on_star = 1;
}
}
/* If one of the language tags of the variant matched on *, we
* need to see if its q is better than that of any non-* match
* on any other tag of the variant. If so the * match takes
* precedence and the overall match is not definite.
*/
if ( any_match_on_star &&
((best && star->quality > best->quality) ||
(!best)) ) {
best = star;
variant->definite = 0;
}
variant->lang_quality = best ? best->quality : fiddle_q;
}
}
/* Handle the ForceDefaultLanguage overrides, based on the best match
* to LanguagePriority order. The best match is the lowest index of
* any LanguagePriority match.
*/
if (((forcepriority & FLP_PREFER)
&& (variant->lang_index < 0))
|| ((forcepriority & FLP_FALLBACK)
&& !variant->lang_quality))
{
int bestidx = -1;
int j;
for (j = 0; j < variant->content_languages->nelts; ++j)
{
/* lang is the variant's language-tag, which is the one
* we are allowed to use the prefix of in HTTP/1.1
*/
char *lang = ((char **) (variant->content_languages->elts))[j];
int idx = -1;
/* If we wish to fallback or
* we use our own LanguagePriority index.
*/
idx = find_lang_index(neg->conf->language_priority, lang);
if ((idx >= 0) && ((bestidx == -1) || (idx < bestidx))) {
bestidx = idx;
}
}
if (bestidx >= 0) {
if (variant->lang_quality) {
if (forcepriority & FLP_PREFER) {
variant->lang_index = bestidx;
}
}
else {
if (forcepriority & FLP_FALLBACK) {
variant->lang_index = bestidx;
variant->lang_quality = .0001f;
variant->definite = 0;
}
}
}
}
return;
}
/* Determining the content length --- if the map didn't tell us,
* we have to do a stat() and remember for next time.
*/
static apr_off_t find_content_length(negotiation_state *neg, var_rec *variant)
{
apr_finfo_t statb;
if (variant->bytes < 0) {
if ( variant->sub_req
&& (variant->sub_req->finfo.valid & APR_FINFO_SIZE)) {
variant->bytes = variant->sub_req->finfo.size;
}
else {
char *fullname = ap_make_full_path(neg->pool, neg->dir_name,
variant->file_name);
if (apr_stat(&statb, fullname,
APR_FINFO_SIZE, neg->pool) == APR_SUCCESS) {
variant->bytes = statb.size;
}
}
}
return variant->bytes;
}
/* For a given variant, find the best matching Accept: header
* and assign the Accept: header's quality value to the
* mime_type_quality field of the variant, for later use in
* determining the best matching variant.
*/
static void set_accept_quality(negotiation_state *neg, var_rec *variant)
{
int i;
accept_rec *accept_recs;
float q = 0.0f;
int q_definite = 1;
/* if no Accept: header, leave quality alone (will
* remain at the default value of 1)
*
* XXX: This if is currently never true because of the effect of
* maybe_add_default_accepts().
*/
if (!neg->accepts) {
if (variant->mime_type && *variant->mime_type)
variant->definite = 0;
return;
}
accept_recs = (accept_rec *) neg->accepts->elts;
/*
* Go through each of the ranges on the Accept: header,
* looking for the 'best' match with this variant's
* content-type. We use the best match's quality
* value (from the Accept: header) for this variant's
* mime_type_quality field.
*
* The best match is determined like this:
* type/type is better than type/ * is better than * / *
* if match is type/type, use the level mime param if available
*/
for (i = 0; i < neg->accepts->nelts; ++i) {
accept_rec *type = &accept_recs[i];
int prev_mime_stars;
prev_mime_stars = variant->mime_stars;
if (!mime_match(type, variant)) {
continue; /* didn't match the content type at all */
}
else {
/* did match - see if there were less or more stars than
* in previous match
*/
if (prev_mime_stars == variant->mime_stars) {
continue; /* more stars => not as good a match */
}
}
/* If we are allowed to mess with the q-values
* and have no explicit q= parameters in the accept header,
* make wildcards very low, so we have a low chance
* of ending up with them if there's something better.
*/
if (!neg->dont_fiddle_headers && !neg->accept_q &&
variant->mime_stars == 1) {
q = 0.01f;
}
else if (!neg->dont_fiddle_headers && !neg->accept_q &&
variant->mime_stars == 2) {
q = 0.02f;
}
else {
q = type->quality;
}
q_definite = (variant->mime_stars == 3);
}
variant->mime_type_quality = q;
variant->definite = variant->definite && q_definite;
}
/* For a given variant, find the 'q' value of the charset given
* on the Accept-Charset line. If no charsets are listed,
* assume value of '1'.
*/
static void set_charset_quality(negotiation_state *neg, var_rec *variant)
{
int i;
accept_rec *accept_recs;
const char *charset = variant->content_charset;
accept_rec *star = NULL;
/* if no Accept-Charset: header, leave quality alone (will
* remain at the default value of 1)
*/
if (!neg->accept_charsets) {
if (charset && *charset)
variant->definite = 0;
return;
}
accept_recs = (accept_rec *) neg->accept_charsets->elts;
if (charset == NULL || !*charset) {
/* Charset of variant not known */
/* if not a text / * type, leave quality alone */
if (!(!strncmp(variant->mime_type, "text/", 5)
|| !strcmp(variant->mime_type, INCLUDES_MAGIC_TYPE)
|| !strcmp(variant->mime_type, INCLUDES_MAGIC_TYPE3)
))
return;
/* Don't go guessing if we are in strict header mode,
* e.g. when running the rvsa, as any guess won't be reflected
* in the variant list or content-location headers.
*/
if (neg->dont_fiddle_headers)
return;
charset = "iso-8859-1"; /* The default charset for HTTP text types */
}
/*
* Go through each of the items on the Accept-Charset header,
* looking for a match with this variant's charset. If none
* match, charset is unacceptable, so set quality to 0.
*/
for (i = 0; i < neg->accept_charsets->nelts; ++i) {
accept_rec *type = &accept_recs[i];
if (!strcmp(type->name, charset)) {
variant->charset_quality = type->quality;
return;
}
else if (strcmp(type->name, "*") == 0) {
star = type;
}
}
/* No explicit match */
if (star) {
variant->charset_quality = star->quality;
variant->definite = 0;
return;
}
/* If this variant is in charset iso-8859-1, the default is 1.0 */
if (strcmp(charset, "iso-8859-1") == 0) {
variant->charset_quality = 1.0f;
}
else {
variant->charset_quality = 0.0f;
}
}
/* is_identity_encoding is included for back-compat, but does anyone
* use 7bit, 8bin or binary in their var files??
*/
static int is_identity_encoding(const char *enc)
{
return (!enc || !enc[0] || !strcmp(enc, "7bit") || !strcmp(enc, "8bit")
|| !strcmp(enc, "binary"));
}
/*
* set_encoding_quality determines whether the encoding for a particular
* variant is acceptable for the user-agent.
*
* The rules for encoding are that if the user-agent does not supply
* any Accept-Encoding header, then all encodings are allowed but a
* variant with no encoding should be preferred.
* If there is an empty Accept-Encoding header, then no encodings are
* acceptable. If there is a non-empty Accept-Encoding header, then
* any of the listed encodings are acceptable, as well as no encoding
* unless the "identity" encoding is specifically excluded.
*/
static void set_encoding_quality(negotiation_state *neg, var_rec *variant)
{
accept_rec *accept_recs;
const char *enc = variant->content_encoding;
accept_rec *star = NULL;
float value_if_not_found = 0.0f;
int i;
if (!neg->accept_encodings) {
/* We had no Accept-Encoding header, assume that all
* encodings are acceptable with a low quality,
* but we prefer no encoding if available.
*/
if (!enc || is_identity_encoding(enc))
variant->encoding_quality = 1.0f;
else
variant->encoding_quality = 0.5f;
return;
}
if (!enc || is_identity_encoding(enc)) {
enc = "identity";
value_if_not_found = 0.0001f;
}
accept_recs = (accept_rec *) neg->accept_encodings->elts;
/* Go through each of the encodings on the Accept-Encoding: header,
* looking for a match with our encoding. x- prefixes are ignored.
*/
if (enc[0] == 'x' && enc[1] == '-') {
enc += 2;
}
for (i = 0; i < neg->accept_encodings->nelts; ++i) {
char *name = accept_recs[i].name;
if (name[0] == 'x' && name[1] == '-') {
name += 2;
}
if (!strcmp(name, enc)) {
variant->encoding_quality = accept_recs[i].quality;
return;
}
if (strcmp(name, "*") == 0) {
star = &accept_recs[i];
}
}
/* No explicit match */
if (star) {
variant->encoding_quality = star->quality;
return;
}
/* Encoding not found on Accept-Encoding: header, so it is
* _not_ acceptable unless it is the identity (no encoding)
*/
variant->encoding_quality = value_if_not_found;
}
/*************************************************************
* Possible results of the variant selection algorithm
*/
enum algorithm_results {
alg_choice = 1, /* choose variant */
alg_list /* list variants */
};
/* Below is the 'best_match' function. It returns an int, which has
* one of the two values alg_choice or alg_list, which give the result
* of the variant selection algorithm. alg_list means that no best
* variant was found by the algorithm, alg_choice means that a best
* variant was found and should be returned. The list/choice
* terminology comes from TCN (rfc2295), but is used in a more generic
* way here. The best variant is returned in *pbest. best_match has
* two possible algorithms for determining the best variant: the
* RVSA/1.0 algorithm (from RFC2296), and the standard Apache
* algorithm. These are split out into separate functions
* (is_variant_better_rvsa() and is_variant_better()). Selection of
* one is through the neg->use_rvsa flag.
*
* The call to best_match also creates full information, including
* language, charset, etc quality for _every_ variant. This is needed
* for generating a correct Vary header, and can be used for the
* Alternates header, the human-readable list responses and 406 errors.
*/
/* Firstly, the RVSA/1.0 (HTTP Remote Variant Selection Algorithm
* v1.0) from rfc2296. This is the algorithm that goes together with
* transparent content negotiation (TCN).
*/
static int is_variant_better_rvsa(negotiation_state *neg, var_rec *variant,
var_rec *best, float *p_bestq)
{
float bestq = *p_bestq, q;
/* TCN does not cover negotiation on content-encoding. For now,
* we ignore the encoding unless it was explicitly excluded.
*/
if (variant->encoding_quality == 0.0f)
return 0;
q = variant->mime_type_quality *
variant->source_quality *
variant->charset_quality *
variant->lang_quality;
/* RFC 2296 calls for the result to be rounded to 5 decimal places,
* but we don't do that because it serves no useful purpose other
* than to ensure that a remote algorithm operates on the same
* precision as ours. That is silly, since what we obviously want
* is for the algorithm to operate on the best available precision
* regardless of who runs it. Since the above calculation may
* result in significant variance at 1e-12, rounding would be bogus.
*/
#ifdef NEG_DEBUG
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
"Variant: file=%s type=%s lang=%s sourceq=%1.3f "
"mimeq=%1.3f langq=%1.3f charq=%1.3f encq=%1.3f "
"q=%1.5f definite=%d",
(variant->file_name ? variant->file_name : ""),
(variant->mime_type ? variant->mime_type : ""),
(variant->content_languages
? apr_array_pstrcat(neg->pool, variant->content_languages, ',')
: ""),
variant->source_quality,
variant->mime_type_quality,
variant->lang_quality,
variant->charset_quality,
variant->encoding_quality,
q,
variant->definite);
#endif
if (q <= 0.0f) {
return 0;
}
if (q > bestq) {
*p_bestq = q;
return 1;
}
if (q == bestq) {
/* If the best variant's encoding is of lesser quality than
* this variant, then we prefer this variant
*/
if (variant->encoding_quality > best->encoding_quality) {
*p_bestq = q;
return 1;
}
}
return 0;
}
/* Negotiation algorithm as used by previous versions of Apache
* (just about).
*/
static int is_variant_better(negotiation_state *neg, var_rec *variant,
var_rec *best, float *p_bestq)
{
float bestq = *p_bestq, q;
int levcmp;
/* For non-transparent negotiation, server can choose how
* to handle the negotiation. We'll use the following in
* order: content-type, language, content-type level, charset,
* content encoding, content length.
*
* For each check, we have three possible outcomes:
* This variant is worse than current best: return 0
* This variant is better than the current best:
* assign this variant's q to *p_bestq, and return 1
* This variant is just as desirable as the current best:
* drop through to the next test.
*
* This code is written in this long-winded way to allow future
* customisation, either by the addition of additional
* checks, or to allow the order of the checks to be determined
* by configuration options (e.g. we might prefer to check
* language quality _before_ content type).
*/
/* First though, eliminate this variant if it is not
* acceptable by type, charset, encoding or language.
*/
#ifdef NEG_DEBUG
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
"Variant: file=%s type=%s lang=%s sourceq=%1.3f "
"mimeq=%1.3f langq=%1.3f langidx=%d charq=%1.3f encq=%1.3f ",
(variant->file_name ? variant->file_name : ""),
(variant->mime_type ? variant->mime_type : ""),
(variant->content_languages
? apr_array_pstrcat(neg->pool, variant->content_languages, ',')
: ""),
variant->source_quality,
variant->mime_type_quality,
variant->lang_quality,
variant->lang_index,
variant->charset_quality,
variant->encoding_quality);
#endif
if (variant->encoding_quality == 0.0f ||
variant->lang_quality == 0.0f ||
variant->source_quality == 0.0f ||
variant->charset_quality == 0.0f ||
variant->mime_type_quality == 0.0f) {
return 0; /* don't consider unacceptables */
}
q = variant->mime_type_quality * variant->source_quality;
if (q == 0.0 || q < bestq) {
return 0;
}
if (q > bestq || !best) {
*p_bestq = q;
return 1;
}
/* language */
if (variant->lang_quality < best->lang_quality) {
return 0;
}
if (variant->lang_quality > best->lang_quality) {
*p_bestq = q;
return 1;
}
/* if language qualities were equal, try the LanguagePriority stuff */
if (best->lang_index != -1 &&
(variant->lang_index == -1 || variant->lang_index > best->lang_index)) {
return 0;
}
if (variant->lang_index != -1 &&
(best->lang_index == -1 || variant->lang_index < best->lang_index)) {
*p_bestq = q;
return 1;
}
/* content-type level (sometimes used with text/html, though we
* support it on other types too)
*/
levcmp = level_cmp(variant, best);
if (levcmp == -1) {
return 0;
}
if (levcmp == 1) {
*p_bestq = q;
return 1;
}
/* charset */
if (variant->charset_quality < best->charset_quality) {
return 0;
}
/* If the best variant's charset is ISO-8859-1 and this variant has
* the same charset quality, then we prefer this variant
*/
if (variant->charset_quality > best->charset_quality ||
((variant->content_charset != NULL &&
*variant->content_charset != '\0' &&
strcmp(variant->content_charset, "iso-8859-1") != 0) &&
(best->content_charset == NULL ||
*best->content_charset == '\0' ||
strcmp(best->content_charset, "iso-8859-1") == 0))) {
*p_bestq = q;
return 1;
}
/* Prefer the highest value for encoding_quality.
*/
if (variant->encoding_quality < best->encoding_quality) {
return 0;
}
if (variant->encoding_quality > best->encoding_quality) {
*p_bestq = q;
return 1;
}
/* content length if all else equal */
if (find_content_length(neg, variant) >= find_content_length(neg, best)) {
return 0;
}
/* ok, to get here means every thing turned out equal, except
* we have a shorter content length, so use this variant
*/
*p_bestq = q;
return 1;
}
/* figure out, whether a variant is in a specific language
* it returns also false, if the variant has no language.
*/
static int variant_has_language(var_rec *variant, const char *lang)
{
int j, max;
/* fast exit */
if ( !lang
|| !variant->content_languages
|| !(max = variant->content_languages->nelts)) {
return 0;
}
for (j = 0; j < max; ++j) {
if (!strcmp(lang,
((char **) (variant->content_languages->elts))[j])) {
return 1;
}
}
return 0;
}
static int best_match(negotiation_state *neg, var_rec **pbest)
{
int j;
var_rec *best;
float bestq = 0.0f;
enum algorithm_results algorithm_result;
var_rec *avail_recs = (var_rec *) neg->avail_vars->elts;
const char *preferred_language = apr_table_get(neg->r->subprocess_env,
"prefer-language");
set_default_lang_quality(neg);
/*
* Find the 'best' variant
* We run the loop possibly twice: if "prefer-language"
* environment variable is set but we did not find an appropriate
* best variant. In that case forget the preferred language and
* negotiate over all variants.
*/
do {
best = NULL;
for (j = 0; j < neg->avail_vars->nelts; ++j) {
var_rec *variant = &avail_recs[j];
/* if a language is preferred, but the current variant
* is not in that language, then drop it for now
*/
if ( preferred_language
&& !variant_has_language(variant, preferred_language)) {
continue;
}
/* Find all the relevant 'quality' values from the
* Accept... headers, and store in the variant. This also
* prepares for sending an Alternates header etc so we need to
* do it even if we do not actually plan to find a best
* variant.
*/
set_accept_quality(neg, variant);
/* accept the preferred language, even when it's not listed within
* the Accept-Language header
*/
if (preferred_language) {
variant->lang_quality = 1.0f;
variant->definite = 1;
}
else {
set_language_quality(neg, variant);
}
set_encoding_quality(neg, variant);
set_charset_quality(neg, variant);
/* Only do variant selection if we may actually choose a
* variant for the client
*/
if (neg->may_choose) {
/* Now find out if this variant is better than the current
* best, either using the RVSA/1.0 algorithm, or Apache's
* internal server-driven algorithm. Presumably other
* server-driven algorithms are possible, and could be
* implemented here.
*/
if (neg->use_rvsa) {
if (is_variant_better_rvsa(neg, variant, best, &bestq)) {
best = variant;
}
}
else {
if (is_variant_better(neg, variant, best, &bestq)) {
best = variant;
}
}
}
}
/* We now either have a best variant, or no best variant */
if (neg->use_rvsa) {
/* calculate result for RVSA/1.0 algorithm:
* only a choice response if the best variant has q>0
* and is definite
*/
algorithm_result = (best && best->definite) && (bestq > 0) ?
alg_choice : alg_list;
}
else {
/* calculate result for Apache negotiation algorithm */
algorithm_result = bestq > 0 ? alg_choice : alg_list;
}
/* run the loop again, if the "prefer-language" got no clear result */
if (preferred_language && (!best || algorithm_result != alg_choice)) {
preferred_language = NULL;
continue;
}
break;
} while (1);
/* Returning a choice response with a non-neighboring variant is a
* protocol security error in TCN (see rfc2295). We do *not*
* verify here that the variant and URI are neighbors, even though
* we may return alg_choice. We depend on the environment (the
* caller) to only declare the resource transparently negotiable if
* all variants are neighbors.
*/
*pbest = best;
return algorithm_result;
}
/* Sets response headers for a negotiated response.
* neg->is_transparent determines whether a transparently negotiated
* response or a plain `server driven negotiation' response is
* created. Applicable headers are Alternates, Vary, and TCN.
*
* The Vary header we create is sometimes longer than is required for
* the correct caching of negotiated results by HTTP/1.1 caches. For
* example if we have 3 variants x.html, x.ps.en and x.ps.nl, and if
* the Accept: header assigns a 0 quality to .ps, then the results of
* the two server-side negotiation algorithms we currently implement
* will never depend on Accept-Language so we could return `Vary:
* negotiate, accept' instead of the longer 'Vary: negotiate, accept,
* accept-language' which the code below will return. A routine for
* computing the exact minimal Vary header would be a huge pain to code
* and maintain though, especially because we need to take all possible
* twiddles in the server-side negotiation algorithms into account.
*/
static void set_neg_headers(request_rec *r, negotiation_state *neg,
int alg_result)
{
apr_table_t *hdrs;
var_rec *avail_recs = (var_rec *) neg->avail_vars->elts;
const char *sample_type = NULL;
const char *sample_language = NULL;
const char *sample_encoding = NULL;
const char *sample_charset = NULL;
char *lang;
char *qstr;
char *lenstr;
apr_off_t len;
apr_array_header_t *arr;
int max_vlist_array = (neg->avail_vars->nelts * 21);
int first_variant = 1;
int vary_by_type = 0;
int vary_by_language = 0;
int vary_by_charset = 0;
int vary_by_encoding = 0;
int j;
/* In order to avoid O(n^2) memory copies in building Alternates,
* we preallocate a apr_table_t with the maximum substrings possible,
* fill it with the variant list, and then concatenate the entire array.
* Note that if you change the number of substrings pushed, you also
* need to change the calculation of max_vlist_array above.
*/
if (neg->send_alternates && neg->avail_vars->nelts)
arr = apr_array_make(r->pool, max_vlist_array, sizeof(char *));
else
arr = NULL;
/* Put headers into err_headers_out, since send_http_header()
* outputs both headers_out and err_headers_out.
*/
hdrs = r->err_headers_out;
for (j = 0; j < neg->avail_vars->nelts; ++j) {
var_rec *variant = &avail_recs[j];
if (variant->content_languages && variant->content_languages->nelts) {
lang = apr_array_pstrcat(r->pool, variant->content_languages, ',');
}
else {
lang = NULL;
}
/* Calculate Vary by looking for any difference between variants */
if (first_variant) {
sample_type = variant->mime_type;
sample_charset = variant->content_charset;
sample_language = lang;
sample_encoding = variant->content_encoding;
}
else {
if (!vary_by_type &&
strcmp(sample_type ? sample_type : "",
variant->mime_type ? variant->mime_type : "")) {
vary_by_type = 1;
}
if (!vary_by_charset &&
strcmp(sample_charset ? sample_charset : "",
variant->content_charset ?
variant->content_charset : "")) {
vary_by_charset = 1;
}
if (!vary_by_language &&
strcmp(sample_language ? sample_language : "",
lang ? lang : "")) {
vary_by_language = 1;
}
if (!vary_by_encoding &&
strcmp(sample_encoding ? sample_encoding : "",
variant->content_encoding ?
variant->content_encoding : "")) {
vary_by_encoding = 1;
}
}
first_variant = 0;
if (!neg->send_alternates)
continue;
/* Generate the string components for this Alternates entry */
*((const char **) apr_array_push(arr)) = "{\"";
*((const char **) apr_array_push(arr)) = variant->file_name;
*((const char **) apr_array_push(arr)) = "\" ";
qstr = (char *) apr_palloc(r->pool, 6);
apr_snprintf(qstr, 6, "%1.3f", variant->source_quality);
/* Strip trailing zeros (saves those valuable network bytes) */
if (qstr[4] == '0') {
qstr[4] = '\0';
if (qstr[3] == '0') {
qstr[3] = '\0';
if (qstr[2] == '0') {
qstr[1] = '\0';
}
}
}
*((const char **) apr_array_push(arr)) = qstr;
if (variant->mime_type && *variant->mime_type) {
*((const char **) apr_array_push(arr)) = " {type ";
*((const char **) apr_array_push(arr)) = variant->mime_type;
*((const char **) apr_array_push(arr)) = "}";
}
if (variant->content_charset && *variant->content_charset) {
*((const char **) apr_array_push(arr)) = " {charset ";
*((const char **) apr_array_push(arr)) = variant->content_charset;
*((const char **) apr_array_push(arr)) = "}";
}
if (lang) {
*((const char **) apr_array_push(arr)) = " {language ";
*((const char **) apr_array_push(arr)) = lang;
*((const char **) apr_array_push(arr)) = "}";
}
if (variant->content_encoding && *variant->content_encoding) {
/* Strictly speaking, this is non-standard, but so is TCN */
*((const char **) apr_array_push(arr)) = " {encoding ";
*((const char **) apr_array_push(arr)) = variant->content_encoding;
*((const char **) apr_array_push(arr)) = "}";
}
/* Note that the Alternates specification (in rfc2295) does
* not require that we include {length x}, so we could omit it
* if determining the length is too expensive. We currently
* always include it though. 22 bytes is enough for 2^64.
*
* If the variant is a CGI script, find_content_length would
* return the length of the script, not the output it
* produces, so we check for the presence of a handler and if
* there is one we don't add a length.
*
* XXX: TODO: This check does not detect a CGI script if we
* get the variant from a type map. This needs to be fixed
* (without breaking things if the type map specifies a
* content-length, which currently leads to the correct result).
*/
if (!(variant->sub_req && variant->sub_req->handler)
&& (len = find_content_length(neg, variant)) >= 0) {
lenstr = (char *) apr_palloc(r->pool, 22);
apr_snprintf(lenstr, 22, "%" APR_OFF_T_FMT, len);
*((const char **) apr_array_push(arr)) = " {length ";
*((const char **) apr_array_push(arr)) = lenstr;
*((const char **) apr_array_push(arr)) = "}";
}
*((const char **) apr_array_push(arr)) = "}";
*((const char **) apr_array_push(arr)) = ", "; /* trimmed below */
}
if (neg->send_alternates && neg->avail_vars->nelts) {
arr->nelts--; /* remove last comma */
apr_table_mergen(hdrs, "Alternates",
apr_array_pstrcat(r->pool, arr, '\0'));
}
if (neg->is_transparent || vary_by_type || vary_by_language ||
vary_by_language || vary_by_charset || vary_by_encoding) {
apr_table_mergen(hdrs, "Vary", 2 + apr_pstrcat(r->pool,
neg->is_transparent ? ", negotiate" : "",
vary_by_type ? ", accept" : "",
vary_by_language ? ", accept-language" : "",
vary_by_charset ? ", accept-charset" : "",
vary_by_encoding ? ", accept-encoding" : "", NULL));
}
if (neg->is_transparent) { /* Create TCN response header */
apr_table_setn(hdrs, "TCN",
alg_result == alg_list ? "list" : "choice");
}
}
/**********************************************************************
*
* Return an HTML list of variants. This is output as part of the
* choice response or 406 status body.
*/
static char *make_variant_list(request_rec *r, negotiation_state *neg)
{
apr_array_header_t *arr;
int i;
int max_vlist_array = (neg->avail_vars->nelts * 15) + 2;
/* In order to avoid O(n^2) memory copies in building the list,
* we preallocate a apr_table_t with the maximum substrings possible,
* fill it with the variant list, and then concatenate the entire array.
*/
arr = apr_array_make(r->pool, max_vlist_array, sizeof(char *));
*((const char **) apr_array_push(arr)) = "Available variants:\n<ul>\n";
for (i = 0; i < neg->avail_vars->nelts; ++i) {
var_rec *variant = &((var_rec *) neg->avail_vars->elts)[i];
const char *filename = variant->file_name ? variant->file_name : "";
apr_array_header_t *languages = variant->content_languages;
const char *description = variant->description
? variant->description
: "";
/* The format isn't very neat, and it would be nice to make
* the tags human readable (eg replace 'language en' with 'English').
* Note that if you change the number of substrings pushed, you also
* need to change the calculation of max_vlist_array above.
*/
*((const char **) apr_array_push(arr)) = "<li><a href=\"";
*((const char **) apr_array_push(arr)) = filename;
*((const char **) apr_array_push(arr)) = "\">";
*((const char **) apr_array_push(arr)) = filename;
*((const char **) apr_array_push(arr)) = "</a> ";
*((const char **) apr_array_push(arr)) = description;
if (variant->mime_type && *variant->mime_type) {
*((const char **) apr_array_push(arr)) = ", type ";
*((const char **) apr_array_push(arr)) = variant->mime_type;
}
if (languages && languages->nelts) {
*((const char **) apr_array_push(arr)) = ", language ";
*((const char **) apr_array_push(arr)) = apr_array_pstrcat(r->pool,
languages, ',');
}
if (variant->content_charset && *variant->content_charset) {
*((const char **) apr_array_push(arr)) = ", charset ";
*((const char **) apr_array_push(arr)) = variant->content_charset;
}
if (variant->content_encoding) {
*((const char **) apr_array_push(arr)) = ", encoding ";
*((const char **) apr_array_push(arr)) = variant->content_encoding;
}
*((const char **) apr_array_push(arr)) = "</li>\n";
}
*((const char **) apr_array_push(arr)) = "</ul>\n";
return apr_array_pstrcat(r->pool, arr, '\0');
}
static void store_variant_list(request_rec *r, negotiation_state *neg)
{
if (r->main == NULL) {
apr_table_setn(r->notes, "variant-list", make_variant_list(r, neg));
}
else {
apr_table_setn(r->main->notes, "variant-list",
make_variant_list(r->main, neg));
}
}
/* Called if we got a "Choice" response from the variant selection algorithm.
* It checks the result of the chosen variant to see if it
* is itself negotiated (if so, return error HTTP_VARIANT_ALSO_VARIES).
* Otherwise, add the appropriate headers to the current response.
*/
static int setup_choice_response(request_rec *r, negotiation_state *neg,
var_rec *variant)
{
request_rec *sub_req;
const char *sub_vary;
if (!variant->sub_req) {
int status;
sub_req = ap_sub_req_lookup_file(variant->file_name, r, NULL);
status = sub_req->status;
if (status != HTTP_OK &&
!apr_table_get(sub_req->err_headers_out, "TCN")) {
ap_destroy_sub_req(sub_req);
return status;
}
variant->sub_req = sub_req;
}
else {
sub_req = variant->sub_req;
}
/* The variant selection algorithm told us to return a "Choice"
* response. This is the normal variant response, with
* some extra headers. First, ensure that the chosen
* variant did or will not itself engage in transparent negotiation.
* If not, set the appropriate headers, and fall through to
* the normal variant handling
*/
/* This catches the error that a transparent type map selects a
* transparent multiviews resource as the best variant.
*
* XXX: We do not signal an error if a transparent type map
* selects a _non_transparent multiviews resource as the best
* variant, because we can generate a legal negotiation response
* in this case. In this case, the vlist_validator of the
* nontransparent subrequest will be lost however. This could
* lead to cases in which a change in the set of variants or the
* negotiation algorithm of the nontransparent resource is never
* propagated up to a HTTP/1.1 cache which interprets Vary. To be
* completely on the safe side we should return HTTP_VARIANT_ALSO_VARIES
* for this type of recursive negotiation too.
*/
if (neg->is_transparent &&
apr_table_get(sub_req->err_headers_out, "TCN")) {
return HTTP_VARIANT_ALSO_VARIES;
}
/* This catches the error that a transparent type map recursively
* selects, as the best variant, another type map which itself
* causes transparent negotiation to be done.
*
* XXX: Actually, we catch this error by catching all cases of
* type map recursion. There are some borderline recursive type
* map arrangements which would not produce transparent
* negotiation protocol errors or lack of cache propagation
* problems, but such arrangements are very hard to detect at this
* point in the control flow, so we do not bother to single them
* out.
*
* Recursive type maps imply a recursive arrangement of negotiated
* resources which is visible to outside clients, and this is not
* supported by the transparent negotiation caching protocols, so
* if we are to have generic support for recursive type maps, we
* have to create some configuration setting which makes all type
* maps non-transparent when recursion is enabled. Also, if we
* want recursive type map support which ensures propagation of
* type map changes into HTTP/1.1 caches that handle Vary, we
* would have to extend the current mechanism for generating
* variant list validators.
*/
if (sub_req->handler && strcmp(sub_req->handler, "type-map") == 0) {
return HTTP_VARIANT_ALSO_VARIES;
}
/* This adds an appropriate Variant-Vary header if the subrequest
* is a multiviews resource.
*
* XXX: TODO: Note that this does _not_ handle any Vary header
* returned by a CGI if sub_req is a CGI script, because we don't
* see that Vary header yet at this point in the control flow.
* This won't cause any cache consistency problems _unless_ the
* CGI script also returns a Cache-Control header marking the
* response as cachable. This needs to be fixed, also there are
* problems if a CGI returns an Etag header which also need to be
* fixed.
*/
if ((sub_vary = apr_table_get(sub_req->err_headers_out, "Vary")) != NULL) {
apr_table_setn(r->err_headers_out, "Variant-Vary", sub_vary);
/* Move the subreq Vary header into the main request to
* prevent having two Vary headers in the response, which
* would be legal but strange.
*/
apr_table_setn(r->err_headers_out, "Vary", sub_vary);
apr_table_unset(sub_req->err_headers_out, "Vary");
}
apr_table_setn(r->err_headers_out, "Content-Location",
apr_pstrdup(r->pool, variant->file_name));
set_neg_headers(r, neg, alg_choice); /* add Alternates and Vary */
/* Still to do by caller: add Expires */
return 0;
}
/****************************************************************
*
* Executive...
*/
static int do_negotiation(request_rec *r, negotiation_state *neg,
var_rec **bestp, int prefer_scripts)
{
var_rec *avail_recs = (var_rec *) neg->avail_vars->elts;
int alg_result; /* result of variant selection algorithm */
int res;
int j;
/* Decide if resource is transparently negotiable */
/* GET or HEAD? (HEAD has same method number as GET) */
if (r->method_number == M_GET) {
/* maybe this should be configurable, see also the comment
* about recursive type maps in setup_choice_response()
*/
neg->is_transparent = 1;
/* We can't be transparent if we are a map file in the middle
* of the request URI.
*/
if (r->path_info && *r->path_info)
neg->is_transparent = 0;
for (j = 0; j < neg->avail_vars->nelts; ++j) {
var_rec *variant = &avail_recs[j];
/* We can't be transparent, because of internal
* assumptions in best_match(), if there is a
* non-neighboring variant. We can have a non-neighboring
* variant when processing a type map.
*/
if (ap_strchr_c(variant->file_name, '/'))
neg->is_transparent = 0;
/* We can't be transparent, because of the behavior
* of variant typemap bodies.
*/
if (variant->body) {
neg->is_transparent = 0;
}
}
}
if (neg->is_transparent) {
parse_negotiate_header(r, neg);
}
else { /* configure negotiation on non-transparent resource */
neg->may_choose = 1;
}
maybe_add_default_accepts(neg, prefer_scripts);
alg_result = best_match(neg, bestp);
/* alg_result is one of
* alg_choice: a best variant is chosen
* alg_list: no best variant is chosen
*/
if (alg_result == alg_list) {
/* send a list response or HTTP_NOT_ACCEPTABLE error response */
neg->send_alternates = 1; /* always include Alternates header */
set_neg_headers(r, neg, alg_result);
store_variant_list(r, neg);
if (neg->is_transparent && neg->ua_supports_trans) {
/* XXX todo: expires? cachability? */
/* Some HTTP/1.0 clients are known to choke when they get
* a 300 (multiple choices) response without a Location
* header. However the 300 code response we are are about
* to generate will only reach 1.0 clients which support
* transparent negotiation, and they should be OK. The
* response should never reach older 1.0 clients, even if
* we have CacheNegotiatedDocs enabled, because no 1.0
* proxy cache (we know of) will cache and return 300
* responses (they certainly won't if they conform to the
* HTTP/1.0 specification).
*/
return HTTP_MULTIPLE_CHOICES;
}
if (!*bestp) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"no acceptable variant: %s", r->filename);
return HTTP_NOT_ACCEPTABLE;
}
}
/* Variant selection chose a variant */
/* XXX todo: merge the two cases in the if statement below */
if (neg->is_transparent) {
if ((res = setup_choice_response(r, neg, *bestp)) != 0) {
return res; /* return if error */
}
}
else {
set_neg_headers(r, neg, alg_result);
}
/* Make sure caching works - Vary should handle HTTP/1.1, but for
* HTTP/1.0, we can't allow caching at all.
*/
/* XXX: Note that we only set r->no_cache to 1, which causes
* Expires: <now> to be added, when responding to a HTTP/1.0
* client. If we return the response to a 1.1 client, we do not
* add Expires <now>, because doing so would degrade 1.1 cache
* performance by preventing re-use of the response without prior
* revalidation. On the other hand, if the 1.1 client is a proxy
* which was itself contacted by a 1.0 client, or a proxy cache
* which can be contacted later by 1.0 clients, then we currently
* rely on this 1.1 proxy to add the Expires: <now> when it
* forwards the response.
*
* XXX: TODO: Find out if the 1.1 spec requires proxies and
* tunnels to add Expires: <now> when forwarding the response to
* 1.0 clients. I (kh) recall it is rather vague on this point.
* Testing actual 1.1 proxy implementations would also be nice. If
* Expires: <now> is not added by proxies then we need to always
* include Expires: <now> ourselves to ensure correct caching, but
* this would degrade HTTP/1.1 cache efficiency unless we also add
* Cache-Control: max-age=N, which we currently don't.
*
* Roy: No, we are not going to screw over HTTP future just to
* ensure that people who can't be bothered to upgrade their
* clients will always receive perfect server-side negotiation.
* Hell, those clients are sending bogus accept headers anyway.
*
* Manual setting of cache-control/expires always overrides this
* automated kluge, on purpose.
*/
if ((!do_cache_negotiated_docs(r->server)
&& (r->proto_num < HTTP_VERSION(1,1)))
&& neg->count_multiviews_variants != 1) {
r->no_cache = 1;
}
return OK;
}
static int handle_map_file(request_rec *r)
{
negotiation_state *neg;
apr_file_t *map;
var_rec *best;
int res;
char *udir;
if(strcmp(r->handler,MAP_FILE_MAGIC_TYPE) && strcmp(r->handler,"type-map"))
return DECLINED;
neg = parse_accept_headers(r);
if ((res = read_type_map(&map, neg, r))) {
return res;
}
res = do_negotiation(r, neg, &best, 0);
if (res != 0) return res;
if (best->body)
{
conn_rec *c = r->connection;
apr_bucket_brigade *bb;
apr_bucket *e;
ap_allow_standard_methods(r, REPLACE_ALLOW, M_GET, M_OPTIONS,
M_POST, -1);
/* XXX: ?
* if (r->method_number == M_OPTIONS) {
* return ap_send_http_options(r);
*}
*/
if (r->method_number != M_GET && r->method_number != M_POST) {
return HTTP_METHOD_NOT_ALLOWED;
}
/* ### These may be implemented by adding some 'extra' info
* of the file offset onto the etag
* ap_update_mtime(r, r->finfo.mtime);
* ap_set_last_modified(r);
* ap_set_etag(r);
*/
apr_table_setn(r->headers_out, "Accept-Ranges", "bytes");
ap_set_content_length(r, best->bytes);
/* set MIME type and charset as negotiated */
if (best->mime_type && *best->mime_type) {
if (best->content_charset && *best->content_charset) {
ap_set_content_type(r, apr_pstrcat(r->pool,
best->mime_type,
"; charset=",
best->content_charset,
NULL));
}
else {
ap_set_content_type(r, apr_pstrdup(r->pool, best->mime_type));
}
}
/* set Content-language(s) as negotiated */
if (best->content_languages && best->content_languages->nelts) {
r->content_languages = apr_array_copy(r->pool,
best->content_languages);
}
/* set Content-Encoding as negotiated */
if (best->content_encoding && *best->content_encoding) {
r->content_encoding = apr_pstrdup(r->pool,
best->content_encoding);
}
if ((res = ap_meets_conditions(r)) != OK) {
return res;
}
if ((res = ap_discard_request_body(r)) != OK) {
return res;
}
bb = apr_brigade_create(r->pool, c->bucket_alloc);
e = apr_bucket_file_create(map, best->body,
(apr_size_t)best->bytes, r->pool,
c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, e);
e = apr_bucket_eos_create(c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, e);
return ap_pass_brigade(r->output_filters, bb);
}
if (r->path_info && *r->path_info) {
/* remove any path_info from the end of the uri before trying
* to change the filename. r->path_info from the original
* request is passed along on the redirect.
*/
r->uri[ap_find_path_info(r->uri, r->path_info)] = '\0';
}
udir = ap_make_dirstr_parent(r->pool, r->uri);
udir = ap_escape_uri(r->pool, udir);
ap_internal_redirect(apr_pstrcat(r->pool, udir, best->file_name,
r->path_info, NULL), r);
return OK;
}
static int handle_multi(request_rec *r)
{
negotiation_state *neg;
var_rec *best, *avail_recs;
request_rec *sub_req;
int res;
int j;
if (r->finfo.filetype != APR_NOFILE
|| !(ap_allow_options(r) & OPT_MULTI)) {
return DECLINED;
}
neg = parse_accept_headers(r);
if ((res = read_types_multi(neg))) {
return_from_multi:
/* free all allocated memory from subrequests */
avail_recs = (var_rec *) neg->avail_vars->elts;
for (j = 0; j < neg->avail_vars->nelts; ++j) {
var_rec *variant = &avail_recs[j];
if (variant->sub_req) {
ap_destroy_sub_req(variant->sub_req);
}
}
return res;
}
if (neg->avail_vars->nelts == 0) {
return DECLINED;
}
res = do_negotiation(r, neg, &best,
(r->method_number != M_GET) || r->args ||
(r->path_info && *r->path_info));
if (res != 0)
goto return_from_multi;
if (!(sub_req = best->sub_req)) {
/* We got this out of a map file, so we don't actually have
* a sub_req structure yet. Get one now.
*/
sub_req = ap_sub_req_lookup_file(best->file_name, r, NULL);
if (sub_req->status != HTTP_OK) {
res = sub_req->status;
ap_destroy_sub_req(sub_req);
goto return_from_multi;
}
}
if (sub_req->args == NULL) {
sub_req->args = r->args;
}
/* now do a "fast redirect" ... promotes the sub_req into the main req */
ap_internal_fast_redirect(sub_req, r);
/* give no advise for time on this subrequest. Perhaps we
* should tally the last mtime amoung all variants, and date
* the most recent, but that could confuse the proxies.
*/
r->mtime = 0;
/* clean up all but our favorite variant, since that sub_req
* is now merged into the main request!
*/
avail_recs = (var_rec *) neg->avail_vars->elts;
for (j = 0; j < neg->avail_vars->nelts; ++j) {
var_rec *variant = &avail_recs[j];
if (variant != best && variant->sub_req) {
ap_destroy_sub_req(variant->sub_req);
}
}
return OK;
}
/**********************************************************************
* There is a problem with content-encoding, as some clients send and
* expect an x- token (e.g. x-gzip) while others expect the plain token
* (i.e. gzip). To try and deal with this as best as possible we do
* the following: if the client sent an Accept-Encoding header and it
* contains a plain token corresponding to the content encoding of the
* response, then set content encoding using the plain token. Else if
* the A-E header contains the x- token use the x- token in the C-E
* header. Else don't do anything.
*
* Note that if no A-E header was sent, or it does not contain a token
* compatible with the final content encoding, then the token in the
* C-E header will be whatever was specified in the AddEncoding
* directive.
*/
static int fix_encoding(request_rec *r)
{
const char *enc = r->content_encoding;
char *x_enc = NULL;
apr_array_header_t *accept_encodings;
accept_rec *accept_recs;
int i;
if (!enc || !*enc) {
return DECLINED;
}
if (enc[0] == 'x' && enc[1] == '-') {
enc += 2;
}
if ((accept_encodings = do_header_line(r->pool,
apr_table_get(r->headers_in, "Accept-Encoding"))) == NULL) {
return DECLINED;
}
accept_recs = (accept_rec *) accept_encodings->elts;
for (i = 0; i < accept_encodings->nelts; ++i) {
char *name = accept_recs[i].name;
if (!strcmp(name, enc)) {
r->content_encoding = name;
return OK;
}
if (name[0] == 'x' && name[1] == '-' && !strcmp(name+2, enc)) {
x_enc = name;
}
}
if (x_enc) {
r->content_encoding = x_enc;
return OK;
}
return DECLINED;
}
static void register_hooks(apr_pool_t *p)
{
ap_hook_fixups(fix_encoding,NULL,NULL,APR_HOOK_MIDDLE);
ap_hook_type_checker(handle_multi,NULL,NULL,APR_HOOK_FIRST);
ap_hook_handler(handle_map_file,NULL,NULL,APR_HOOK_MIDDLE);
}
module AP_MODULE_DECLARE_DATA negotiation_module =
{
STANDARD20_MODULE_STUFF,
create_neg_dir_config, /* dir config creator */
merge_neg_dir_configs, /* dir merger --- default is to override */
NULL, /* server config */
NULL, /* merge server config */
negotiation_cmds, /* command apr_table_t */
register_hooks /* register hooks */
};
|