1 module hunt.http.codec.http.model.HttpCompliance;
2 
3 import hunt.logging;
4 
5 import hunt.http.codec.http.model.HttpComplianceSection;
6 
7 import hunt.collection.HashMap;
8 import hunt.collection.Map;
9 
10 import std.algorithm;
11 import std.array;
12 import std.string;
13 
14 /**
15  * HTTP compliance modes for Jetty HTTP parsing and handling.
16  * A Compliance mode consists of a set of {@link HttpComplianceSection}s which are applied
17  * when the mode is enabled.
18  * <p>
19  * Currently the set of modes is an enum and cannot be dynamically extended, but future major releases may convert this
20  * to a class. To modify modes there are four custom modes that can be modified by setting the property
21  * <code>org.eclipse.jetty.http.HttpCompliance.CUSTOMn</code> (where 'n' is '0', '1', '2' or '3'), to a comma separated
22  * list of sections.  The list should start with one of the following strings:<dl>
23  * <dt>0</dt><dd>No {@link HttpComplianceSection}s</dd>
24  * <dt>*</dt><dd>All {@link HttpComplianceSection}s</dd>
25  * <dt>RFC2616</dt><dd>The set of {@link HttpComplianceSection}s application to https://tools.ietf.org/html/rfc2616,
26  * but not https://tools.ietf.org/html/rfc7230</dd>
27  * <dt>RFC7230</dt><dd>The set of {@link HttpComplianceSection}s application to https://tools.ietf.org/html/rfc7230</dd>
28  * </dl>
29  * The remainder of the list can contain then names of {@link HttpComplianceSection}s to include them in the mode, or prefixed
30  * with a '-' to exclude thm from the mode.    Note that Jetty's modes may have some historic minor differences from the strict
31  * RFC compliance, for example the <code>RFC2616_LEGACY</code> HttpCompliance is defined as
32  * <code>RFC2616,-FIELD_COLON,-METHOD_CASE_SENSITIVE</code>.
33  * <p>
34  * Note also that the {@link EnumSet} return by {@link HttpCompliance#sections()} is mutable, so that modes may
35  * be altered in code and will affect all usages of the mode.
36  */
37 class HttpCompliance // TODO in Jetty-10 convert this enum to a class so that extra custom modes can be defined dynamically
38 {
39     /**
40      * A Legacy compliance mode to match jetty's behavior prior to RFC2616 and RFC7230. It only
41      * contains {@link HttpComplianceSection#METHOD_CASE_SENSITIVE}
42      */
43     __gshared HttpCompliance LEGACY; 
44 
45     /**
46      * The legacy RFC2616 support, which incorrectly excludes
47      * {@link HttpComplianceSection#METHOD_CASE_SENSITIVE}, {@link HttpComplianceSection#FIELD_COLON}
48      */
49     __gshared HttpCompliance RFC2616_LEGACY;
50 
51     /**
52      * The strict RFC2616 support mode
53      */
54     __gshared HttpCompliance RFC2616;
55 
56     /**
57      * Jetty's current RFC7230 support, which incorrectly excludes  {@link HttpComplianceSection#METHOD_CASE_SENSITIVE}
58      */
59     __gshared HttpCompliance RFC7230_LEGACY;
60 
61     /**
62      * The RFC7230 support mode
63      */
64     __gshared HttpCompliance RFC7230;
65 
66     // /**
67     //  * Custom compliance mode that can be defined with System property <code>org.eclipse.jetty.http.HttpCompliance.CUSTOM0</code>
68     //  */
69     // deprecated("")
70     // CUSTOM0(sectionsByProperty("CUSTOM0")),
71     // /**
72     //  * Custom compliance mode that can be defined with System property <code>org.eclipse.jetty.http.HttpCompliance.CUSTOM1</code>
73     //  */
74     // deprecated("")
75     // CUSTOM1(sectionsByProperty("CUSTOM1")),
76     // /**
77     //  * Custom compliance mode that can be defined with System property <code>org.eclipse.jetty.http.HttpCompliance.CUSTOM2</code>
78     //  */
79     // deprecated("")
80     // CUSTOM2(sectionsByProperty("CUSTOM2")),
81     // /**
82     //  * Custom compliance mode that can be defined with System property <code>org.eclipse.jetty.http.HttpCompliance.CUSTOM3</code>
83     //  */
84     // deprecated("")
85     // CUSTOM3(sectionsByProperty("CUSTOM3"));
86 
87     // static string VIOLATIONS_ATTR = "org.eclipse.jetty.http.compliance.violations";
88 
89 
90     // private static HttpComplianceSection[] sectionsByProperty(string property) {
91     //     string s = System.getProperty(HttpCompliance.class.getName() + property);
92     //     return sectionsBySpec(s == null ? "*" : s);
93     // }
94 
95     static HttpComplianceSection[] sectionsBySpec(string spec) {
96         HttpComplianceSection[] sections;
97         string[] elements = spec.split(",").map!(a => strip(a)).array;
98         int i = 0;
99 
100         switch (elements[i]) {
101             case "0":
102                 sections = [];
103                 i++;
104                 break;
105 
106             case "*":
107                 i++;
108                 sections = [HttpComplianceSection.CASE_INSENSITIVE_FIELD_VALUE_CACHE, HttpComplianceSection.FIELD_COLON, 
109                             HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE, HttpComplianceSection.NO_WS_AFTER_FIELD_NAME,
110                             HttpComplianceSection.NO_FIELD_FOLDING, HttpComplianceSection.NO_HTTP_0_9];
111                 break;
112 
113             case "RFC2616":
114                 sections = [HttpComplianceSection.CASE_INSENSITIVE_FIELD_VALUE_CACHE, HttpComplianceSection.FIELD_COLON,
115                             HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE, HttpComplianceSection.NO_WS_AFTER_FIELD_NAME];
116                 i++;
117                 break;
118 
119             case "RFC7230":
120                 i++;
121                 sections = [HttpComplianceSection.CASE_INSENSITIVE_FIELD_VALUE_CACHE, HttpComplianceSection.FIELD_COLON, 
122                             HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE, HttpComplianceSection.NO_WS_AFTER_FIELD_NAME,
123                             HttpComplianceSection.NO_FIELD_FOLDING, HttpComplianceSection.NO_HTTP_0_9];
124                 break;
125 
126             default:
127                 sections = [];
128                 break;
129         }
130 
131         while (i < elements.length) {
132             string element = elements[i++];
133             bool exclude = element.startsWith("-");
134             if (exclude)
135                 element = element[1..$];
136             HttpComplianceSection section = HttpComplianceSection.valueOf(element);
137             if (section == HttpComplianceSection.Null) {
138                 warningf("Unknown section '" ~ element ~ "' in HttpCompliance spec: " ~ spec);
139                 continue;
140             }
141             if (exclude)
142                 sections = sections.remove!(x => x == section)();
143             else
144                 sections ~= (section);
145 
146         }
147 
148         return sections;
149     }
150 
151     private __gshared Map!(HttpComplianceSection, HttpCompliance) __required; 
152 
153     __gshared HttpCompliance[] values;
154 
155     shared static this() {
156 
157         LEGACY = new HttpCompliance(sectionsBySpec("0,METHOD_CASE_SENSITIVE"));
158         RFC2616_LEGACY = new HttpCompliance(sectionsBySpec("RFC2616,-FIELD_COLON,-METHOD_CASE_SENSITIVE"));
159         RFC2616 = new HttpCompliance(sectionsBySpec("RFC2616"));
160         RFC7230_LEGACY = new HttpCompliance(sectionsBySpec("RFC7230,-METHOD_CASE_SENSITIVE"));
161         RFC7230 = new HttpCompliance(sectionsBySpec("RFC7230"));
162         values ~= LEGACY;
163         values ~= RFC2616_LEGACY;
164         values ~= RFC2616;
165         values ~= RFC7230_LEGACY;
166         values ~= RFC7230;
167 
168         __required = new HashMap!(HttpComplianceSection, HttpCompliance)();
169         // LEGACY = new HttpCompliance(sectionsBySpec("0,METHOD_CASE_SENSITIVE"));
170         // LEGACY = new HttpCompliance(sectionsBySpec("0,METHOD_CASE_SENSITIVE"));
171         // LEGACY = new HttpCompliance(sectionsBySpec("0,METHOD_CASE_SENSITIVE"));
172 
173         __required = new HashMap!(HttpComplianceSection, HttpCompliance)();
174 
175         foreach (HttpComplianceSection section ; HttpComplianceSection.values.byValue) {
176             foreach (HttpCompliance compliance ; HttpCompliance.values) {
177                 if (compliance.sections().canFind(section)) {
178                     __required.put(section, compliance);
179                     break;
180                 }
181             }
182         }
183     }
184 
185     /**
186      * @param section The section to query
187      * @return The minimum compliance required to enable the section.
188      */
189     static HttpCompliance requiredCompliance(HttpComplianceSection section) {
190         return __required.get(section);
191     }
192 
193     private HttpComplianceSection[] _sections;
194 
195     private this(HttpComplianceSection[] sections) {
196         _sections = sections;
197     }
198 
199     /**
200      * Get the set of {@link HttpComplianceSection}s supported by this compliance mode. This set
201      * is mutable, so it can be modified. Any modification will affect all usages of the mode
202      * within the same {@link ClassLoader}.
203      *
204      * @return The set of {@link HttpComplianceSection}s supported by this compliance mode.
205      */
206     HttpComplianceSection[] sections() {
207         return _sections;
208     }
209 
210 }