614 lines
11 KiB
C++
614 lines
11 KiB
C++
/*
|
|
* This file is a part of Winix
|
|
* and is distributed under the 2-Clause BSD licence.
|
|
* Author: Tomasz Sowa <t.sowa@ttmath.org>
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2008-2021, Tomasz Sowa
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include "postmultiparser.h"
|
|
#include "log.h"
|
|
#include "utf8/utf8.h"
|
|
#include "convert/text.h"
|
|
|
|
|
|
|
|
namespace Winix
|
|
{
|
|
|
|
|
|
|
|
|
|
PostMultiParser::PostMultiParser()
|
|
{
|
|
in_buffer = new unsigned char[WINIX_POSTMULTI_INPUT_BUFFER];
|
|
}
|
|
|
|
|
|
PostMultiParser::PostMultiParser(const PostMultiParser & p)
|
|
{
|
|
in_buffer = new unsigned char[WINIX_POSTMULTI_INPUT_BUFFER];
|
|
config = p.config;
|
|
}
|
|
|
|
|
|
PostMultiParser & PostMultiParser::operator=(const PostMultiParser & p)
|
|
{
|
|
in_buffer = new unsigned char[WINIX_POSTMULTI_INPUT_BUFFER];
|
|
config = p.config;
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
PostMultiParser::~PostMultiParser()
|
|
{
|
|
delete [] in_buffer;
|
|
}
|
|
|
|
|
|
void PostMultiParser::SetConfig(Config * pconfig)
|
|
{
|
|
config = pconfig;
|
|
}
|
|
|
|
|
|
void PostMultiParser::ReadBoundary()
|
|
{
|
|
boundary.clear();
|
|
|
|
while( last != -1 && last != 10 && last != 13 )
|
|
{
|
|
boundary += last;
|
|
ReadChar();
|
|
}
|
|
|
|
if( last == 13 )
|
|
{
|
|
ReadChar();
|
|
line_end_dos = true;
|
|
}
|
|
|
|
if( last == 10 )
|
|
ReadChar();
|
|
}
|
|
|
|
|
|
bool PostMultiParser::IsWhite(int c)
|
|
{
|
|
if( c==' ' || c=='\t' || c==13 )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void PostMultiParser::SkipWhite()
|
|
{
|
|
while( IsWhite(last) )
|
|
ReadChar();
|
|
}
|
|
|
|
|
|
bool PostMultiParser::IsHeader()
|
|
{
|
|
SkipWhite();
|
|
|
|
if( last == 10 )
|
|
{
|
|
ReadChar();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void PostMultiParser::ReadHeaderName()
|
|
{
|
|
SkipWhite();
|
|
|
|
while( last!=-1 && last!=':' && last!='=' && !IsWhite(last) && last!=10 )
|
|
{
|
|
header_name += last;
|
|
ReadChar();
|
|
}
|
|
|
|
SkipWhite();
|
|
|
|
if( last != ':' && last != '=' )
|
|
{
|
|
err = WINIX_ERR_BROKEN_INPUT;
|
|
return;
|
|
}
|
|
|
|
ReadChar();
|
|
}
|
|
|
|
|
|
void PostMultiParser::ReadHeaderValue()
|
|
{
|
|
bool was_apost = false;
|
|
|
|
SkipWhite();
|
|
|
|
if( last == '"' )
|
|
{
|
|
was_apost = true;
|
|
ReadChar();
|
|
}
|
|
|
|
while( last!=-1 && last!=10 &&
|
|
((!was_apost && last!=';' && !IsWhite(last)) || (was_apost && last!='"')))
|
|
{
|
|
header_value += last;
|
|
ReadChar();
|
|
}
|
|
|
|
if( was_apost )
|
|
{
|
|
if( last != '"' )
|
|
{
|
|
err = WINIX_ERR_BROKEN_INPUT;
|
|
return;
|
|
}
|
|
|
|
ReadChar();
|
|
}
|
|
|
|
SkipWhite();
|
|
|
|
if( last != ';' && last != 10 )
|
|
{
|
|
err = WINIX_ERR_BROKEN_INPUT;
|
|
return;
|
|
}
|
|
|
|
ReadChar();
|
|
}
|
|
|
|
|
|
void PostMultiParser::ReadPartHeader()
|
|
{
|
|
header_name.clear();
|
|
header_value.clear();
|
|
|
|
ReadHeaderName();
|
|
|
|
if( err != WINIX_ERR_OK )
|
|
return;
|
|
|
|
ReadHeaderValue();
|
|
|
|
if( err != WINIX_ERR_OK )
|
|
return;
|
|
|
|
log << "PMP: " << header_name << ": " << header_value << logend;
|
|
|
|
if( header_name == "name" )
|
|
name = header_value;
|
|
|
|
if( header_name == "filename" )
|
|
filename = header_value;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool PostMultiParser::HasBoundary()
|
|
{
|
|
if( content.size() < boundary.size() )
|
|
return false;
|
|
|
|
size_t c = content.size() - boundary.size();
|
|
size_t b = 0;
|
|
|
|
for( ; c<content.size() ; ++c, ++b)
|
|
{
|
|
if( content[c] != boundary[b] )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void PostMultiParser::LogFirst(const std::string & to_log, size_t len)
|
|
{
|
|
if( len > to_log.size() )
|
|
len = to_log.size();
|
|
|
|
log << log3 << "PMP: Content (";
|
|
|
|
if( len > 0 )
|
|
log << "len: " << to_log.size() << ", first " << len << " bytes";
|
|
else
|
|
log << "empty";
|
|
|
|
log << "): \"";
|
|
log.put_string(to_log, len);
|
|
log << "\"" << logend;
|
|
}
|
|
|
|
|
|
void PostMultiParser::ReadContentSkipBoundary(bool has_boundary)
|
|
{
|
|
if( has_boundary && content.size() >= boundary.size() )
|
|
{
|
|
content.erase(content.size()-boundary.size());
|
|
content_len -= boundary.size();
|
|
}
|
|
|
|
// the last new line character doesn't belong to the content
|
|
// this is a new line character before the boundary
|
|
if( !content.empty() && content[content.size()-1] == 10 )
|
|
{
|
|
content.erase(content.size()-1);
|
|
content_len -= 1;
|
|
|
|
if( line_end_dos && !content.empty() && content[content.size()-1] == 13 )
|
|
{
|
|
content.erase(content.size()-1);
|
|
content_len -= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void PostMultiParser::ReadContentToFileLoop()
|
|
{
|
|
bool has_boundary = false;
|
|
|
|
|
|
while( last!=-1 )
|
|
{
|
|
content += last;
|
|
content_len += 1;
|
|
ReadChar();
|
|
|
|
if( HasBoundary() )
|
|
{
|
|
has_boundary = true;
|
|
break;
|
|
}
|
|
|
|
if( content.size() >= WINIX_POSTMULTI_OUTPUT_BUFFER )
|
|
{
|
|
tmp_file.write(content.c_str(), content.size());
|
|
content.clear();
|
|
}
|
|
|
|
if( config->post_file_max != 0 && content_len > config->post_file_max )
|
|
{
|
|
err = WINIX_ERR_INPUT_TOO_LARGE;
|
|
log << log1 << "PMP: content greater than " << config->post_file_max << " (skipping)" << logend;
|
|
return;
|
|
}
|
|
}
|
|
|
|
ReadContentSkipBoundary(has_boundary);
|
|
|
|
// saving the rest
|
|
if( !content.empty() )
|
|
{
|
|
tmp_file.write(content.c_str(), content.size());
|
|
content.clear();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void PostMultiParser::ReadContentToFile()
|
|
{
|
|
time_t t1, t2;
|
|
|
|
content.clear();
|
|
content.reserve(WINIX_POSTMULTI_OUTPUT_BUFFER);
|
|
content_len = 0;
|
|
t1 = time(0);
|
|
|
|
ReadContentToFileLoop();
|
|
|
|
tmp_file.close();
|
|
log << log2 << "PMP: content size: " << content_len << " bytes" << logend;
|
|
|
|
t2 = time(0);
|
|
|
|
if( t2 - t1 > 1 )
|
|
log << log2 << "PMP: content read in " << (t2-t1) << " sec" << logend;
|
|
}
|
|
|
|
|
|
void PostMultiParser::ReadContentLoop()
|
|
{
|
|
bool has_boundary = false;
|
|
|
|
|
|
while( last!=-1 && !(has_boundary=HasBoundary()) )
|
|
{
|
|
content += last;
|
|
content_len += 1;
|
|
ReadChar();
|
|
|
|
if( config->post_file_max != 0 && content_len > (size_t)config->post_file_max )
|
|
{
|
|
err = WINIX_ERR_INPUT_TOO_LARGE;
|
|
log << log1 << "PMP: content greater than " << config->post_file_max << " (skipping)" << logend;
|
|
return;
|
|
}
|
|
}
|
|
|
|
ReadContentSkipBoundary(has_boundary);
|
|
}
|
|
|
|
|
|
void PostMultiParser::ReadContent()
|
|
{
|
|
content.clear();
|
|
content_len = 0;
|
|
|
|
ReadContentLoop();
|
|
|
|
log << log2 << "PMP: content size: " << content_len << " bytes" << logend;
|
|
|
|
if( !pt::is_substr_nc("pass", name.c_str()) )
|
|
LogFirst(content, config->log_post_value_size);
|
|
}
|
|
|
|
|
|
void PostMultiParser::ConvStr(const std::string & src, std::wstring & dst)
|
|
{
|
|
pt::utf8_to_wide(src, dst);
|
|
}
|
|
|
|
|
|
void PostMultiParser::AddNormalPostVar()
|
|
{
|
|
ConvStr(name, namew);
|
|
ConvStr(content, contentw);
|
|
|
|
bool added = request->AddPostVar(namew, contentw);
|
|
|
|
log << log2 << "PMP: POST var, name: \"" << namew << "\"";
|
|
|
|
if( !added )
|
|
log << log2 << " (skipped)";
|
|
|
|
log << logend;
|
|
}
|
|
|
|
|
|
void PostMultiParser::AddFilePostVar()
|
|
{
|
|
if( request->post_file_tab.size() >= WINIX_POSTTABLE_MAXSIZE )
|
|
{
|
|
err = WINIX_ERR_INPUT_TOO_LARGE;
|
|
log << log1 << "PMP: more than " << WINIX_POSTTABLE_MAXSIZE << " post file variables (skipping)" << logend;
|
|
return;
|
|
}
|
|
|
|
ConvStr(name, namew);
|
|
ConvStr(filename, post_file_temp.filename);
|
|
post_file_temp.tmp_filename = tmp_filename;
|
|
post_file_temp.file_size = content_len;
|
|
|
|
bool added = InsertPostVar(request->post_file_tab, namew, post_file_temp);
|
|
|
|
log << log2 << "PMP: POST FILE var, name: \"" << namew << "\"";
|
|
|
|
if( !added )
|
|
log << log2 << " (skipped)";
|
|
|
|
log << logend;
|
|
}
|
|
|
|
|
|
void PostMultiParser::AddPostVar()
|
|
{
|
|
if( name.empty() )
|
|
return;
|
|
|
|
if( filename.empty() )
|
|
AddNormalPostVar();
|
|
else
|
|
AddFilePostVar();
|
|
}
|
|
|
|
|
|
|
|
void PostMultiParser::CheckBoundaryEnd()
|
|
{
|
|
if( last == '-' )
|
|
{
|
|
ReadChar();
|
|
|
|
if( last != '-' )
|
|
{
|
|
err = WINIX_ERR_BROKEN_INPUT;
|
|
return;
|
|
}
|
|
|
|
// end of parsing
|
|
// the rest input (if exists) is ignored
|
|
last = -1;
|
|
}
|
|
|
|
// skipping a new line after the boundary
|
|
if( last == 13 )
|
|
ReadChar();
|
|
|
|
if( last == 10 )
|
|
ReadChar();
|
|
}
|
|
|
|
|
|
void PostMultiParser::CreateTmpFile()
|
|
{
|
|
wchar_t buf[WINIX_OS_PATH_SIZE];
|
|
size_t buf_len = sizeof(buf)/sizeof(wchar_t);
|
|
|
|
if( config->upload_dir.empty() )
|
|
{
|
|
log << log1 << "PMP: upload_dir is not set in the config" << logend;
|
|
err = WINIX_ERR_CANT_CREATE_FILE;
|
|
return;
|
|
}
|
|
|
|
swprintf(buf, buf_len, L"%ls/tmp/pmp_%u_%d_%u", config->upload_dir.c_str(), (unsigned)getpid(), tmp_filename_postfix, rand());
|
|
tmp_filename_postfix += 1;
|
|
|
|
tmp_filename = buf;
|
|
pt::wide_to_utf8(tmp_filename, atmp_filename);
|
|
|
|
tmp_file.open(atmp_filename.c_str(), std::ios_base::binary | std::ios_base::out);
|
|
// !! IMPROVE ME dodac ustawienie chmod config.upload_files_chmod dla tymczasowego pliku
|
|
|
|
if( !tmp_file )
|
|
{
|
|
log << log1 << "PMP: can't create a temporary file: " << atmp_filename << logend;
|
|
err = WINIX_ERR_CANT_CREATE_FILE;
|
|
return;
|
|
}
|
|
|
|
log << log3 << "PMP: using temporary file for the content: " << atmp_filename << logend;
|
|
}
|
|
|
|
|
|
|
|
|
|
void PostMultiParser::ReadPart()
|
|
{
|
|
name.clear();
|
|
filename.clear();
|
|
|
|
while( IsHeader() )
|
|
ReadPartHeader();
|
|
|
|
if( err != WINIX_ERR_OK )
|
|
return;
|
|
|
|
if( !filename.empty() )
|
|
CreateTmpFile();
|
|
|
|
if( err != WINIX_ERR_OK )
|
|
return;
|
|
|
|
if( !filename.empty() )
|
|
ReadContentToFile();
|
|
else
|
|
ReadContent();
|
|
|
|
if( err == WINIX_ERR_OK )
|
|
{
|
|
AddPostVar();
|
|
CheckBoundaryEnd();
|
|
}
|
|
|
|
if( err != WINIX_ERR_OK && !filename.empty() )
|
|
{
|
|
log << log1 << "PMP: deleting the tmp file: " << tmp_filename << logend;
|
|
RemoveFile(tmp_filename);
|
|
}
|
|
}
|
|
|
|
|
|
void PostMultiParser::ReadChar()
|
|
{
|
|
if( last == -1 )
|
|
return;
|
|
|
|
if( in_buffer_ind >= in_buffer_len )
|
|
{
|
|
if( in_buffer_len < WINIX_POSTMULTI_INPUT_BUFFER )
|
|
{
|
|
last = -1;
|
|
return;
|
|
}
|
|
|
|
in_buffer_len = FCGX_GetStr((char*)in_buffer, WINIX_POSTMULTI_INPUT_BUFFER, in);
|
|
in_buffer_ind = 0;
|
|
}
|
|
|
|
if( in_buffer_len == 0 )
|
|
{
|
|
last = -1;
|
|
}
|
|
else
|
|
{
|
|
last = in_buffer[in_buffer_ind];
|
|
in_buffer_ind += 1;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
Error PostMultiParser::Parse(FCGX_Stream * in_, Request & request)
|
|
{
|
|
in = in_;
|
|
last = 0;
|
|
err = WINIX_ERR_OK;
|
|
var_index = 1;
|
|
line_end_dos = false;
|
|
in_buffer_ind = WINIX_POSTMULTI_INPUT_BUFFER;
|
|
in_buffer_len = WINIX_POSTMULTI_INPUT_BUFFER;
|
|
this->request = &request;
|
|
tmp_filename_postfix = 1;
|
|
|
|
ReadChar();
|
|
ReadBoundary();
|
|
|
|
if( boundary.empty() )
|
|
return WINIX_ERR_NO_BOUNDARY;
|
|
|
|
while( last!=-1 && err == WINIX_ERR_OK )
|
|
ReadPart();
|
|
|
|
if( err != WINIX_ERR_OK )
|
|
{
|
|
RemovePostFileTmp(request.post_file_tab);
|
|
request.post_in.clear();
|
|
|
|
if( err != WINIX_ERR_INPUT_TOO_LARGE && err != WINIX_ERR_CANT_CREATE_FILE )
|
|
log << log1 << "PMP: syntax error" << logend;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
|
|
} // namespace Winix
|
|
|