author | Michael Krelin <hacker@klever.net> | 2005-05-09 11:00:28 (UTC) |
---|---|---|
committer | Michael Krelin <hacker@klever.net> | 2005-05-09 11:00:28 (UTC) |
commit | 43d47575878e4eaf3c8da84bf609fcd0bde595fb (patch) (unidiff) | |
tree | f7ec4d1f0d0a01b43feb5c9b4f414e870036522c /src | |
parent | d9578a5ae0ac4e44ff5e3c13d3f39f400f51bcf2 (diff) | |
download | kingate-43d47575878e4eaf3c8da84bf609fcd0bde595fb.zip kingate-43d47575878e4eaf3c8da84bf609fcd0bde595fb.tar.gz kingate-43d47575878e4eaf3c8da84bf609fcd0bde595fb.tar.bz2 |
1. http headers container added
2. preliminary cookies support
3. absolutely useless http_quoted_string and http_quote utility functions added
-rw-r--r-- | src/Makefile.am | 4 | ||||
-rw-r--r-- | src/cgi_gateway.cc | 4 | ||||
-rw-r--r-- | src/cookies.cc | 242 | ||||
-rw-r--r-- | src/headers.cc | 40 | ||||
-rw-r--r-- | src/util.cc | 53 |
5 files changed, 342 insertions, 1 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 12bb1f8..2d462c8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am | |||
@@ -10,9 +10,11 @@ LDADD = ${KONFORKA_LIBS} | |||
10 | 10 | ||
11 | libkingate_la_SOURCES = \ | 11 | libkingate_la_SOURCES = \ |
12 | cgi_gateway.cc \ | 12 | cgi_gateway.cc \ |
13 | cgi_interface.cc \ | 13 | cgi_interface.cc \ |
14 | util.cc | 14 | util.cc \ |
15 | cookies.cc \ | ||
16 | headers.cc | ||
15 | libkingate_la_LDFLAGS = -version-info 2:0:0 | 17 | libkingate_la_LDFLAGS = -version-info 2:0:0 |
16 | 18 | ||
17 | libkingate_fcgi_la_SOURCES = \ | 19 | libkingate_fcgi_la_SOURCES = \ |
18 | fastcgi.cc | 20 | fastcgi.cc |
diff --git a/src/cgi_gateway.cc b/src/cgi_gateway.cc index 30410f2..ab48f78 100644 --- a/src/cgi_gateway.cc +++ b/src/cgi_gateway.cc | |||
@@ -26,8 +26,12 @@ namespace kingate { | |||
26 | parse_query(qs,post); | 26 | parse_query(qs,post); |
27 | } | 27 | } |
28 | b_parsed_content = true; | 28 | b_parsed_content = true; |
29 | } | 29 | } |
30 | // Parse cookies | ||
31 | try { | ||
32 | cookies.parse_cookies(get_meta("HTTP_COOKIE")); | ||
33 | }catch(exception_notfound& enf) { } | ||
30 | } | 34 | } |
31 | 35 | ||
32 | bool cgi_gateway::has_GET(const string& n) const { | 36 | bool cgi_gateway::has_GET(const string& n) const { |
33 | return get.find(n) != get.end(); | 37 | return get.find(n) != get.end(); |
diff --git a/src/cookies.cc b/src/cookies.cc new file mode 100644 index 0000000..40a0c8b --- a/dev/null +++ b/src/cookies.cc | |||
@@ -0,0 +1,242 @@ | |||
1 | #include "kingate/cookies.h" | ||
2 | #include "kingate/util.h" | ||
3 | #include "kingate/exception.h" | ||
4 | |||
5 | namespace kingate { | ||
6 | |||
7 | /* | ||
8 | * RFC 2109: | ||
9 | * av-pairs = av-pair *(";" av-pair) | ||
10 | * av-pair = attr ["=" value] ; optional value | ||
11 | * attr = token | ||
12 | * value = word | ||
13 | * word = token | quoted-string | ||
14 | */ | ||
15 | |||
16 | /* RFC 2109: | ||
17 | * | ||
18 | * The origin server effectively ends a session by sending the client a | ||
19 | * Set-Cookie header with Max-Age=0. | ||
20 | * | ||
21 | * An origin server may include multiple Set-Cookie headers in a response. | ||
22 | * Note that an intervening gateway could fold multiple such headers into a | ||
23 | * single header. | ||
24 | * | ||
25 | * | ||
26 | * set-cookie = "Set-Cookie:" cookies | ||
27 | * cookies = 1#cookie | ||
28 | * cookie = NAME "=" VALUE *(";" cookie-av) | ||
29 | * NAME = attr | ||
30 | * VALUE = value | ||
31 | * cookie-av = "Comment" "=" value | ||
32 | * | "Domain" "=" value | ||
33 | * | "Max-Age" "=" value | ||
34 | * | "Path" "=" value | ||
35 | * | "Secure" | ||
36 | * | "Version" "=" 1*DIGIT | ||
37 | * | ||
38 | * | ||
39 | * The origin server should send the following additional HTTP/1.1 | ||
40 | * response headers, depending on circumstances: | ||
41 | * | ||
42 | * * To suppress caching of the Set-Cookie header: Cache-control: no- | ||
43 | * cache="set-cookie". | ||
44 | * | ||
45 | * and one of the following: | ||
46 | * | ||
47 | * * To suppress caching of a private document in shared caches: Cache- | ||
48 | * control: private. | ||
49 | * | ||
50 | * * To allow caching of a document and require that it be validated | ||
51 | * before returning it to the client: Cache-control: must-revalidate. | ||
52 | * | ||
53 | * * To allow caching of a document, but to require that proxy caches | ||
54 | * (not user agent caches) validate it before returning it to the | ||
55 | * client: Cache-control: proxy-revalidate. | ||
56 | * | ||
57 | * * To allow caching of a document and request that it be validated | ||
58 | * before returning it to the client (by "pre-expiring" it): | ||
59 | * Cache-control: max-age=0. Not all caches will revalidate the | ||
60 | * document in every case. | ||
61 | * | ||
62 | * HTTP/1.1 servers must send Expires: old-date (where old-date is a | ||
63 | * date long in the past) on responses containing Set-Cookie response | ||
64 | * headers unless they know for certain (by out of band means) that | ||
65 | * there are no downsteam HTTP/1.0 proxies. HTTP/1.1 servers may send | ||
66 | * other Cache-Control directives that permit caching by HTTP/1.1 | ||
67 | * proxies in addition to the Expires: old-date directive; the Cache- | ||
68 | * Control directive will override the Expires: old-date for HTTP/1.1 | ||
69 | * proxies. | ||
70 | * | ||
71 | */ | ||
72 | |||
73 | void cookie::_set_string(const string& p,const string& v) { | ||
74 | (*this)[p]=v; | ||
75 | } | ||
76 | |||
77 | void cookie::set_comment(const string& c) { | ||
78 | _set_string("comment",c); | ||
79 | } | ||
80 | void cookie::set_domain(const string& d) { | ||
81 | _set_string("domain",d); | ||
82 | } | ||
83 | void cookie::set_max_age(const string& ma) { | ||
84 | _set_string("max-age",ma); | ||
85 | } | ||
86 | void cookie::set_path(const string& p) { | ||
87 | _set_string("path",p); | ||
88 | } | ||
89 | void cookie::set_secure(bool s) { | ||
90 | if(s) | ||
91 | _set_string("secure",""); | ||
92 | else | ||
93 | erase("secure"); | ||
94 | } | ||
95 | |||
96 | void cookie::set_expires(const string& e) { | ||
97 | (*this)["expires"] = e; | ||
98 | } | ||
99 | |||
100 | const string& cookie::_get_string(const string& s) const { | ||
101 | const_iterator i = find(s); | ||
102 | if(i==end()) | ||
103 | throw exception_notfound(CODEPOINT,"No parameter set"); | ||
104 | return i->second; | ||
105 | } | ||
106 | |||
107 | const string& cookie::get_comment() const { | ||
108 | return _get_string("comment"); | ||
109 | } | ||
110 | const string& cookie::get_domain() const { | ||
111 | return _get_string("domain"); | ||
112 | } | ||
113 | const string& cookie::get_max_age() const { | ||
114 | return _get_string("max-age"); | ||
115 | } | ||
116 | const string& cookie::get_path() const { | ||
117 | return _get_string("path"); | ||
118 | } | ||
119 | bool cookie::get_secure() const { | ||
120 | return find("secure")!=end(); | ||
121 | } | ||
122 | |||
123 | const string& cookie::get_expires() const { | ||
124 | return _get_string("expires"); | ||
125 | } | ||
126 | |||
127 | bool cookie::has_comment() const { | ||
128 | return find("comment")!=end(); | ||
129 | } | ||
130 | bool cookie::has_domain() const { | ||
131 | return find("domain")!=end(); | ||
132 | } | ||
133 | bool cookie::has_max_age() const { | ||
134 | return find("max-age")!=end(); | ||
135 | } | ||
136 | bool cookie::has_path() const { | ||
137 | return find("path")!=end(); | ||
138 | } | ||
139 | |||
140 | bool cookie::has_expires() const { | ||
141 | return find("expires")!=end(); | ||
142 | } | ||
143 | |||
144 | void cookie::unset_comment() { | ||
145 | erase("comment"); | ||
146 | } | ||
147 | void cookie::unset_domain() { | ||
148 | erase("domain"); | ||
149 | } | ||
150 | void cookie::unset_max_age() { | ||
151 | erase("max-age"); | ||
152 | } | ||
153 | void cookie::unset_path() { | ||
154 | erase("path"); | ||
155 | } | ||
156 | |||
157 | void cookie::unset_expires() { | ||
158 | erase("expires"); | ||
159 | } | ||
160 | |||
161 | string cookie::set_cookie_header_rfc2109() const { | ||
162 | string rv = name + "=" + http_quoted_string(value); | ||
163 | for(const_iterator i=begin();i!=end();++i) { | ||
164 | if(i->first=="secure") { | ||
165 | rv += "; secure"; | ||
166 | }else{ | ||
167 | rv += "; "+i->first+"="+http_quote(i->second); | ||
168 | } | ||
169 | } | ||
170 | rv += "; Version=1"; | ||
171 | return rv; | ||
172 | } | ||
173 | |||
174 | string cookie::set_cookie_header() const { | ||
175 | string rv = name + "=" + value; | ||
176 | for(const_iterator i=begin();i!=end();++i) { | ||
177 | if(i->first=="secure") { | ||
178 | rv += "; secure"; | ||
179 | }else{ | ||
180 | rv += "; "+i->first+"="+i->second; | ||
181 | } | ||
182 | } | ||
183 | return rv; | ||
184 | } | ||
185 | |||
186 | bool cookies_t::has_cookie(const key_type& n) const { | ||
187 | return find(n)!=end(); | ||
188 | } | ||
189 | |||
190 | const cookie& cookies_t::get_cookie(const key_type& n) const { | ||
191 | const_iterator i=find(n); | ||
192 | if(i==end()) | ||
193 | throw exception_notfound(CODEPOINT,"No cookie with such name found"); | ||
194 | return i->second; | ||
195 | } | ||
196 | |||
197 | cookie& cookies_t::get_cookie(const key_type& n) { | ||
198 | iterator i=find(n); | ||
199 | if(i==end()) | ||
200 | throw exception_notfound(CODEPOINT,"No cookie with such name found"); | ||
201 | return i->second; | ||
202 | } | ||
203 | |||
204 | void cookies_t::parse_cookies(const string& s) { | ||
205 | string str = s; | ||
206 | while(!str.empty()) { | ||
207 | string::size_type sc = str.find(';'); | ||
208 | string s; | ||
209 | if(sc==string::npos) { | ||
210 | s = str; | ||
211 | str.erase(); | ||
212 | }else{ | ||
213 | s = str.substr(0,sc); | ||
214 | str.erase(0,sc+1); | ||
215 | } | ||
216 | string::size_type nsp=s.find_first_not_of(" \t"); | ||
217 | if((nsp!=string::npos) && nsp) | ||
218 | s.erase(0,nsp); | ||
219 | string::size_type eq=s.find('='); | ||
220 | if(eq==string::npos) | ||
221 | continue; | ||
222 | string n = s.substr(0,eq); | ||
223 | s.erase(0,eq+1); | ||
224 | nsp = n.find_last_not_of(" \t"); | ||
225 | n.erase(nsp+1); | ||
226 | nsp = s.find_first_not_of(" \t"); | ||
227 | string v; | ||
228 | if(nsp!=string::npos) | ||
229 | v = s.substr(nsp); | ||
230 | else | ||
231 | v = s; | ||
232 | nsp = v.find_last_not_of(" \t"); | ||
233 | if(nsp==string::npos) | ||
234 | v.erase(); | ||
235 | else | ||
236 | v.erase(nsp+1); | ||
237 | cookie& c = (*this)[n]; | ||
238 | c.set_name(n); c.set_value(v); | ||
239 | } | ||
240 | } | ||
241 | |||
242 | } | ||
diff --git a/src/headers.cc b/src/headers.cc new file mode 100644 index 0000000..89ac519 --- a/dev/null +++ b/src/headers.cc | |||
@@ -0,0 +1,40 @@ | |||
1 | #include "kingate/headers.h" | ||
2 | #include "kingate/exception.h" | ||
3 | |||
4 | namespace kingate { | ||
5 | |||
6 | void headers::set_header(const key_type& h,const mapped_type& c) { | ||
7 | erase(h); | ||
8 | insert(value_type(h,c)); | ||
9 | } | ||
10 | void headers::add_header(const key_type& h,const mapped_type& c) { | ||
11 | insert(value_type(h,c)); | ||
12 | } | ||
13 | void headers::unset_header(const key_type& h) { | ||
14 | erase(h); | ||
15 | } | ||
16 | const headers::mapped_type& headers::get_header(const key_type& h) const { | ||
17 | const_iterator i=find(h); | ||
18 | if(i==end()) | ||
19 | throw exception_notfound(CODEPOINT,"No such header"); | ||
20 | return i->second; | ||
21 | } | ||
22 | headers::mapped_type& headers::get_header(const key_type& h) { | ||
23 | // XXX: or should it fail if there's no such thing, unlike operator[]? | ||
24 | iterator i=find(h); | ||
25 | if(i==end()) | ||
26 | i = insert(value_type(h,"")); | ||
27 | return i->second; | ||
28 | } | ||
29 | |||
30 | pair<headers::const_iterator,headers::const_iterator> headers::get_headers(const key_type& h) const { | ||
31 | return equal_range(h); | ||
32 | } | ||
33 | pair<headers::iterator,headers::iterator> headers::get_headers(const key_type& h) { | ||
34 | return equal_range(h); | ||
35 | } | ||
36 | |||
37 | bool headers::has_header(const key_type& h) const { | ||
38 | return find(h)!=end(); | ||
39 | } | ||
40 | } | ||
diff --git a/src/util.cc b/src/util.cc index 3166e62..48e486a 100644 --- a/src/util.cc +++ b/src/util.cc | |||
@@ -49,5 +49,58 @@ namespace kingate { | |||
49 | } | 49 | } |
50 | } | 50 | } |
51 | return rv; | 51 | return rv; |
52 | } | 52 | } |
53 | |||
54 | /* | ||
55 | * RFC 2616: | ||
56 | * | ||
57 | * separators = "(" | ")" | "<" | ">" | "@" | ||
58 | * | "," | ";" | ":" | "\" | <"> | ||
59 | * | "/" | "[" | "]" | "?" | "=" | ||
60 | * | "{" | "}" | SP | HT | ||
61 | */ | ||
62 | |||
63 | /* | ||
64 | * RFC 2616: | ||
65 | * | ||
66 | * token = 1*<any CHAR except CTLs or separators> | ||
67 | */ | ||
68 | |||
69 | static const char *quotible_chars = | ||
70 | "\001\002\003\004\005\006\007\010" | ||
71 | "\011\012\013\014\015\016\017\020" | ||
72 | "\021\022\023\024\025\026\027\030" | ||
73 | "\031\032\033\034\035\036\037\040" | ||
74 | "()<>@,;:\\\"/[]?={}" /* separator chars (except for SP and HT mentioned elsewhere */ | ||
75 | "\177" | ||
76 | ; | ||
77 | |||
78 | /* | ||
79 | * RFC 2616: | ||
80 | * | ||
81 | * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) | ||
82 | * qdtext = <any TEXT except <">> | ||
83 | * | ||
84 | * The backslash character ("\") MAY be used as a single-character | ||
85 | * quoting mechanism only within quoted-string and comment constructs. | ||
86 | * | ||
87 | * quoted-pair = "\" CHAR | ||
88 | */ | ||
89 | |||
90 | string http_quoted_string(const string& str) { | ||
91 | string rv = str; | ||
92 | string::size_type sp=0; | ||
93 | for(string::size_type q=rv.find('"');(q=rv.find('"',q))!=string::npos;q+=2) | ||
94 | rv.insert(q,1,'\\'); | ||
95 | rv.insert(0,1,'"'); | ||
96 | rv += '"'; | ||
97 | return rv; | ||
98 | } | ||
99 | |||
100 | string http_quote(const string& str) { | ||
101 | if(str.find_first_of(quotible_chars)==string::npos) | ||
102 | return str; | ||
103 | return http_quoted_string(str); | ||
104 | } | ||
105 | |||
53 | } | 106 | } |