/////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2008-2015 Artyom Beilis (Tonkikh) // // See accompanying file COPYING.TXT file for licensing details. // /////////////////////////////////////////////////////////////////////////////// #ifndef CPPCMS_IMPL_HTTP_FILE_BUFFER_H #define CPPCMS_IMPL_HTTP_FILE_BUFFER_H #ifndef _FILE_OFFSET_BITS #define _FILE_OFFSET_BITS 64 #endif #include #include #ifdef CPPCMS_HAVE_FSEEKO // nothing #elif defined(CPPCMS_HAVE_FSEEKI64) #define fseeko(f,o,w) _fseeki64(f,o,w) #else #define fseek(f,o,w) fseek(f,o,w) #endif #include #include #include "tohex.h" #include #include #include namespace cppcms { namespace http { namespace impl { // // Properties: // // output - write only no seek // input - independent seek of write // if fime size is under certain limit - goes to memory // tracs full size via size() member function // class file_buffer : public std::streambuf { public: static const size_t buffer_size = 1024; file_buffer(size_t mlimit = 0) : in_memory_(true), f_(0), limit_(mlimit), file_size_(0), read_offset_(0) { setp(0,0); setg(0,0,0); } ~file_buffer() { if(f_) fclose(f_); } bool in_memory() { return in_memory_; } void set_limit(size_t mlimit) { if(in_memory_ && limit_ < mlimit) { limit_ = mlimit; } } void temp_dir(std::string const &tdir) { if(!in_memory_) throw booster::logic_error("Can't update temporary dir for open file"); temp_dir_ = tdir; } std::string name() { return name_; } void name(std::string const &n) { if(!in_memory_) throw booster::logic_error("File name updated on open file"); name_ = n; } int close() { if(sync() < 0) return -1; if(f_) { if(fclose(f_)!=0) { f_ = 0; return -1; } f_ = 0; } return 0; } #ifdef DEBUG_FILE_BUFFER void status(char const *name = "") { printf("------------ %s------------\n",name); printf(" in_memory=%d\n",in_memory_); printf(" limit = %d\n",int(limit_)); printf(" pbuf = {%d|%d} of %d\n",int(pptr()-pbase()),int(epptr()-pptr()),int(epptr()-pbase())); printf(" gbuf = {%d|%d} of %d\n",int(gptr()-eback()),int(egptr()-gptr()),int(egptr()-eback())); printf(" file_size = %5lld\n",file_size_); printf(" read_offset = %5lld\n",read_offset_); printf("\n"); } #endif long long size() { return file_size_ + (pptr() - pbase()); } int to_file() { if(!in_memory_) return 0; size_t read_offset =gptr() - eback(); if(write_buffer() != 0) return -1; clear(data_); output_.resize(buffer_size); setp(&output_[0],&output_[0]+buffer_size); setg(0,0,0); read_offset_ = read_offset; in_memory_=false; return 0; } private: void get_name() { if(!name_.empty()) return; std::string tmp_dir; if(temp_dir_.empty()) { char const *tmp=getenv("TEMP"); if(!tmp) tmp=getenv("TMP"); if(!tmp) tmp="/tmp"; tmp_dir=tmp; } else { tmp_dir = temp_dir_; } name_ = tmp_dir + "/cppcms_uploads_"; urandom_device rnd; char buf[16]; char rand[33]={0}; rnd.generate(buf,16); cppcms::impl::tohex(buf,sizeof(buf),rand); name_.append(rand); name_+=".tmp"; } protected: std::streampos seekpos(std::streampos off,std::ios_base::openmode mode) { return seekoff(off,std::ios_base::beg,mode); } std::streampos seekoff (std::streamoff off, std::ios_base::seekdir way,std::ios_base::openmode mode) { if(mode & std::ios_base::out) { if(off != 0 || way != std::ios_base::cur) return -1; return size(); } if(in_memory_) { size_t rpos; size_t stream_size = pptr() - pbase(); switch(way) { case std::ios_base::beg: rpos = off ; break; case std::ios_base::cur: rpos = gptr() - eback() + off; break; case std::ios_base::end: rpos = stream_size + off; break; default: return -1; } if(rpos > stream_size) return -1; setg(pbase(),pbase()+rpos,pptr()); return rpos; } else { if(sync() < 0) return -1; read_offset_ += gptr() - eback(); setg(0,0,0); long long new_offset; switch(way) { case std::ios_base::beg: new_offset = off; break; case std::ios_base::cur: new_offset = read_offset_ + off; break; case std::ios_base::end: new_offset = file_size_ + off; break; default: return -1; } if(new_offset < 0 || new_offset > file_size_) { new_offset = file_size_; return -1; } read_offset_ = new_offset; return read_offset_; } } int pbackfail(int) { if(in_memory_) return -1; if(read_offset_ == 0) return -1; int by = buffer_size / 2; if(read_offset_ < by) by = read_offset_; if(seekoff(-by,std::ios_base::cur,std::ios_base::in) < 0) return -1; if(underflow() < 0) return -1; gbump(by - 1); return std::char_traits::to_int_type(*gptr()); } int underflow() { if(in_memory_) { size_t read_size = gptr() - eback(); setg(pbase(),pbase()+read_size,pptr()); } else { if(sync() < 0) return -1; read_offset_ += gptr() - eback(); if(fseeko(f_,read_offset_,SEEK_SET)!=0) return -1; input_.resize(buffer_size); char *input_data = &input_[0]; size_t n = fread(input_data,1,buffer_size,f_); setg(input_data,input_data,input_data+n); } if(gptr()==egptr()) return -1; return std::char_traits::to_int_type(*gptr()); } int overflow(int c) { size_t size = pptr() - pbase(); if(in_memory_) { if(size >= limit_) { if(to_file() < 0) return -1; } else { size_t read_offset =gptr() - eback(); size_t new_size = data_.size() * 2; if(new_size == 0) new_size = 64; if(new_size > limit_) new_size = limit_; data_.resize(new_size); setp(&data_[0],&data_[0] + data_.size()); pbump(size); setg(pbase(),pbase() + read_offset,pptr()); } } else { if(write_buffer()!= 0) return -1; setp(pbase(),epptr()); } if(c!=EOF) { *pptr() = c; pbump(1); } return 0; } int sync() { if(in_memory_) return 0; if(write_buffer()!=0) return -1; if(fflush(f_)!=0) return -1; return 0; } int write_buffer() { if(!f_) { get_name(); f_ = booster::nowide::fopen(name_.c_str(),"w+b"); if(!f_) return -1; } if(fseek(f_,0,SEEK_END) !=0) return -1; size_t size = pptr()-pbase(); if(size != 0 && fwrite(pbase(),1,size,f_)!=size) return -1; file_size_ += size; setp(pbase(),epptr()); return 0; } private: void clear(std::vector &v) { std::vector tmp; tmp.swap(v); } bool in_memory_; FILE *f_; size_t limit_; long long file_size_; long long read_offset_; std::vector input_; std::vector output_; std::vector data_; std::string temp_dir_; std::string name_; }; } // impl } // http } // cppcms #endif