/** \file HttpdForm.cpp - read stdin, parse cgi input ** ** Written: 1999-Feb-10 grymse@alhem.net **/ /* Copyright (C) 1999-2010 Anders Hedstrom This library is made available under the terms of the GNU GPL, with the additional exemption that compiling, linking, and/or using OpenSSL is allowed. If you would like to use this library in a closed-source application, a separate license agreement is available. For information about the closed-source license agreement for the C++ sockets library, please visit http://www.alhem.net/Sockets/license.html and/or email license@alhem.net. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef _MSC_VER #pragma warning(disable:4786) #endif #include "socket_include.h" #include "Parse.h" #include "IFile.h" #include "HttpdForm.h" #include "IFileUpload.h" #include "IStream.h" #include "File.h" #ifdef SOCKETS_NAMESPACE namespace SOCKETS_NAMESPACE { #endif #define TMPSIZE 32000 #ifdef _DEBUG #define DEB(x) x #else #define DEB(x) #endif HttpdForm::HttpdForm(IFile *infil, const std::string& content_type, size_t content_length) : raw(false) , m_file_upload(NULL) , m_upload_stream(NULL) { CGI *cgi = NULL; size_t extra = 2; int cl = (int)content_length; m_current = m_cgi.end(); if (content_type.size() >= 19 && content_type.substr(0, 19) == "multipart/form-data") { Parse pa(content_type,";="); char *tempcmp = NULL; size_t tc = 0; size_t l = 0; std::string str = pa.getword(); m_strBoundary = ""; while (!str.empty()) { if (!strcmp(str.c_str(),"boundary")) { m_strBoundary = pa.getword(); l = m_strBoundary.size(); tempcmp = new char[l + extra]; } // str = pa.getword(); } if (!m_strBoundary.empty()) { std::string content_type; std::string current_name; std::string current_filename; char *slask = new char[TMPSIZE]; infil -> fgets(slask, TMPSIZE); cl -= (int)strlen(slask); while (cl >= 0 && !infil -> eof()) { while (strlen(slask) && (slask[strlen(slask) - 1] == 13 || slask[strlen(slask) - 1] == 10)) { slask[strlen(slask) - 1] = 0; } content_type = ""; current_name = ""; current_filename = ""; if ((strstr(slask,m_strBoundary.c_str()) || strstr(m_strBoundary.c_str(),slask)) && strcmp(slask, m_strBoundary.c_str())) { m_strBoundary = slask; l = m_strBoundary.size(); delete[] tempcmp; tempcmp = new char[l + extra]; } if (!strcmp(slask, m_strBoundary.c_str())) { // Get headers until empty line infil -> fgets(slask, TMPSIZE); cl -= (int)strlen(slask); while (strlen(slask) && (slask[strlen(slask) - 1] == 13 || slask[strlen(slask) - 1] == 10)) { slask[strlen(slask) - 1] = 0; } while (cl >= 0 && !infil -> eof() && *slask) { Parse pa(slask,":"); std::string h = pa.getword(); std::string h2 = pa.getrest(); if (!strcasecmp(h.c_str(),"Content-type")) { Parse pa(h2, ";"); content_type = pa.getword(); } else if (!strcasecmp(h.c_str(),"Content-Disposition")) { Parse pa(h2, ";"); h = pa.getword(); if (!strcmp(h.c_str(),"form-data")) { pa.EnableQuote(true); h = pa.getword(); while (!h.empty()) { Parse pa2(h,"="); std::string name = pa2.getword(); h = pa2.getrest(); if (!strcmp(name.c_str(),"name")) { if (!h.empty() && h[0] == '"') { current_name = h.substr(1, h.size() - 2); } else { current_name = h; } } else if (!strcmp(name.c_str(),"filename")) { if (!h.empty() && h[0] == '"') { current_filename = h.substr(1, h.size() - 2); } else { current_filename = h; } size_t x = 0; for (size_t i = 0; i < current_filename.size(); i++) { if (current_filename[i] == '/' || current_filename[i] == '\\') x = i + 1; } if (x) { current_filename = current_filename.substr(x); } } h = pa.getword(); } } } // get next header value infil -> fgets(slask, TMPSIZE); cl -= (int)strlen(slask); while (strlen(slask) && (slask[strlen(slask) - 1] == 13 || slask[strlen(slask) - 1] == 10)) { slask[strlen(slask) - 1] = 0; } } // Read content, save...? if (current_filename.empty()) // not a file { std::string val; infil -> fgets(slask, TMPSIZE); cl -= (int)strlen(slask); while (cl >= 0 && !infil -> eof() && strncmp(slask,m_strBoundary.c_str(),m_strBoundary.size() )) { val += slask; infil -> fgets(slask, TMPSIZE); cl -= (int)strlen(slask); } // remove trailing cr/linefeed while (!val.empty() && (val[val.size() - 1] == 13 || val[val.size() - 1] == 10)) { val = val.substr(0,val.size() - 1); } cgi = new CGI(current_name, val); m_cgi.push_back(cgi); if (!cl) { break; } } else // current_filename.size() > 0 { // read until m_strBoundary... IFile *fil = NULL; int out = 0; char c; char fn[2000]; // where post'd file will be saved #ifdef _WIN32 { char tmp_path[2000]; ::GetTempPathA(2000, tmp_path); if (tmp_path[strlen(tmp_path) - 1] != '\\') { #ifdef __CYGWIN__ strcat(tmp_path, "\\"); #else strcat_s(tmp_path, sizeof(tmp_path), "\\"); #endif } snprintf(fn,sizeof(fn),"%s%s",tmp_path,current_filename.c_str()); } #else snprintf(fn,sizeof(fn),"/tmp/%s",current_filename.c_str()); #endif if (m_file_upload && !m_upload_stream) m_upload_stream = &m_file_upload -> IFileUploadBegin(current_name, current_filename, content_type); else { fil = new File; if (!fil -> fopen(fn, "wb")) { delete fil; fil = NULL; } } if (fil || m_upload_stream) { infil -> fread(&c,1,1); cl -= 1; while (cl >= 0 && !infil -> eof()) { if (out) { if (m_upload_stream) m_upload_stream -> IStreamWrite(&tempcmp[tc], 1); else fil -> fwrite(&tempcmp[tc],1,1); } tempcmp[tc] = c; tc++; if (tc >= l + extra) { tc = 0; out = 1; } if (tc) { if (!strncmp(tempcmp + tc + extra, m_strBoundary.c_str(), l - tc) && !strncmp(tempcmp, m_strBoundary.c_str() + l - tc, tc)) { break; } } else { if (!strncmp(tempcmp + extra, m_strBoundary.c_str(), l)) { break; } } infil -> fread(&c,1,1); cl -= 1; } if (m_file_upload && m_upload_stream) { m_file_upload -> IFileUploadEnd(); m_upload_stream = NULL; } else if (fil) { fil -> fclose(); delete fil; } cgi = new CGI(current_name,fn,fn); m_cgi.push_back(cgi); #if defined( _WIN32) && !defined(__CYGWIN__) strcpy_s(slask, TMPSIZE, m_strBoundary.c_str()); #else strcpy(slask, m_strBoundary.c_str()); #endif size_t l = strlen(slask); infil -> fgets(slask + l, TMPSIZE - (int)l); // next line cl -= (int)strlen(slask + l); } else { // couldn't open file break; } if (!cl) { break; } } } else { // Probably '--' break; } } // while (!infil -> eof()) delete[] slask; } // if (m_strBoundary) if (tempcmp) { delete[] tempcmp; } } // end of multipart else if (strstr(content_type.c_str(), "x-www-form-urlencoded")) { bool got_name = false; // tnx to FatherNitwit int cl = (int)content_length; char c,chigh,clow; std::string slask; m_current = m_cgi.end(); std::string name; infil -> fread(&c,1,1); cl--; while (cl >= 0 && !infil -> eof()) { switch (c) // built-in url decoder { case '=': /* end of name */ name = slask; slask.resize(0); got_name = true; break; case '&': /* end of value */ if (got_name) { cgi = new CGI(name,slask); got_name = false; } else { cgi = new CGI(slask, ""); } slask.resize(0); m_cgi.push_back(cgi); break; case '+': /* space */ slask += " "; break; case '%': /* hex value */ infil -> fread(&chigh,1,1); cl--; chigh -= 48 + (chigh > '9' ? 7 : 0) + (chigh >= 'a' ? 32 : 0); infil -> fread(&clow,1,1); cl--; clow -= 48 + (clow > '9' ? 7 : 0) + (clow >= 'a' ? 32 : 0); slask += (char)(chigh * 16 + clow); break; default: /* just another char */ slask += c; break; } // if (cl > 0) { infil -> fread(&c,1,1); } cl--; } if (got_name) { cgi = new CGI(name,slask); } else { cgi = new CGI(slask, ""); } m_cgi.push_back(cgi); } } // HttpdForm(buffer,l) -- request_method GET HttpdForm::HttpdForm(const std::string& buffer,size_t l) : raw(false) , m_file_upload(NULL) , m_upload_stream(NULL) { CGI *cgi = NULL; std::string slask; std::string name; char c,chigh,clow; size_t ptr = 0; bool got_name = false; m_current = m_cgi.end(); ptr = 0; while (ptr < l) { c = buffer[ptr++]; switch (c) { case '=': /* end of name */ name = slask; slask.resize(0); got_name = true; break; case '&': /* end of value */ if (got_name) { cgi = new CGI(name,slask); got_name = false; } else { cgi = new CGI(slask, ""); } slask.resize(0); m_cgi.push_back(cgi); break; case '+': /* space */ slask += " "; break; case '%': /* hex value */ chigh = buffer[ptr++]; chigh -= 48 + (chigh > '9' ? 7 : 0) + (chigh >= 'a' ? 32 : 0); clow = buffer[ptr++]; clow -= 48 + (clow > '9' ? 7 : 0) + (clow >= 'a' ? 32 : 0); slask += (char)(chigh * 16 + clow); break; default: /* just another char */ slask += c; break; } } if (got_name) { cgi = new CGI(name,slask); } else { cgi = new CGI(slask, ""); } m_cgi.push_back(cgi); } HttpdForm::~HttpdForm() { CGI *cgi = NULL; //,*tmp; for (cgi_v::iterator it = m_cgi.begin(); it != m_cgi.end(); ++it) { cgi = *it; delete cgi; } } void HttpdForm::EnableRaw(bool b) { raw = b; } void HttpdForm::strcpyval(std::string& v,const char *value) const { v = ""; for (size_t i = 0; i < strlen(value); i++) { if (value[i] == '<') { v += "<"; } else if (value[i] == '>') { v += ">"; } else if (value[i] == '&') { v += "&"; } else { v += value[i]; } } } bool HttpdForm::getfirst(std::string& n) const { m_current = m_cgi.begin(); return getnext(n); } bool HttpdForm::getnext(std::string& n) const { if (m_current != m_cgi.end() ) { CGI *current = *m_current; n = current -> name; m_current++; return true; } else { n = ""; } return false; } bool HttpdForm::getfirst(std::string& n,std::string& v) const { m_current = m_cgi.begin(); return getnext(n,v); } bool HttpdForm::getnext(std::string& n,std::string& v) const { if (m_current != m_cgi.end() ) { CGI *current = *m_current; n = current -> name; if (raw) { v = current -> value; } else { strcpyval(v,current -> value.c_str()); } m_current++; return true; } else { n = ""; } return false; } int HttpdForm::getvalue(const std::string& n,std::string& v) const { CGI *cgi = NULL; int r = 0; for (cgi_v::const_iterator it = m_cgi.begin(); it != m_cgi.end(); ++it) { cgi = *it; if (cgi -> name == n) break; cgi = NULL; } if (cgi) { if (raw) { v = cgi -> value; } else { strcpyval(v,cgi -> value.c_str()); } r++; } else { v = ""; } return r; } std::string HttpdForm::getvalue(const std::string& n) const { for (cgi_v::const_iterator it = m_cgi.begin(); it != m_cgi.end(); ++it) { CGI *cgi = *it; if (cgi -> name == n) { return cgi -> value; } } return ""; } size_t HttpdForm::getlength(const std::string& n) const { CGI *cgi = NULL; size_t l; for (cgi_v::const_iterator it = m_cgi.begin(); it != m_cgi.end(); ++it) { cgi = *it; if (cgi -> name == n) break; cgi = NULL; } l = cgi ? cgi -> value.size() : 0; if (cgi && !raw) { for (size_t i = 0; i < cgi -> value.size(); i++) { switch (cgi -> value[i]) { case '<': // < case '>': // > l += 4; break; case '&': // & l += 5; break; } } } return l; } HttpdForm::cgi_v& HttpdForm::getbase() { return m_cgi; } const std::string& HttpdForm::GetBoundary() const { return m_strBoundary; } void HttpdForm::SetFileUpload(IFileUpload& cb) { m_file_upload = &cb; } #ifdef SOCKETS_NAMESPACE } #endif