Factor absolutize method back into xmltooling
[shibboleth/cpp-xmltooling.git] / xmltooling / io / HTTPRequest.cpp
1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20
21 /**
22  * HTTPRequest.cpp
23  * 
24  * Interface to HTTP requests.
25  */
26
27 #include "internal.h"
28 #include "HTTPRequest.h"
29 #include "util/Threads.h"
30
31 #include <cstring>
32 #include <boost/algorithm/string.hpp>
33 #include <boost/bind.hpp>
34 #include <boost/lexical_cast.hpp>
35 #include <boost/tokenizer.hpp>
36 #include <xercesc/util/XMLStringTokenizer.hpp>
37
38 using namespace xmltooling;
39 using namespace xercesc;
40 using namespace boost;
41 using namespace std;
42
43 bool GenericRequest::m_langFromClient = true;
44 GenericRequest::langrange_t GenericRequest::m_defaultRange;
45
46 GenericRequest::GenericRequest() : m_langRangeIter(m_langRange.rend())
47 {
48 }
49
50 GenericRequest::~GenericRequest()
51 {
52 }
53
54 bool GenericRequest::isDefaultPort() const
55 {
56     return false;
57 }
58
59 void GenericRequest::absolutize(string& url) const
60 {
61     if (url.empty())
62         url = '/';
63     if (url[0] == '/') {
64         // Compute a URL to the root of the site.
65         const char* scheme = getScheme();
66         string root = string(scheme) + "://" + getHostname();
67         if (!isDefaultPort())
68             root += ":" + lexical_cast<string>(getPort());
69         url = root + url;
70     }
71 }
72
73 void GenericRequest::setLangDefaults(bool langFromClient, const XMLCh* defaultRange)
74 {
75     m_langFromClient = langFromClient;
76     m_defaultRange.clear();
77     if (!defaultRange)
78         return;
79     float q = 0.0f;
80     XMLStringTokenizer tokens(defaultRange);
81     while (tokens.hasMoreTokens()) {
82         const XMLCh* t = tokens.nextToken();
83         if (t && *t) {
84             vector<xstring> tagArray;
85             static const XMLCh delims[] = {chDash, chNull};
86             XMLStringTokenizer tags(t, delims);
87             while (tags.hasMoreTokens())
88                 tagArray.push_back(tags.nextToken());
89             m_defaultRange.insert(langrange_t::value_type(q, tagArray));
90             q -= 0.0001f;
91         }
92     }
93 }
94
95 bool GenericRequest::startLangMatching() const
96 {
97     // This is a no-op except on the first call, to populate the
98     // range information to use in matching.
99     if (m_langRange.empty()) {
100         if (m_langFromClient) {
101             string hdr(getLanguageRange());
102             char_separator<char> sep1(", "); // tags are split by commas or spaces
103             char_separator<char> sep2("; "); // quality is separated by semicolon
104             tokenizer< char_separator<char> > tokens(hdr, sep1);
105             for (tokenizer< char_separator<char> >::iterator t = tokens.begin(); t != tokens.end(); ++t) {
106                 string tag = trim_copy(*t);   // handle any surrounding ws
107                 tokenizer< char_separator<char> > subtokens(tag, sep2);
108                 tokenizer< char_separator<char> >::iterator s = subtokens.begin();
109                 if (s != subtokens.end() && *s != "*") {
110                     float q = 1.0f;
111                     auto_ptr_XMLCh lang((s++)->c_str());
112
113                     // Check for quality tag
114                     if (s != subtokens.end() && starts_with(*s, "q=")) {
115                         try {
116                             q = lexical_cast<float,string>(s->c_str() + 2);
117                         }
118                         catch (bad_lexical_cast&) {
119                             q = 0.0f;
120                         }
121                     }
122
123                     // Split range into tokens.
124                     vector<xstring> tagArray;
125                     static const XMLCh delims[] = {chDash, chNull};
126                     XMLStringTokenizer tags(lang.get(), delims);
127                     const XMLCh* tag;
128                     while (tags.hasMoreTokens()) {
129                         tag = tags.nextToken();
130                         if (*tag != chAsterisk)
131                             tagArray.push_back(tag);
132                     }
133
134                     if (tagArray.empty())
135                         continue;
136
137                     // Adjust q using the server priority list. As long as the supplied q deltas are larger than
138                     // factors like .0001, the client settings will always trump ours.
139                     if (!m_defaultRange.empty()) {
140                         float adj = (m_defaultRange.size() + 1) * 0.0001f;
141                         for (langrange_t::const_iterator prio = m_defaultRange.begin(); prio != m_defaultRange.end(); ++prio) {
142                             if (prio->second == tagArray) {
143                                 adj = prio->first;
144                                 break;
145                             }
146                         }
147                         q -= adj;
148                     }
149                     m_langRange.insert(langrange_t::value_type(q, tagArray));
150                 }
151             }
152         }
153         else {
154             m_langRange = m_defaultRange;
155         }
156     }
157     
158     m_langRangeIter = m_langRange.rbegin();
159     return (m_langRangeIter != m_langRange.rend());
160 }
161
162 bool GenericRequest::continueLangMatching() const
163 {
164     return (++m_langRangeIter != m_langRange.rend());
165 }
166
167 bool GenericRequest::matchLang(const XMLCh* tag) const
168 {
169     if (m_langRangeIter == m_langRange.rend())
170         return false;
171
172     // To match against a given range, the range has to be built up and then
173     // truncated segment by segment to look for a match against the tag.
174     // That allows more specific ranges like en-US to match the tag en.
175     // The "end" fence tells us how much of the original range to recompose
176     // into a hyphenated string, and we stop on a match, or when the fence
177     // moves back to the beginning of the array.
178     bool match = false;
179     vector<xstring>::size_type end = m_langRangeIter->second.size();
180     do {
181         // Skip single-character private extension separators.
182         while (end > 1 && m_langRangeIter->second[end-1].length() <= 1)
183             --end;
184         // Build a range from 0 to end - 1 of segments.
185         xstring compareTo(m_langRangeIter->second[0]);
186         for (vector<xstring>::size_type ix = 1; ix <= end - 1; ++ix)
187             compareTo = compareTo + chDash + m_langRangeIter->second[ix];
188         match = (compareTo.length() > 1 && XMLString::compareIStringASCII(compareTo.c_str(), tag) == 0);
189     } while (!match && --end > 0);
190     return match;
191 }
192
193 HTTPRequest::HTTPRequest()
194 {
195 }
196
197 HTTPRequest::~HTTPRequest()
198 {
199 }
200
201 bool HTTPRequest::isSecure() const
202 {
203     return strcmp(getScheme(),"https")==0;
204 }
205
206 bool HTTPRequest::isDefaultPort() const
207 {
208     if (isSecure())
209         return getPort() == 443;
210     else
211         return getPort() == 80;
212 }
213
214 string HTTPRequest::getLanguageRange() const
215 {
216     return getHeader("Accept-Language");
217 }
218
219 namespace {
220     void handle_cookie_fn(map<string,string>& cookieMap, vector<string>& nvpair, const string& s) {
221         nvpair.clear();
222         split(nvpair, s, is_any_of("="));
223         if (nvpair.size() == 2) {
224             trim(nvpair[0]);
225             cookieMap[nvpair[0]] = nvpair[1];
226         }
227     }
228 }
229
230 const char* HTTPRequest::getCookie(const char* name) const
231 {
232     if (m_cookieMap.empty()) {
233         string cookies=getHeader("Cookie");
234         vector<string> nvpair;
235         tokenizer< char_separator<char> > nvpairs(cookies, char_separator<char>(";"));
236         for_each(nvpairs.begin(), nvpairs.end(), boost::bind(handle_cookie_fn, boost::ref(m_cookieMap), boost::ref(nvpair), _1));
237     }
238     map<string,string>::const_iterator lookup=m_cookieMap.find(name);
239     return (lookup==m_cookieMap.end()) ? nullptr : lookup->second.c_str();
240 }