PHP file upload source analysis of RFC1867

  • 2020-03-31 16:46:24
  • OfStack

You don't want to tell the user "open FTP client, upload file to //www.jb51.net/uploads/ and name it 2dk433423l.jpg" when they want to upload avatar, do you?

However, the uploading based on HTTP is relatively easier to use and more secure than FTP. There are three kinds of uploading methods: PUT, WEBDAV, and RFC1867. This paper will analyze how to upload files based on RFC1867 in PHP.

RFC1867

RCF1867 is a standard protocol for form-based File Upload in HTML. RFC1867 standard makes two modifications to HTML:

1 adds a file option for the type attribute of the input element.
2 the input tag can have an accept attribute that specifies a list of file types or file formats that can be uploaded.


In addition, this standard also defines a new mime type: multipart/form-data, and when working with an enctype= "multipart/form-data" and/or containing < Input type = "file" > The tag of the form when the behavior should be taken.

For example, when HTML wants a user to be able to upload one or more files, he can write:

< Form enctype = "multipart/form - the data" action = "upload. PHP method = post>"
Select file:
< Input the name = "userfile" type = "file" >
File description:
< Input the name = "description" type = "text" >
< Input type="submit" value=" upload ">
< / form>

< Form enctype = "multipart/form - the data" action = "upload. PHP method = post>"
< Input type = "hidden" value = "5000" name = "MAX_FILE_SIZE >" < ! -- file size -->
Select file:
< Input the name = "userfile" type = "file" >
File description:
< Input the name = "description" type = "text" >
< Input type="submit" value=" upload ">
< / form>

Regardless of how unreliable the MAX_FILE_SIZE is (so browser-based controls are), I'll show you how the MAX_FILE_SIZE works in terms of implementation alone.

What happens when the user selects a file (laruence. TXT), fills in the file description (" personal profile of laruence "), and clicks upload?

The form submission

After the user confirms the submission, the browser sends the following packet in a similar format to the page specified by the action attribute in the form (in this case, upload.php):

/ / request header
POST/upload. HTTP / 1.0 PHP \ r \ n
.
Host: www.laruence.com\r\n
.
The Content - length: XXXXX \ r \ n
.
The content-type: multipart/form - data, boundary = -- -- -- -- -- -- -- -- -- -- -- -- -- -- 7 d51863950254 \ r \ n
. \ r \ n \ r \ n
// start POST data content
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 7 d51863950254
The content - disposition: the form - data; Name = "description"
Personal introduction to laruence
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 7 d51863950254
The content - disposition: the form - data; Name = "userfile"; Filename = "laruence. TXT"
The content-type: text/plain
. Laruence. TXT content...
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 7 d51863950254

The next step is how the server handles the data.

Accept the upload

When the Web server, assumed here to be Apache(and also assumed that PHP is installed on Apache as a module), receives the user's data, it first determines that the MIME TYPE is PHP, based on the HTTP request header, and then, after some process (see my previous PHP Life Cycle PPT for this part), eventually gives control to the PHP module.

At this point, PHP calls sapi_activate to initiate a request. In this process, the request type is first determined, which is POST, so sapi_read_post_data is called. By content-type, the handler function rfc1867_post_handler is found and the handler is called to analyze the data from the POST.

The source code for the rfc1867_post_handler section can be found in mian/rfc1867.c, as well as the source code listed in my previous in-depth PHP file upload.

Then, through the boundary, PHP passed the check for each section to see whether the following is defined at the same time:

Name and filename properties (named file upload)
Not defined name defines filename(nameless upload)
Defines name without defining filename,

So we can do different things.

If ((CD = php_mime_get_hdr_value(header, "content-disposition ")) {
Char * pair = NULL;
Int the end = 0;

While (isspace (* CD)) {
+ + CD;
}

While (* CD && (pair = php_ap_getword(& CD, '; ')))
{
Char *key=NULL, *word = pair;

While (isspace (* CD)) {
+ + CD;
}

If (STRCHR (pair, '=')) {
Key = php_ap_getword (& pair, '=');

If (! The strcasecmp (key, "name")) {
// gets the name field
If (param) {
Efree (param);
}
Param = php_ap_getword_conf (& pair TSRMLS_CC);
} else if (! The strcasecmp (key, "filename")) {
// gets the filename field
If (filename) {
Efree (filename);
}
Filename = php_ap_getword_conf (& pair TSRMLS_CC);
}
}
{if (key)
Efree (key);
}
Efree (word);
}

In this process, PHP checks for MAX_FILE_SIZE in normal data.


If (! Filename && param) {
Unsigned int value_len;
Char *value = multipart_buffer_read_body(mbuff, &value_len TSRMLS_CC);
Unsigned int new_val_len;
.

If (! The strcasecmp (param, "MAX_FILE_SIZE")) {
Max_file_size = atol (value);
}

Efree (param);
Efree (value);
The continue;
}

If there is, the file size is checked for excess by its value.

If (PG (upload_max_filesize) > 0 && total_bytes > PG (upload_max_filesize)) {
Cancel_upload = UPLOAD_ERROR_A;
} else if (max_file_size && (total_bytes > Max_file_size)) {
# if DEBUG_FILE_UPLOAD
Sapi_module. Sapi_error (E_NOTICE,
"MAX_FILE_SIZE of % ld bytes exceeded - file [% s] = % s not saved".
Max_file_size, param, filename);
# endif
Cancel_upload = UPLOAD_ERROR_B;
}

From the above code, we can also see that the judgment is divided into two parts, the first part is to check the default upload limit of PHP, the second part is to check the user-defined MAX_FILE_SIZE, so the MAX_FILE_SIZE defined in the form cannot exceed the maximum upload file size set in PHP.

Judging by name and filename, if it is a file upload, a temporary file with a random name is created in the file upload directory according to PHP Settings:

If (! Skip_upload) {

Fd = php_open_temporary_fd_ex (PG (upload_tmp_dir),
"PHP", & temp_filename, 1 TSRMLS_CC);
If (fd = = 1) {
Sapi_module. Sapi_error (E_WARNING,
"File upload error-unable to create a temporary File ");
Cancel_upload = UPLOAD_ERROR_E;
}
}

Returns a file handle, and a temporary random file name.

After that, there will be some validation, such as filename legal, name legal, and so on.

If all of these pass, the contents are read in and written to the temporary file.

.
Else if (blen > 0) {
Wlen = write(fd, buff, blen); // write to temporary file.
If (wlen == -1) {

# if DEBUG_FILE_UPLOAD
Sapi_module. sapi_error(E_NOTICE, "write() failed -% s", strerror(errno));
# endif
Cancel_upload = UPLOAD_ERROR_F;
}
}
.

When the loop is complete, close the temporary file handle.

(rfc1867_uploaded_files zend_hash_add (SG), temp_filename,
Strlen (temp_filename) + 1, &temp_filename, sizeof(char *), NULL);

And generate the FILE variable. At this time, if it is a named upload, it will set:

$_FILES [' userfile '] / / name = "userfile"

If the upload is anonymous, tmp_name will be used to set:

$_FILES['tmp_name'] // upload with no name

Finally, the user wrote the upload.php processing.


Related articles: