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.
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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.
24 * Interface to HTTP requests.
28 #include "HTTPRequest.h"
29 #include "util/Threads.h"
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 #include <xercesc/util/XMLUniDefs.hpp>
39 using namespace xmltooling;
40 using namespace xercesc;
41 using namespace boost;
44 bool GenericRequest::m_langFromClient = true;
45 GenericRequest::langrange_t GenericRequest::m_defaultRange;
47 GenericRequest::GenericRequest() : m_langRangeIter(m_langRange.rend())
51 GenericRequest::~GenericRequest()
55 bool GenericRequest::isDefaultPort() const
60 void GenericRequest::absolutize(string& url) const
65 // Compute a URL to the root of the site.
66 const char* scheme = getScheme();
67 string root = string(scheme) + "://" + getHostname();
69 root += ":" + lexical_cast<string>(getPort());
74 void GenericRequest::setLangDefaults(bool langFromClient, const XMLCh* defaultRange)
76 m_langFromClient = langFromClient;
77 m_defaultRange.clear();
81 XMLStringTokenizer tokens(defaultRange);
82 while (tokens.hasMoreTokens()) {
83 const XMLCh* t = tokens.nextToken();
85 vector<xstring> tagArray;
86 static const XMLCh delims[] = {chDash, chNull};
87 XMLStringTokenizer tags(t, delims);
88 while (tags.hasMoreTokens())
89 tagArray.push_back(tags.nextToken());
90 m_defaultRange.insert(langrange_t::value_type(q, tagArray));
96 bool GenericRequest::startLangMatching() const
98 // This is a no-op except on the first call, to populate the
99 // range information to use in matching.
100 if (m_langRange.empty()) {
101 if (m_langFromClient) {
102 string hdr(getLanguageRange());
103 char_separator<char> sep1(", "); // tags are split by commas or spaces
104 char_separator<char> sep2("; "); // quality is separated by semicolon
105 tokenizer< char_separator<char> > tokens(hdr, sep1);
106 for (tokenizer< char_separator<char> >::iterator t = tokens.begin(); t != tokens.end(); ++t) {
107 string tag = trim_copy(*t); // handle any surrounding ws
108 tokenizer< char_separator<char> > subtokens(tag, sep2);
109 tokenizer< char_separator<char> >::iterator s = subtokens.begin();
110 if (s != subtokens.end() && *s != "*") {
112 auto_ptr_XMLCh lang((s++)->c_str());
114 // Check for quality tag
115 if (s != subtokens.end() && starts_with(*s, "q=")) {
117 q = lexical_cast<float,string>(s->c_str() + 2);
119 catch (bad_lexical_cast&) {
124 // Split range into tokens.
125 vector<xstring> tagArray;
126 static const XMLCh delims[] = {chDash, chNull};
127 XMLStringTokenizer tags(lang.get(), delims);
129 while (tags.hasMoreTokens()) {
130 tag = tags.nextToken();
131 if (*tag != chAsterisk)
132 tagArray.push_back(tag);
135 if (tagArray.empty())
138 // Adjust q using the server priority list. As long as the supplied q deltas are larger than
139 // factors like .0001, the client settings will always trump ours.
140 if (!m_defaultRange.empty()) {
141 float adj = (m_defaultRange.size() + 1) * 0.0001f;
142 for (langrange_t::const_iterator prio = m_defaultRange.begin(); prio != m_defaultRange.end(); ++prio) {
143 if (prio->second == tagArray) {
150 m_langRange.insert(langrange_t::value_type(q, tagArray));
155 m_langRange = m_defaultRange;
159 m_langRangeIter = m_langRange.rbegin();
160 return (m_langRangeIter != const_cast<const langrange_t&>(m_langRange).rend());
163 bool GenericRequest::continueLangMatching() const
165 return (++m_langRangeIter != const_cast<const langrange_t&>(m_langRange).rend());
168 bool GenericRequest::matchLang(const XMLCh* tag) const
170 if (m_langRangeIter == const_cast<const langrange_t&>(m_langRange).rend())
173 // To match against a given range, the range has to be built up and then
174 // truncated segment by segment to look for a match against the tag.
175 // That allows more specific ranges like en-US to match the tag en.
176 // The "end" fence tells us how much of the original range to recompose
177 // into a hyphenated string, and we stop on a match, or when the fence
178 // moves back to the beginning of the array.
180 vector<xstring>::size_type end = m_langRangeIter->second.size();
182 // Skip single-character private extension separators.
183 while (end > 1 && m_langRangeIter->second[end-1].length() <= 1)
185 // Build a range from 0 to end - 1 of segments.
186 xstring compareTo(m_langRangeIter->second[0]);
187 for (vector<xstring>::size_type ix = 1; ix <= end - 1; ++ix)
188 compareTo = compareTo + chDash + m_langRangeIter->second[ix];
189 match = (compareTo.length() > 1 && XMLString::compareIStringASCII(compareTo.c_str(), tag) == 0);
190 } while (!match && --end > 0);
194 HTTPRequest::HTTPRequest()
198 HTTPRequest::~HTTPRequest()
202 bool HTTPRequest::isSecure() const
204 return strcmp(getScheme(),"https")==0;
207 bool HTTPRequest::isDefaultPort() const
210 return getPort() == 443;
212 return getPort() == 80;
215 string HTTPRequest::getLanguageRange() const
217 return getHeader("Accept-Language");
221 void handle_cookie_fn(map<string,string>& cookieMap, vector<string>& nvpair, const string& s) {
223 split(nvpair, s, is_any_of("="));
224 if (nvpair.size() == 2) {
226 cookieMap[nvpair[0]] = nvpair[1];
231 const map<string,string>& HTTPRequest::getCookies() const
233 if (m_cookieMap.empty()) {
234 string cookies=getHeader("Cookie");
235 vector<string> nvpair;
236 tokenizer< char_separator<char> > nvpairs(cookies, char_separator<char>(";"));
237 for_each(nvpairs.begin(), nvpairs.end(), boost::bind(handle_cookie_fn, boost::ref(m_cookieMap), boost::ref(nvpair), _1));
242 const char* HTTPRequest::getCookie(const char* name) const
244 map<string,string>::const_iterator lookup = getCookies().find(name);
245 return (lookup==m_cookieMap.end()) ? nullptr : lookup->second.c_str();