507 lines
13 KiB
C++
507 lines
13 KiB
C++
|
/* -*-mode:c++; c-file-style: "gnu";-*- */
|
||
|
/*
|
||
|
* $Id: Cgicc.cpp,v 1.34 2014/04/23 20:55:04 sebdiaz Exp $
|
||
|
*
|
||
|
* Copyright (C) 1996 - 2004 Stephen F. Booth <sbooth@gnu.org>
|
||
|
* 2007 Sebastien DIAZ <sebastien.diaz@gmail.com>
|
||
|
* Part of the GNU cgicc library, http://www.gnu.org/software/cgicc
|
||
|
*
|
||
|
* This library is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU Lesser General Public
|
||
|
* License as published by the Free Software Foundation; either
|
||
|
* version 3 of the License, or (at your option) any later version.
|
||
|
*
|
||
|
* This library is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
* Lesser General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU Lesser General Public
|
||
|
* License along with this library; if not, write to the Free Software
|
||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
|
||
|
*/
|
||
|
|
||
|
#ifdef __GNUG__
|
||
|
# pragma implementation
|
||
|
#endif
|
||
|
#if HAVE_CONFIG_H
|
||
|
# include "config.h"
|
||
|
#endif
|
||
|
#include <new>
|
||
|
#include <algorithm>
|
||
|
#include <functional>
|
||
|
#include <iterator>
|
||
|
#include <stdexcept>
|
||
|
|
||
|
#include "CgiUtils.h"
|
||
|
#include "Cgicc.h"
|
||
|
|
||
|
|
||
|
namespace cgicc {
|
||
|
|
||
|
// ============================================================
|
||
|
// Class FE_nameCompare
|
||
|
// ============================================================
|
||
|
class FE_nameCompare : public std::unary_function<FormEntry, bool>
|
||
|
{
|
||
|
public:
|
||
|
|
||
|
inline explicit FE_nameCompare(const std::string& name)
|
||
|
: fName(name) {}
|
||
|
|
||
|
inline bool operator() (const FormEntry& entry) const
|
||
|
{ return stringsAreEqual(fName, entry.getName()); }
|
||
|
|
||
|
private:
|
||
|
std::string fName;
|
||
|
};
|
||
|
|
||
|
// ============================================================
|
||
|
// Class FE_valueCompare
|
||
|
// ============================================================
|
||
|
class FE_valueCompare : public std::unary_function<FormEntry, bool>
|
||
|
{
|
||
|
public:
|
||
|
|
||
|
inline explicit FE_valueCompare(const std::string& value)
|
||
|
: fValue(value) {}
|
||
|
|
||
|
inline bool operator() (const FormEntry& entry) const
|
||
|
{ return stringsAreEqual(fValue, entry.getValue()); }
|
||
|
|
||
|
private:
|
||
|
std::string fValue;
|
||
|
};
|
||
|
|
||
|
|
||
|
// ============================================================
|
||
|
// Class FF_compare
|
||
|
// ============================================================
|
||
|
class FF_compare : public std::unary_function<FormFile, bool>
|
||
|
{
|
||
|
public:
|
||
|
|
||
|
inline explicit FF_compare(const std::string& name)
|
||
|
: fName(name) {}
|
||
|
|
||
|
inline bool operator() (const FormFile& entry) const
|
||
|
{ return stringsAreEqual(fName, entry.getName()); }
|
||
|
|
||
|
private:
|
||
|
std::string fName;
|
||
|
};
|
||
|
|
||
|
// ============================================================
|
||
|
// Function copy_if (handy, missing from STL)
|
||
|
// ============================================================
|
||
|
// This code taken directly from
|
||
|
// "The C++ Programming Language, Third Edition" by Bjarne Stroustrup
|
||
|
template<class In, class Out, class Pred>
|
||
|
Out
|
||
|
copy_if(In first,
|
||
|
In last,
|
||
|
Out res,
|
||
|
Pred p)
|
||
|
{
|
||
|
while(first != last) {
|
||
|
if(p(*first))
|
||
|
*res++ = *first;
|
||
|
++first;
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
} // namespace cgicc
|
||
|
|
||
|
// ============================================================
|
||
|
// Class MultipartHeader
|
||
|
// ============================================================
|
||
|
class cgicc::MultipartHeader
|
||
|
{
|
||
|
public:
|
||
|
|
||
|
MultipartHeader(const std::string& disposition,
|
||
|
const std::string& name,
|
||
|
const std::string& filename,
|
||
|
const std::string& cType);
|
||
|
|
||
|
inline
|
||
|
MultipartHeader(const MultipartHeader& head)
|
||
|
{ operator=(head); }
|
||
|
~MultipartHeader();
|
||
|
|
||
|
MultipartHeader&
|
||
|
operator= (const MultipartHeader& head);
|
||
|
|
||
|
inline std::string
|
||
|
getContentDisposition() const
|
||
|
{ return fContentDisposition; }
|
||
|
|
||
|
inline std::string
|
||
|
getName() const
|
||
|
{ return fName; }
|
||
|
|
||
|
inline std::string
|
||
|
getFilename() const
|
||
|
{ return fFilename; }
|
||
|
|
||
|
inline std::string
|
||
|
getContentType() const
|
||
|
{ return fContentType; }
|
||
|
|
||
|
private:
|
||
|
std::string fContentDisposition;
|
||
|
std::string fName;
|
||
|
std::string fFilename;
|
||
|
std::string fContentType;
|
||
|
};
|
||
|
|
||
|
cgicc::MultipartHeader::MultipartHeader(const std::string& disposition,
|
||
|
const std::string& name,
|
||
|
const std::string& filename,
|
||
|
const std::string& cType)
|
||
|
: fContentDisposition(disposition),
|
||
|
fName(name),
|
||
|
fFilename(filename),
|
||
|
fContentType(cType)
|
||
|
{}
|
||
|
|
||
|
cgicc::MultipartHeader::~MultipartHeader()
|
||
|
{}
|
||
|
|
||
|
cgicc::MultipartHeader&
|
||
|
cgicc::MultipartHeader::operator= (const MultipartHeader& head)
|
||
|
{
|
||
|
fContentDisposition = head.fContentDisposition;
|
||
|
fName = head.fName;
|
||
|
fFilename = head.fFilename;
|
||
|
fContentType = head.fContentType;
|
||
|
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
// ============================================================
|
||
|
// Class Cgicc
|
||
|
// ============================================================
|
||
|
cgicc::Cgicc::Cgicc(CgiInput *input)
|
||
|
: fEnvironment(input)
|
||
|
{
|
||
|
// this can be tweaked for performance
|
||
|
fFormData.reserve(20);
|
||
|
fFormFiles.reserve(2);
|
||
|
|
||
|
parseFormInput(fEnvironment.getPostData(), fEnvironment.getContentType());
|
||
|
parseFormInput(fEnvironment.getQueryString());
|
||
|
}
|
||
|
|
||
|
cgicc::Cgicc::~Cgicc()
|
||
|
{}
|
||
|
|
||
|
cgicc::Cgicc&
|
||
|
cgicc::Cgicc::operator= (const Cgicc& cgi)
|
||
|
{
|
||
|
this->fEnvironment = cgi.fEnvironment;
|
||
|
|
||
|
fFormData.clear();
|
||
|
fFormFiles.clear();
|
||
|
|
||
|
parseFormInput(fEnvironment.getPostData(), fEnvironment.getContentType());
|
||
|
parseFormInput(fEnvironment.getQueryString());
|
||
|
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
const char*
|
||
|
cgicc::Cgicc::getCompileDate() const
|
||
|
{ return __DATE__; }
|
||
|
|
||
|
const char*
|
||
|
cgicc::Cgicc::getCompileTime() const
|
||
|
{ return __TIME__; }
|
||
|
|
||
|
const char*
|
||
|
cgicc::Cgicc::getVersion() const
|
||
|
{ return VERSION; }
|
||
|
|
||
|
const char*
|
||
|
cgicc::Cgicc::getHost() const
|
||
|
{ return HOST; }
|
||
|
|
||
|
void
|
||
|
cgicc::Cgicc::save(const std::string& filename) const
|
||
|
{
|
||
|
fEnvironment.save(filename);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
cgicc::Cgicc::restore(const std::string& filename)
|
||
|
{
|
||
|
fEnvironment.restore(filename);
|
||
|
|
||
|
// clear the current data and re-parse the enviroment
|
||
|
fFormData.clear();
|
||
|
fFormFiles.clear();
|
||
|
|
||
|
parseFormInput(fEnvironment.getPostData(), fEnvironment.getContentType());
|
||
|
parseFormInput(fEnvironment.getQueryString());
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
cgicc::Cgicc::queryCheckbox(const std::string& elementName) const
|
||
|
{
|
||
|
const_form_iterator iter = getElement(elementName);
|
||
|
return (iter != fFormData.end() && stringsAreEqual(iter->getValue(), "on"));
|
||
|
}
|
||
|
|
||
|
std::string
|
||
|
cgicc::Cgicc::operator() (const std::string& name) const
|
||
|
{
|
||
|
std::string result;
|
||
|
const_form_iterator iter = getElement(name);
|
||
|
if(iter != fFormData.end() && false == iter->isEmpty())
|
||
|
result = iter->getValue();
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
cgicc::form_iterator
|
||
|
cgicc::Cgicc::getElement(const std::string& name)
|
||
|
{
|
||
|
return std::find_if(fFormData.begin(), fFormData.end(),FE_nameCompare(name));
|
||
|
}
|
||
|
|
||
|
cgicc::const_form_iterator
|
||
|
cgicc::Cgicc::getElement(const std::string& name) const
|
||
|
{
|
||
|
return std::find_if(fFormData.begin(), fFormData.end(),FE_nameCompare(name));
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
cgicc::Cgicc::getElement(const std::string& name,
|
||
|
std::vector<FormEntry>& result) const
|
||
|
{
|
||
|
return findEntries(name, true, result);
|
||
|
}
|
||
|
|
||
|
cgicc::form_iterator
|
||
|
cgicc::Cgicc::getElementByValue(const std::string& value)
|
||
|
{
|
||
|
return std::find_if(fFormData.begin(), fFormData.end(),
|
||
|
FE_valueCompare(value));
|
||
|
}
|
||
|
|
||
|
cgicc::const_form_iterator
|
||
|
cgicc::Cgicc::getElementByValue(const std::string& value) const
|
||
|
{
|
||
|
return std::find_if(fFormData.begin(), fFormData.end(),
|
||
|
FE_valueCompare(value));
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
cgicc::Cgicc::getElementByValue(const std::string& value,
|
||
|
std::vector<FormEntry>& result) const
|
||
|
{
|
||
|
return findEntries(value, false, result);
|
||
|
}
|
||
|
|
||
|
cgicc::file_iterator
|
||
|
cgicc::Cgicc::getFile(const std::string& name)
|
||
|
{
|
||
|
return std::find_if(fFormFiles.begin(), fFormFiles.end(), FF_compare(name));
|
||
|
}
|
||
|
|
||
|
cgicc::const_file_iterator
|
||
|
cgicc::Cgicc::getFile(const std::string& name) const
|
||
|
{
|
||
|
return std::find_if(fFormFiles.begin(), fFormFiles.end(), FF_compare(name));
|
||
|
}
|
||
|
|
||
|
|
||
|
// implementation method
|
||
|
bool
|
||
|
cgicc::Cgicc::findEntries(const std::string& param,
|
||
|
bool byName,
|
||
|
std::vector<FormEntry>& result) const
|
||
|
{
|
||
|
// empty the target vector
|
||
|
result.clear();
|
||
|
|
||
|
if(byName) {
|
||
|
cgicc::copy_if(fFormData.begin(), fFormData.end(),
|
||
|
std::back_inserter(result),FE_nameCompare(param));
|
||
|
}
|
||
|
else {
|
||
|
cgicc::copy_if(fFormData.begin(), fFormData.end(),
|
||
|
std::back_inserter(result), FE_valueCompare(param));
|
||
|
}
|
||
|
|
||
|
return false == result.empty();
|
||
|
}
|
||
|
|
||
|
void
|
||
|
cgicc::Cgicc::parseFormInput(const std::string& data, const std::string &content_type)
|
||
|
{
|
||
|
|
||
|
std::string standard_type = "application/x-www-form-urlencoded";
|
||
|
std::string multipart_type = "multipart/form-data";
|
||
|
|
||
|
// Don't waste time on empty input
|
||
|
if(true == data.empty())
|
||
|
return;
|
||
|
|
||
|
// Standard content type = application/x-www-form-urlencoded
|
||
|
// It may not be explicitly specified
|
||
|
if(true == content_type.empty()
|
||
|
|| stringsAreEqual(content_type, standard_type,standard_type.length())) {
|
||
|
std::string name, value;
|
||
|
std::string::size_type pos;
|
||
|
std::string::size_type oldPos = 0;
|
||
|
|
||
|
// Parse the data in one fell swoop for efficiency
|
||
|
while(true) {
|
||
|
// Find the '=' separating the name from its value, also have to check for '&' as its a common misplaced delimiter but is a delimiter none the less
|
||
|
pos = data.find_first_of( "&=", oldPos);
|
||
|
|
||
|
// If no '=', we're finished
|
||
|
if(std::string::npos == pos)
|
||
|
break;
|
||
|
|
||
|
// Decode the name
|
||
|
// pos == '&', that means whatever is in name is the only name/value
|
||
|
if( data.at( pos ) == '&' )
|
||
|
{
|
||
|
const char * pszData = data.c_str() + oldPos;
|
||
|
while( *pszData == '&' ) // eat up extraneous '&'
|
||
|
{
|
||
|
++pszData; ++oldPos;
|
||
|
}
|
||
|
if( oldPos >= pos )
|
||
|
{ // its all &'s
|
||
|
oldPos = ++pos;
|
||
|
continue;
|
||
|
}
|
||
|
// this becomes an name with an empty value
|
||
|
name = form_urldecode(data.substr(oldPos, pos - oldPos));
|
||
|
fFormData.push_back(FormEntry(name, "" ) );
|
||
|
oldPos = ++pos;
|
||
|
continue;
|
||
|
}
|
||
|
name = form_urldecode(data.substr(oldPos, pos - oldPos));
|
||
|
oldPos = ++pos;
|
||
|
|
||
|
// Find the '&' or ';' separating subsequent name/value pairs
|
||
|
pos = data.find_first_of(";&", oldPos);
|
||
|
|
||
|
// Even if an '&' wasn't found the rest of the string is a value
|
||
|
value = form_urldecode(data.substr(oldPos, pos - oldPos));
|
||
|
|
||
|
// Store the pair
|
||
|
fFormData.push_back(FormEntry(name, value));
|
||
|
|
||
|
if(std::string::npos == pos)
|
||
|
break;
|
||
|
|
||
|
// Update parse position
|
||
|
oldPos = ++pos;
|
||
|
}
|
||
|
}
|
||
|
// File upload type = multipart/form-data
|
||
|
else if(stringsAreEqual(multipart_type, content_type,
|
||
|
multipart_type.length())){
|
||
|
|
||
|
// Find out what the separator is
|
||
|
std::string bType = "boundary=";
|
||
|
std::string::size_type pos = content_type.find(bType);
|
||
|
|
||
|
// Remove next sentence
|
||
|
std::string commatek=";";
|
||
|
|
||
|
// generate the separators
|
||
|
std::string sep1 = content_type.substr(pos + bType.length());
|
||
|
if (sep1.find(";")!=std::string::npos)
|
||
|
sep1=sep1.substr(0,sep1.find(";"));
|
||
|
sep1.append("\r\n");
|
||
|
sep1.insert(0, "--");
|
||
|
|
||
|
std::string sep2 = content_type.substr(pos + bType.length());
|
||
|
if (sep2.find(";")!=std::string::npos)
|
||
|
sep2=sep2.substr(0,sep2.find(";"));
|
||
|
sep2.append("--\r\n");
|
||
|
sep2.insert(0, "--");
|
||
|
|
||
|
// Find the data between the separators
|
||
|
std::string::size_type start = data.find(sep1);
|
||
|
std::string::size_type sepLen = sep1.length();
|
||
|
std::string::size_type oldPos = start + sepLen;
|
||
|
|
||
|
while(true) {
|
||
|
pos = data.find(sep1, oldPos);
|
||
|
|
||
|
// If sep1 wasn't found, the rest of the data is an item
|
||
|
if(std::string::npos == pos)
|
||
|
break;
|
||
|
|
||
|
// parse the data
|
||
|
parseMIME(data.substr(oldPos, pos - oldPos));
|
||
|
|
||
|
// update position
|
||
|
oldPos = pos + sepLen;
|
||
|
}
|
||
|
|
||
|
// The data is terminated by sep2
|
||
|
pos = data.find(sep2, oldPos);
|
||
|
// parse the data, if found
|
||
|
if(std::string::npos != pos) {
|
||
|
parseMIME(data.substr(oldPos, pos - oldPos));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cgicc::MultipartHeader
|
||
|
cgicc::Cgicc::parseHeader(const std::string& data)
|
||
|
{
|
||
|
std::string disposition;
|
||
|
disposition = extractBetween(data, "Content-Disposition: ", ";");
|
||
|
|
||
|
std::string name;
|
||
|
name = extractBetween(data, "name=\"", "\"");
|
||
|
|
||
|
std::string filename;
|
||
|
filename = extractBetween(data, "filename=\"", "\"");
|
||
|
|
||
|
std::string cType;
|
||
|
cType = extractBetween(data, "Content-Type: ", "\r\n\r\n");
|
||
|
|
||
|
// This is hairy: Netscape and IE don't encode the filenames
|
||
|
// The RFC says they should be encoded, so I will assume they are.
|
||
|
filename = form_urldecode(filename);
|
||
|
|
||
|
return MultipartHeader(disposition, name, filename, cType);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
cgicc::Cgicc::parseMIME(const std::string& data)
|
||
|
{
|
||
|
// Find the header
|
||
|
std::string end = "\r\n\r\n";
|
||
|
std::string::size_type headLimit = data.find(end, 0);
|
||
|
|
||
|
// Detect error
|
||
|
if(std::string::npos == headLimit)
|
||
|
throw std::runtime_error("Malformed input");
|
||
|
|
||
|
// Extract the value - there is still a trailing CR/LF to be subtracted off
|
||
|
std::string::size_type valueStart = headLimit + end.length();
|
||
|
std::string value = data.substr(valueStart, data.length() - valueStart - 2);
|
||
|
|
||
|
// Parse the header - pass trailing CR/LF x 2 to parseHeader
|
||
|
MultipartHeader head = parseHeader(data.substr(0, valueStart));
|
||
|
|
||
|
if(head.getFilename().empty())
|
||
|
fFormData.push_back(FormEntry(head.getName(), value));
|
||
|
else
|
||
|
fFormFiles.push_back(FormFile(head.getName(),
|
||
|
head.getFilename(),
|
||
|
head.getContentType(),
|
||
|
value));
|
||
|
}
|