hphp/runtime/server/upload.cpp (854 lines of code) (raw):
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
| Copyright (c) 1997-2010 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include "hphp/runtime/server/upload.h"
#include "hphp/runtime/base/execution-context.h"
#include "hphp/runtime/base/program-functions.h"
#include "hphp/runtime/base/request-event-handler.h"
#include "hphp/runtime/base/runtime-option.h"
#include "hphp/runtime/base/string-util.h"
#include "hphp/runtime/base/zend-printf.h"
#include "hphp/runtime/ext/apc/ext_apc.h"
#include "hphp/util/logger.h"
#include "hphp/util/rds-local.h"
#include "hphp/util/text-util.h"
#include <folly/FileUtil.h>
using std::set;
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
static void destroy_uploaded_files();
struct Rfc1867Data final : RequestEventHandler {
std::set<std::string> rfc1867ProtectedVariables;
std::set<std::string> rfc1867UploadedFiles;
apc_rfc1867_data rfc1867ApcData;
int (*rfc1867Callback)(apc_rfc1867_data *rfc1867ApcData,
unsigned int event, void *event_data, void **extra);
void requestInit() override {
if (RuntimeOption::EnableUploadProgress) {
rfc1867Callback = apc_rfc1867_progress;
} else {
rfc1867Callback = nullptr;
}
}
void requestShutdown() override {
if (!rfc1867UploadedFiles.empty()) destroy_uploaded_files();
}
};
IMPLEMENT_STATIC_REQUEST_LOCAL(Rfc1867Data, s_rfc1867_data);
/*
* This product includes software developed by the Apache Group
* for use in the Apache HTTP server project (http://www.apache.org/).
*
*/
static void safe_php_register_variable(char *var, const Variant& val,
Array& track_vars_array,
bool override_protection);
#define FAILURE -1
/* The longest property name we use in an uploaded file array */
#define MAX_SIZE_OF_INDEX sizeof("[tmp_name]")
/* The longest anonymous name */
#define MAX_SIZE_ANONNAME 33
/* Errors */
#define UPLOAD_ERROR_OK 0 /* File upload successful */
#define UPLOAD_ERROR_A 1 /* Uploaded file exceeded upload_max_filesize */
#define UPLOAD_ERROR_B 2 /* Uploaded file exceeded MAX_FILE_SIZE */
#define UPLOAD_ERROR_C 3 /* Partially uploaded */
#define UPLOAD_ERROR_D 4 /* No file uploaded */
#define UPLOAD_ERROR_E 6 /* Missing /tmp or similar directory */
#define UPLOAD_ERROR_F 7 /* Failed to write file to disk */
#define UPLOAD_ERROR_X 8 /* File upload stopped by extension */
static void normalize_protected_variable(char *varname) {
char *s=varname, *index=nullptr, *indexend=nullptr, *p;
/* overjump leading space */
while (*s == ' ') {
s++;
}
/* and remove it */
if (s != varname) {
memmove(varname, s, strlen(s)+1);
}
for (p=varname; *p && *p != '['; p++) {
switch(*p) {
case ' ':
case '.':
*p='_';
break;
}
}
/* find index */
index = strchr(varname, '[');
if (index) {
index++;
s=index;
} else {
return;
}
/* done? */
while (index) {
while (*index == ' ' || *index == '\r' ||
*index == '\n' || *index=='\t') {
index++;
}
indexend = strchr(index, ']');
indexend = indexend ? indexend + 1 : index + strlen(index);
if (s != index) {
memmove(s, index, strlen(index)+1);
s += indexend-index;
} else {
s = indexend;
}
if (*s == '[') {
s++;
index = s;
} else {
index = nullptr;
}
}
*s++='\0';
}
static void add_protected_variable(char *varname) {
normalize_protected_variable(varname);
s_rfc1867_data->rfc1867ProtectedVariables.insert(varname);
}
static bool is_protected_variable(char *varname) {
normalize_protected_variable(varname);
auto iter = s_rfc1867_data->rfc1867ProtectedVariables.find(varname);
return iter != s_rfc1867_data->rfc1867ProtectedVariables.end();
}
static void safe_php_register_variable(char *var, const Variant& val,
Array& track_vars_array,
bool override_protection) {
if (override_protection || !is_protected_variable(var)) {
register_variable(track_vars_array, var, val);
}
}
bool is_uploaded_file(const std::string filename) {
std::set<std::string> &rfc1867UploadedFiles =
s_rfc1867_data->rfc1867UploadedFiles;
return rfc1867UploadedFiles.find(filename) != rfc1867UploadedFiles.end();
}
const std::set<std::string> &get_uploaded_files() {
return s_rfc1867_data->rfc1867UploadedFiles;
}
static void destroy_uploaded_files() {
std::set<std::string> &rfc1867UploadedFiles =
s_rfc1867_data->rfc1867UploadedFiles;
for (auto iter = rfc1867UploadedFiles.begin();
iter != rfc1867UploadedFiles.end(); iter++) {
unlink(iter->c_str());
}
rfc1867UploadedFiles.clear();
}
/*
* Following code is based on apache_multipart_buffer.c from
* libapreq-0.33 package.
*
*/
constexpr size_t FILLUNIT = 1024 * 5;
namespace {
struct multipart_buffer {
Transport *transport;
/* read buffer */
char *buffer;
char *buf_begin;
size_t bufsize;
int64_t bytes_in_buffer; // signed to catch underflow errors
/* boundary info */
char *boundary;
char *boundary_next;
int boundary_next_len;
/* post data */
const char *post_data;
uint64_t post_size;
uint64_t throw_size; // sum of all previously read chunks
char *cursor;
uint64_t read_post_bytes;
};
}
using header_list = std::list<std::pair<std::string, std::string>>;
static uint32_t read_post(multipart_buffer *self, char *buf,
uint32_t bytes_to_read) {
always_assert(bytes_to_read > 0);
always_assert(self->post_data);
always_assert(self->cursor >= self->post_data);
int64_t bytes_remaining = (self->post_size - self->throw_size) -
(self->cursor - self->post_data);
always_assert(bytes_remaining >= 0);
if (bytes_to_read <= bytes_remaining) {
memcpy(buf, self->cursor, bytes_to_read);
self->cursor += bytes_to_read;
return bytes_to_read;
}
uint32_t bytes_read = bytes_remaining;
memcpy(buf, self->cursor, bytes_remaining);
bytes_to_read -= bytes_remaining;
self->cursor += bytes_remaining;
always_assert(self->cursor == (char *)self->post_data +
(self->post_size - self->throw_size));
while (bytes_to_read > 0 && self->transport->hasMorePostData()) {
size_t extra_byte_read = 0;
const void *extra = self->transport->getMorePostData(extra_byte_read);
if (extra_byte_read == 0) break;
if (RuntimeOption::AlwaysPopulateRawPostData) {
// Possible overflow in buffer_append if post_size + extra_byte_read >=
// MAX INT
self->post_data = (const char *)buffer_append(
self->post_data, self->post_size, extra, extra_byte_read);
self->cursor = (char*)self->post_data + self->post_size;
} else {
self->post_data = (const char *)extra;
self->throw_size = self->post_size;
self->cursor = (char*)self->post_data;
}
self->post_size += extra_byte_read;
if (bytes_to_read <= extra_byte_read) {
memcpy(buf + bytes_read, self->cursor, bytes_to_read);
self->cursor += bytes_to_read;
return bytes_read + bytes_to_read;
}
memcpy(buf + bytes_read, self->cursor, extra_byte_read);
bytes_to_read -= extra_byte_read;
bytes_read += extra_byte_read;
self->cursor += extra_byte_read;
}
return bytes_read;
}
/*
fill up the buffer with client data.
returns number of bytes added to buffer.
*/
static uint32_t fill_buffer(multipart_buffer *self) {
uint32_t bytes_to_read, total_read = 0, actual_read = 0;
/* shift the existing data if necessary */
if (self->bytes_in_buffer > 0 && self->buf_begin != self->buffer) {
memmove(self->buffer, self->buf_begin, self->bytes_in_buffer);
}
self->buf_begin = self->buffer;
/* calculate the free space in the buffer */
bytes_to_read = self->bufsize - self->bytes_in_buffer;
always_assert(self->bufsize > 0);
always_assert(self->bytes_in_buffer >= 0);
/* read the required number of bytes */
while (bytes_to_read > 0) {
char *buf = self->buffer + self->bytes_in_buffer;
actual_read = read_post(self, buf, bytes_to_read);
/* update the buffer length */
if (actual_read > 0) {
always_assert(bytes_to_read >= actual_read);
self->bytes_in_buffer += actual_read;
self->read_post_bytes += actual_read;
total_read += actual_read;
bytes_to_read -= actual_read;
} else {
break;
}
}
return total_read;
}
/* eof if we are out of bytes, or if we hit the final boundary */
static int multipart_buffer_eof(multipart_buffer *self) {
if ( (self->bytes_in_buffer == 0 && fill_buffer(self) < 1) ) {
return 1;
} else {
return 0;
}
}
/* create new multipart_buffer structure */
static multipart_buffer *multipart_buffer_new(Transport *transport,
const char *data, size_t size,
std::string boundary) {
multipart_buffer *self =
(multipart_buffer *)calloc(1, sizeof(multipart_buffer));
self->transport = transport;
auto minsize = boundary.length() + 6;
if (minsize < FILLUNIT) minsize = FILLUNIT;
self->buffer = (char *) calloc(1, minsize + 1);
self->bufsize = minsize;
vspprintf(&self->boundary, 0, "--%s", boundary.c_str());
self->boundary_next_len =
vspprintf(&self->boundary_next, 0, "\n--%s", boundary.c_str());
self->buf_begin = self->buffer;
self->bytes_in_buffer = 0;
self->post_data = data;
self->cursor = (char*)self->post_data;
self->post_size = size;
self->throw_size = 0;
return self;
}
/*
gets the next CRLF terminated line from the input buffer.
if it doesn't find a CRLF, and the buffer isn't completely full, returns
NULL; otherwise, returns the beginning of the null-terminated line,
minus the CRLF.
note that we really just look for LF terminated lines. this works
around a bug in internet explorer for the macintosh which sends mime
boundaries that are only LF terminated when you use an image submit
button in a multipart/form-data form.
*/
static char *next_line(multipart_buffer *self) {
/* look for LF in the data */
char* line = self->buf_begin;
char* ptr = (char*)memchr(self->buf_begin, '\n', self->bytes_in_buffer);
if (ptr) { /* LF found */
/* terminate the string, remove CRLF */
if ((ptr - line) > 0 && *(ptr-1) == '\r') {
*(ptr-1) = 0;
} else {
*ptr = 0;
}
/* bump the pointer */
self->buf_begin = ptr + 1;
self->bytes_in_buffer -= (self->buf_begin - line);
} else { /* no LF found */
/* buffer isn't completely full, fail */
if (self->bytes_in_buffer < self->bufsize) {
return nullptr;
}
/* return entire buffer as a partial line */
line[self->bufsize] = 0;
self->buf_begin = ptr;
self->bytes_in_buffer = 0;
}
return line;
}
/* returns the next CRLF terminated line from the client */
static char *get_line(multipart_buffer *self) {
char* ptr = next_line(self);
if (!ptr) {
fill_buffer(self);
ptr = next_line(self);
}
return ptr;
}
/* finds a boundary */
static int find_boundary(multipart_buffer *self, char *boundary) {
char *line;
/* loop thru lines */
while( (line = get_line(self)) )
{
/* finished if we found the boundary */
if (!strcmp(line, boundary)) {
return 1;
}
}
/* didn't find the boundary */
return 0;
}
/* parse headers */
static int multipart_buffer_headers(multipart_buffer *self,
header_list &header) {
char *line;
std::string key;
std::string buf_value;
std::pair<std::string, std::string> entry;
/* didn't find boundary, abort */
if (!find_boundary(self, self->boundary)) {
return 0;
}
/* get lines of text, or CRLF_CRLF */
while( (line = get_line(self)) && strlen(line) > 0 )
{
/* add header to table */
char *value = nullptr;
/* space in the beginning means same header */
if (!isspace(line[0])) {
value = strchr(line, ':');
}
if (value) {
if (!buf_value.empty() && !key.empty() ) {
entry = std::make_pair(key, buf_value);
header.push_back(entry);
buf_value.erase();
key.erase();
}
*value = '\0';
do { value++; } while(isspace(*value));
key.assign(line);
buf_value.append(value);
} else if (!buf_value.empty() ) {
/* If no ':' on the line, add to previous line */
buf_value.append(line);
} else {
continue;
}
}
if (!buf_value.empty() && !key.empty()) {
entry = std::make_pair(key, buf_value);
header.push_back(entry);
}
return 1;
}
static char *php_mime_get_hdr_value(header_list &header, char *key) {
if (key == nullptr) return nullptr;
for (header_list::iterator iter = header.begin();
iter != header.end(); iter++) {
if (!strcasecmp(iter->first.c_str(), key)) {
return (char *)iter->second.c_str();
}
}
return nullptr;
}
static char *php_ap_getword(char **line, char stop) {
char *pos = *line, quote;
char *res;
while (*pos && *pos != stop) {
if ((quote = *pos) == '"' || quote == '\'') {
++pos;
while (*pos && *pos != quote) {
if (*pos == '\\' && pos[1] && pos[1] == quote) {
pos += 2;
} else {
++pos;
}
}
if (*pos) {
++pos;
}
} else ++pos;
}
if (*pos == '\0') {
res = strdup(*line);
*line += strlen(*line);
return res;
}
res = (char*)malloc(pos - *line + 1);
memcpy(res, *line, pos - *line);
res[pos - *line] = 0;
while (*pos == stop) {
++pos;
}
*line = pos;
return res;
}
static char *substring_conf(char *start, int len, char quote) {
char *result = (char *)malloc(len + 2);
char *resp = result;
int i;
for (i = 0; i < len; ++i) {
if (start[i] == '\\' &&
(start[i + 1] == '\\' || (quote && start[i + 1] == quote))) {
*resp++ = start[++i];
} else {
*resp++ = start[i];
}
}
*resp++ = '\0';
return result;
}
static char *php_ap_getword_conf(char **line) {
char *str = *line, *strend, *res, quote;
while (*str && isspace(*str)) {
++str;
}
if (!*str) {
*line = str;
return strdup("");
}
if ((quote = *str) == '"' || quote == '\'') {
strend = str + 1;
look_for_quote:
while (*strend && *strend != quote) {
if (*strend == '\\' && strend[1] && strend[1] == quote) {
strend += 2;
} else {
++strend;
}
}
if (*strend && *strend == quote) {
char p = *(strend + 1);
if (p != '\r' && p != '\n' && p != '\0') {
strend++;
goto look_for_quote;
}
}
res = substring_conf(str + 1, strend - str - 1, quote);
if (*strend == quote) {
++strend;
}
} else {
strend = str;
while (*strend && !isspace(*strend)) {
++strend;
}
res = substring_conf(str, strend - str, 0);
}
while (*strend && isspace(*strend)) {
++strend;
}
*line = strend;
return res;
}
/*
search for a string in a fixed-length byte string.
if partial is true, partial matches are allowed at the end of the buffer.
returns NULL if not found, or a pointer to the start of the first match.
*/
static char *php_ap_memstr(char *haystack, int haystacklen, char *needle,
int needlen, int partial) {
int len = haystacklen;
char *ptr = haystack;
/* iterate through first character matches */
while( (ptr = (char *)memchr(ptr, needle[0], len)) ) {
/* calculate length after match */
len = haystacklen - (ptr - (char *)haystack);
/* done if matches up to capacity of buffer */
if (memcmp(needle, ptr, needlen < len ? needlen : len) == 0 &&
(partial || len >= needlen)) {
break;
}
/* next character */
ptr++; len--;
}
return ptr;
}
/* read until a boundary condition */
static int multipart_buffer_read(multipart_buffer *self, char *buf,
int bytes, int *end) {
int len, max;
char *bound;
/* fill buffer if needed */
if (bytes > self->bytes_in_buffer) {
fill_buffer(self);
}
/* look for a potential boundary match, only read data up to that point */
if ((bound =
php_ap_memstr(self->buf_begin, self->bytes_in_buffer,
self->boundary_next, self->boundary_next_len, 1))) {
max = bound - self->buf_begin;
if (end &&
php_ap_memstr(self->buf_begin, self->bytes_in_buffer,
self->boundary_next, self->boundary_next_len, 0)) {
*end = 1;
}
} else {
max = self->bytes_in_buffer;
}
/* maximum number of bytes we are reading */
len = max < bytes-1 ? max : bytes-1;
/* if we read any data... */
if (len > 0) {
/* copy the data */
memcpy(buf, self->buf_begin, len);
buf[len] = 0;
if (bound && len > 0 && buf[len-1] == '\r') {
buf[--len] = 0;
}
/* update the buffer */
self->bytes_in_buffer -= len;
self->buf_begin += len;
}
return len;
}
/*
XXX: this is horrible memory-usage-wise, but we only expect
to do this on small pieces of form data.
*/
static char *multipart_buffer_read_body(multipart_buffer *self,
unsigned int *len) {
char buf[FILLUNIT], *out=nullptr;
int total_bytes=0, read_bytes=0;
while((read_bytes = multipart_buffer_read(self, buf, sizeof(buf), nullptr))) {
out = (char *)realloc(out, total_bytes + read_bytes + 1);
memcpy(out + total_bytes, buf, read_bytes);
total_bytes += read_bytes;
}
if (out) out[total_bytes] = '\0';
*len = total_bytes;
return out;
}
/*
* The combined READER/HANDLER
*
*/
void rfc1867PostHandler(Transport* transport,
Array& post,
Array& files,
size_t content_length,
const void*& data, size_t& size,
const std::string boundary) {
char *s=nullptr, *start_arr=nullptr;
std::string array_index, abuf;
char *lbuf=nullptr;
int total_bytes=0, cancel_upload=0, is_arr_upload=0, array_len=0;
int max_file_size=0, skip_upload=0, anonindex=0;
std::set<std::string> &uploaded_files = s_rfc1867_data->rfc1867UploadedFiles;
multipart_buffer *mbuff;
int fd=-1;
void *event_extra_data = nullptr;
unsigned int llen = 0;
int upload_count = RuntimeOption::MaxFileUploads;
/* Initialize the buffer */
if (!(mbuff = multipart_buffer_new(transport,
(const char *)data, size, boundary))) {
Logger::Warning("Unable to initialize the input buffer");
return;
}
/* Initialize $_FILES[] */
s_rfc1867_data->rfc1867ProtectedVariables.clear();
uploaded_files.clear();
int (*php_rfc1867_callback)(apc_rfc1867_data *rfc1867ApcData,
unsigned int event, void *event_data,
void **extra) = s_rfc1867_data->rfc1867Callback;
if (php_rfc1867_callback != nullptr) {
multipart_event_start event_start;
event_start.content_length = content_length;
if (php_rfc1867_callback(&s_rfc1867_data->rfc1867ApcData,
MULTIPART_EVENT_START, &event_start,
&event_extra_data) == FAILURE) {
goto fileupload_done;
}
}
while (!multipart_buffer_eof(mbuff)) {
std::string temp_filename;
char buff[FILLUNIT];
char *cd=nullptr,*param=nullptr,*filename=nullptr, *tmp=nullptr;
size_t blen=0, wlen=0;
off_t offset;
header_list header;
if (!multipart_buffer_headers(mbuff, header)) {
goto fileupload_done;
}
if ((cd = php_mime_get_hdr_value(header, "Content-Disposition"))) {
char *pair=nullptr;
int end=0;
while (isspace(*cd)) {
++cd;
}
while (*cd && (pair = php_ap_getword(&cd, ';')))
{
char *key=nullptr, *word = pair;
while (isspace(*cd)) {
++cd;
}
if (strchr(pair, '=')) {
key = php_ap_getword(&pair, '=');
if (!strcasecmp(key, "name")) {
if (param) {
free(param);
}
param = php_ap_getword_conf(&pair);
} else if (!strcasecmp(key, "filename")) {
if (filename) {
free(filename);
}
filename = php_ap_getword_conf(&pair);
}
}
if (key) free(key);
free(word);
}
/* Normal form variable, safe to read all data into memory */
if (!filename && param) {
unsigned int value_len;
char *value = multipart_buffer_read_body(mbuff, &value_len);
unsigned int new_val_len; /* Dummy variable */
if (!value) {
value = strdup("");
}
new_val_len = value_len;
if (php_rfc1867_callback != nullptr) {
multipart_event_formdata event_formdata;
size_t newlength = new_val_len;
event_formdata.post_bytes_processed = mbuff->read_post_bytes;
event_formdata.name = param;
event_formdata.value = &value;
event_formdata.length = new_val_len;
event_formdata.newlength = &newlength;
if (php_rfc1867_callback(&s_rfc1867_data->rfc1867ApcData,
MULTIPART_EVENT_FORMDATA, &event_formdata,
&event_extra_data) == FAILURE) {
free(param);
free(value);
continue;
}
new_val_len = newlength;
}
String val(value, new_val_len, CopyString);
safe_php_register_variable(param, val, post, 0);
if (!strcasecmp(param, "MAX_FILE_SIZE")) {
max_file_size = atol(value);
}
free(param);
free(value);
continue;
}
/* If file_uploads=off, skip the file part */
if (!RuntimeOption::EnableFileUploads) {
skip_upload = 1;
} else if (upload_count <= 0) {
Logger::Warning(
"Maximum number of allowable file uploads has been exceeded"
);
skip_upload = 1;
}
/* Return with an error if the posted data is garbled */
if (!param && !filename) {
Logger::Warning("File Upload Mime headers garbled");
goto fileupload_done;
}
if (!param) {
param = (char*)malloc(MAX_SIZE_ANONNAME);
snprintf(param, MAX_SIZE_ANONNAME, "%u", anonindex++);
}
/* New Rule: never repair potential malicious user input */
if (!skip_upload) {
tmp = param;
long c = 0;
while (*tmp) {
if (*tmp == '[') {
c++;
} else if (*tmp == ']') {
c--;
if (tmp[1] && tmp[1] != '[') {
skip_upload = 1;
break;
}
}
if (c < 0) {
skip_upload = 1;
break;
}
tmp++;
}
}
total_bytes = cancel_upload = 0;
if (!skip_upload) {
/* Handle file */
char path[PATH_MAX];
// open a temporary file
snprintf(path, sizeof(path), "%s/XXXXXX",
RuntimeOption::UploadTmpDir.c_str());
fd = mkstemp(path);
upload_count--;
if (fd == -1) {
Logger::Warning("Unable to open temporary file");
Logger::Warning("File upload error - unable to create a "
"temporary file");
cancel_upload = UPLOAD_ERROR_E;
}
temp_filename = path;
}
if (!skip_upload && php_rfc1867_callback != nullptr) {
multipart_event_file_start event_file_start;
event_file_start.post_bytes_processed = mbuff->read_post_bytes;
event_file_start.name = param;
event_file_start.filename = &filename;
if (php_rfc1867_callback(&s_rfc1867_data->rfc1867ApcData,
MULTIPART_EVENT_FILE_START,
&event_file_start,
&event_extra_data) == FAILURE) {
if (!temp_filename.empty()) {
if (cancel_upload != UPLOAD_ERROR_E) { /* file creation failed */
close(fd);
unlink(temp_filename.c_str());
}
}
temp_filename="";
free(param);
free(filename);
continue;
}
}
if (skip_upload) {
free(param);
free(filename);
continue;
}
if (strlen(filename) == 0) {
Logger::Verbose("No file uploaded");
cancel_upload = UPLOAD_ERROR_D;
}
offset = 0;
end = 0;
while (!cancel_upload &&
(blen = multipart_buffer_read(mbuff, buff, sizeof(buff), &end)))
{
if (php_rfc1867_callback != nullptr) {
multipart_event_file_data event_file_data;
event_file_data.post_bytes_processed = mbuff->read_post_bytes;
event_file_data.offset = offset;
event_file_data.data = buff;
event_file_data.length = blen;
event_file_data.newlength = &blen;
if (php_rfc1867_callback(&s_rfc1867_data->rfc1867ApcData,
MULTIPART_EVENT_FILE_DATA,
&event_file_data,
&event_extra_data) == FAILURE) {
cancel_upload = UPLOAD_ERROR_X;
continue;
}
}
if (VirtualHost::GetUploadMaxFileSize() > 0 &&
total_bytes > VirtualHost::GetUploadMaxFileSize()) {
Logger::Verbose("upload_max_filesize of %" PRId64 " bytes exceeded "
"- file [%s=%s] not saved",
VirtualHost::GetUploadMaxFileSize(),
param, filename);
cancel_upload = UPLOAD_ERROR_A;
} else if (max_file_size && (total_bytes > max_file_size)) {
Logger::Verbose("MAX_FILE_SIZE of %d bytes exceeded - "
"file [%s=%s] not saved",
max_file_size, param, filename);
cancel_upload = UPLOAD_ERROR_B;
} else if (blen > 0) {
wlen = folly::writeFull(fd, buff, blen);
if (wlen < blen) {
Logger::Verbose("Only %zd bytes were written, expected to "
"write %zd", wlen, blen);
cancel_upload = UPLOAD_ERROR_F;
} else {
total_bytes += wlen;
}
offset += wlen;
}
}
if (fd!=-1) { /* may not be initialized if file could not be created */
close(fd);
}
if (!cancel_upload && !end) {
Logger::Verbose("Missing mime boundary at the end of the data for "
"file %s", strlen(filename) > 0 ? filename : "");
cancel_upload = UPLOAD_ERROR_C;
}
if (strlen(filename) > 0 && total_bytes == 0 && !cancel_upload) {
Logger::Verbose("Uploaded file size 0 - file [%s=%s] not saved",
param, filename);
cancel_upload = 5;
}
if (php_rfc1867_callback != nullptr) {
multipart_event_file_end event_file_end;
event_file_end.post_bytes_processed = mbuff->read_post_bytes;
event_file_end.temp_filename = temp_filename.c_str();
event_file_end.cancel_upload = cancel_upload;
if (php_rfc1867_callback(&s_rfc1867_data->rfc1867ApcData,
MULTIPART_EVENT_FILE_END,
&event_file_end,
&event_extra_data) == FAILURE) {
cancel_upload = UPLOAD_ERROR_X;
}
}
if (cancel_upload && cancel_upload != UPLOAD_ERROR_C) {
if (!temp_filename.empty()) {
if (cancel_upload != UPLOAD_ERROR_E) { /* file creation failed */
unlink(temp_filename.c_str());
}
}
temp_filename="";
} else {
s_rfc1867_data->rfc1867UploadedFiles.insert(temp_filename);
}
/* is_arr_upload is true when name of file upload field
* ends in [.*]
* start_arr is set to point to 1st [
*/
is_arr_upload = (start_arr = strchr(param,'[')) &&
(param[strlen(param)-1] == ']');
if (is_arr_upload) {
array_len = strlen(start_arr);
array_index = std::string(start_arr+1, array_len-2);
}
/* Add $foo_name */
if (llen < strlen(param) + MAX_SIZE_OF_INDEX + 1) {
llen = strlen(param);
lbuf = (char *) realloc(lbuf, llen + MAX_SIZE_OF_INDEX + 1);
llen += MAX_SIZE_OF_INDEX + 1;
}
if (is_arr_upload) {
abuf = std::string(param, strlen(param)-array_len);
snprintf(lbuf, llen, "%s_name[%s]",
abuf.c_str(), array_index.c_str());
} else {
snprintf(lbuf, llen, "%s_name", param);
}
/* The \ check should technically be needed for win32 systems only
* where it is a valid path separator. However, IE in all it's wisdom
* always sends the full path of the file on the user's filesystem,
* which means that unless the user does basename() they get a bogus
* file name. Until IE's user base drops to nill or problem is fixed
* this code must remain enabled for all systems.
*/
s = strrchr(filename, '\\');
if ((tmp = strrchr(filename, '/')) > s) {
s = tmp;
}
/* Add $foo[name] */
if (is_arr_upload) {
snprintf(lbuf, llen, "%s[name][%s]",
abuf.c_str(), array_index.c_str());
} else {
snprintf(lbuf, llen, "%s[name]", param);
}
if (s) {
String val(s+1, strlen(s+1), CopyString);
safe_php_register_variable(lbuf, val, files, 0);
} else {
String val(filename, strlen(filename), CopyString);
safe_php_register_variable(lbuf, val, files, 0);
}
free(filename);
s = nullptr;
/* Possible Content-Type: */
if ((cancel_upload && cancel_upload != UPLOAD_ERROR_C) ||
!(cd = php_mime_get_hdr_value(header, "Content-Type"))) {
cd = "";
} else {
/* fix for Opera 6.01 */
s = strchr(cd, ';');
if (s != nullptr) {
*s = '\0';
}
}
/* Add $foo[type] */
if (is_arr_upload) {
snprintf(lbuf, llen, "%s[type][%s]",
abuf.c_str(), array_index.c_str());
} else {
snprintf(lbuf, llen, "%s[type]", param);
}
String val(cd, strlen(cd), CopyString);
safe_php_register_variable(lbuf, val, files, 0);
/* Restore Content-Type Header */
if (s != nullptr) {
*s = ';';
}
s = "";
/* Initialize variables */
add_protected_variable(param);
Variant tempFileName(temp_filename);
/* Add $foo[tmp_name] */
if (is_arr_upload) {
snprintf(lbuf, llen, "%s[tmp_name][%s]",
abuf.c_str(), array_index.c_str());
} else {
snprintf(lbuf, llen, "%s[tmp_name]", param);
}
add_protected_variable(lbuf);
safe_php_register_variable(lbuf, tempFileName, files, 1);
Variant file_size, error_type;
error_type = cancel_upload;
/* Add $foo[error] */
if (cancel_upload) {
file_size = 0;
} else {
file_size = total_bytes;
}
if (is_arr_upload) {
snprintf(lbuf, llen, "%s[error][%s]",
abuf.c_str(), array_index.c_str());
} else {
snprintf(lbuf, llen, "%s[error]", param);
}
safe_php_register_variable(lbuf, error_type, files, 0);
/* Add $foo[size] */
if (is_arr_upload) {
snprintf(lbuf, llen, "%s[size][%s]",
abuf.c_str(), array_index.c_str());
} else {
snprintf(lbuf, llen, "%s[size]", param);
}
safe_php_register_variable(lbuf, file_size, files, 0);
free(param);
}
}
fileupload_done:
data = mbuff->post_data;
size = mbuff->post_size;
if (php_rfc1867_callback != nullptr) {
multipart_event_end event_end;
event_end.post_bytes_processed = mbuff->read_post_bytes;
php_rfc1867_callback(&s_rfc1867_data->rfc1867ApcData,
MULTIPART_EVENT_END, &event_end, &event_extra_data);
}
if (lbuf) free(lbuf);
s_rfc1867_data->rfc1867ProtectedVariables.clear();
if (mbuff->boundary_next) free(mbuff->boundary_next);
if (mbuff->boundary) free(mbuff->boundary);
if (mbuff->buffer) free(mbuff->buffer);
if (mbuff) free(mbuff);
}
///////////////////////////////////////////////////////////////////////////////
}