/* * This file is a part of Winix * and is distributed under the 2-Clause BSD licence. * Author: Tomasz Sowa */ /* * 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 #include #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 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() { if( post_tab->size() >= WINIX_POSTTABLE_MAXSIZE ) { err = WINIX_ERR_INPUT_TOO_LARGE; log << log1 << "PMP: more than " << WINIX_POSTTABLE_MAXSIZE << " post variables (skipping)" << logend; return; } ConvStr(name, namew); ConvStr(content, contentw); bool added = InsertPostVar(*post_tab, namew, contentw); log << log2 << "PMP: POST var, name: \"" << namew << "\""; if( !added ) log << log2 << " (skipped)"; log << logend; } void PostMultiParser::AddFilePostVar() { if( 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(*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_, PostTab & post_tab_, PostFileTab & post_file_tab_) { 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; post_tab = &post_tab_; post_file_tab = &post_file_tab_; 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(*post_file_tab); post_tab->clear(); post_file_tab->clear(); if( err != WINIX_ERR_INPUT_TOO_LARGE && err != WINIX_ERR_CANT_CREATE_FILE ) log << log1 << "PMP: syntax error" << logend; } return err; } } // namespace Winix