All Products
Search
Document Center

Object Storage Service:How do I restrict file types and size when I upload objects to OSS?

Last Updated:Dec 12, 2023

If you do not restrict the types and size of files that can be uploaded to an Object Storage Service (OSS) bucket, the bucket may contain large objects or malicious objects uploaded by users. This can cause unexpected storage usage and bandwidth consumption, and in some cases, bucket domain blocking. OSS does not have a separate feature that you can use to directly limit file types and size in uploads. However, you can specify limits on file types and size when you generate signatures on the server or write your restriction logic on the client side.

Generate a signature and a policy for a POST request on the server side

In scenarios in which you want to limit the attributes of an object that you want to upload, you can obtain information, such as a signature and policy that are required to call the PostObject operation, from the application server. Then, the client can use the obtained information to upload the object without using OSS SDKs. You can use the policy generated by the server to limit the attributes of an object that you want to upload, such as the object size and type. This solution is suitable for uploading objects by using HTML forms. This solution does not support multipart upload and resumable upload. For more information, see PostObject.

Sample code

Sample code for the application server

The following sample code provides an example on how to obtain a signature and policy from the application server:

import os
from hashlib import sha1 as sha
import json
import base64
import hmac
import datetime
import time

# Configure the OSS_ACCESS_KEY_ID environment variable. 
access_key_id = os.environ.get('OSS_ACCESS_KEY_ID')
# Configure the OSS_ACCESS_KEY_SECRET environment variable. 
access_key_secret = os.environ.get('OSS_ACCESS_KEY_SECRET')
# Replace <your-bucket> with the name of the bucket. 
bucket = '<your-bucket>'
# Set host to a value that is in the bucketname.endpoint format. Replace <your-bucket> with the name of the bucket. Replace <your-endpoint> with the OSS endpoint. Example: oss-cn-hangzhou.aliyuncs.com. 
host = 'https://<your-bucket>.<your-endpoint>'
# Specify the prefix of the object that you want to upload to OSS. 
upload_dir = 'user-dir-prefix/'
# Specify the validity period of the signature and the policy. Unit: seconds. 
expire_time = 3600


def generate_expiration(seconds):
    """
    The expiration time is calculated based on the specified validity period. Unit: seconds. 
    :param seconds: validity period (seconds). 
    :return: time string in the ISO 8601 standard. Example: 2014-12-01T12:00:00.000Z. 
    """
    now = int(time.time())
    expiration_time = now + seconds
    gmt = datetime.datetime.utcfromtimestamp(expiration_time).isoformat()
    gmt += 'Z'
    return gmt


def generate_signature(access_key_secret, expiration, conditions, policy_extra_props=None):
    """
    Generate a signature string. 
    :param access_key_secret: the AccessKey secret that has the permissions to access the bucket. 
    :param expiration: the time when the signature expires. Specify the time in the ISO 8601 standard in the yyyy-MM-ddTHH:mm:ssZ format. Example: 2014-12-01T12:00:00.000Z. 
    :param conditions: the policy conditions that can be used to limit the values that you can set during form upload. 
    :param policy_extra_props: the additional policy parameters. If additional parameters are supported by the policy, you can pass additional parameters by using dict. 
    :return: the signature string. 
    """
    policy_dict = {
        'expiration': expiration,
        'conditions': conditions
    }
    if policy_extra_props is not None:
        policy_dict.update(policy_extra_props)
    policy = json.dumps(policy_dict).strip()
    policy_encode = base64.b64encode(policy.encode())
    h = hmac.new(access_key_secret.encode(), policy_encode, sha)
    sign_result = base64.b64encode(h.digest()).strip()
    return sign_result.decode()

def generate_upload_params():
    policy = {
        # The validity period of the policy. 
        "expiration": generate_expiration(expire_time),
        # Specify the conditions in the policy field. 
        "conditions": [
            # If success_action_redirect is not specified, the HTTP status code returned after a successful upload is 204. 
            ["eq", "$success_action_status", "200"],
            # The value of the form field must start with the specified prefix. For example, if the value of the key form field starts with user/user1, the condition is ["starts-with", "$key", "user/user1"]. 
            ["starts-with", "$key", upload_dir],
            # Specify the minimum and maximum sizes of the object that you want to upload. Unit: bytes. 
            ["content-length-range", 1, 1000000],
            # Set the type of the object that you want to upload to the specified image type.
            ["in", "$content-type", ["image/jpg", "image/png"]]
        ]
    }
    signature = generate_signature(access_key_secret, policy.get('expiration'), policy.get('conditions'))
    response = {
        'policy': base64.b64encode(json.dumps(policy).encode('utf-8')).decode(),
        'ossAccessKeyId': access_key_id,
        'signature': signature,
        'host': host,
        'dir': upload_dir
        # Specify additional parameters.
    }
    return json.dumps(response)

Sample code for the client

The following sample code provides an example on how to use the signature and policy to upload an object to OSS from a web client:

const form = document.querySelector('form');
const fileInput = document.querySelector('#file');
form.addEventListener('submit', (event) => {
  event.preventDefault();
  let file = fileInput.files[0];
  let filename = fileInput.files[0].name;
  fetch('/get_post_signature_for_oss_upload', { method: 'GET' })
    .then(response => response.json())
    .then(data => {
      const formData = new FormData();
      formData.append('name',filename);
      formData.append('policy', data.policy);
      formData.append('OSSAccessKeyId', data.ossAccessKeyId);
      formData.append('success_action_status', '200');
      formData.append('signature', data.signature);
      formData.append('key', data.dir + filename);
      // file must be the last form field. No order is required for other form fields. 
      formData.append('file', file);
      fetch(data.host, { method: 'POST', body: formData },).then((res) => {
        console.log(res);
        alert ('Object Uploaded');
      });
    })
    .catch(error => {
      console.log('Error occurred while getting OSS upload parameters:', error);
    });
});

Generate a signed URL on the server side

The content-length-range field is not supported for URL signing. Therefore, you cannot use this method to limit the size of a file to upload. If you want to limit uploads to specific file types, you can specify the Content-Type header when you configure the server to generate a singed URL. When a client uses the signed URL to upload a file, the file must be in one of the specified formats. For more information, see Add signatures to URLs.

Sample code

Sample code for the application server

The following sample code provides an example on how to obtain a signed URL from the application server:

import oss2
from oss2.credentials import EnvironmentVariableCredentialsProvider

# Obtain access credentials from environment variables. Before you run the sample code, make sure that the OSS_ACCESS_KEY_ID and OSS_ACCESS_KEY_SECRET environment variables are configured. 
auth = oss2.ProviderAuth(EnvironmentVariableCredentialsProvider())
# Replace <your-endpoint> with the endpoint of the region in which the bucket is located. For example, if the bucket is located in the China (Hangzhou) region, set the endpoint to https://oss-cn-hangzhou.aliyuncs.com. 
# Replace <your-bucket> with the name of the bucket. 
bucket = oss2.Bucket(auth, '<your-endpoint>', '<your-bucket>')
# Specify the validity period. Unit: seconds. 
expire_time = 3600
# Specify the full path of the object. Example: exampledir/exampleobject.png. Do not include the bucket name in the full path. 
object_name = 'exampledir/exampleobject.png'

def generate_presigned_url():
    # Specify headers. 
    headers = dict()
    # Specify the Content-Type header. 
    headers['Content-Type'] = 'image/png'
    # Specify the storage class of the object. 
    # headers["x-oss-storage-class"] = "Standard"
    # By default, OSS identifies the forward slashes (/) in the full path of an object as escape characters when a signed URL is generated. Therefore, the signed URL cannot be used directly. 
    # Set the slash_safe parameter to True. This way, OSS does not identify the forward slashes (/) in the full path of the object as escape characters. Then, you can use the generated signed URL to upload the object. 
    url = bucket.sign_url('PUT', object_name, expire_time, slash_safe=True, headers=headers)
    return url

Sample code for the client

The following sample code provides an example on how to use the signed URL to upload an object to OSS from a web client:

const form = document.querySelector("form");
form.addEventListener("submit", (event) => {
  event.preventDefault();
  const fileInput = document.querySelector("#file");
  const file = fileInput.files[0];
  fetch(`/get_presigned_url_for_oss_upload?filename=${file.name}`, { method: "GET" })
    .then((response) => {
        return response.text();
     })
    .then((url) => {
      fetch(url, {
        method: "PUT",
        headers: new Headers({
          'Content-Type': 'image/png',
        }),
        body: file,
       }).then((res) => {
            console.log(res);
            alert ('Object Uploaded');
       });
   });
});

Client-side restriction implementation

You can use JavaScript code on the client side to allow uploads of files that meet specific file attribute requirements. To restrict uploads to specific file types and size, you can use conditional statements to check whether a file to upload meets the specified requirements. If yes, the upload is allowed. If no, the upload is denied and a warning or an error message is returned. Take note that client-side restriction implementation does not take effect for uploads that use temporary access credentials provided by Security Token Service (STS). If temporary access credentials are leaked, malicious users may bypass the client-side restrictions and upload malicious content to your OSS bucket.

Sample code

Client-side restriction

The following sample code uses the files attribute and accept attribute in the <input type="file"> element to check whether the file size and file type meet the requirements:

<!DOCTYPE html>
<html>
<head>
  <title>File upload</title>
  <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
  <input type="file" id="file-upload" accept="image/jpeg, image/png, application/pdf" />
  <button onclick="uploadFile()">Upload</button>

  <script>
    function uploadFile() {
      const fileInput = document.getElementById('file-upload');
      const file = fileInput.files[0];

      // Specify the maximum file size allowed. Unit: bytes.
      const maxFileSize = 1024 * 1024; // 1MB

      // Specify the allowed file types.
      const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];

      if (file) {
        // Specify the file size.
        if (file.size > maxFileSize) {
          alert('File size exceeds the limit. The size of the file to upload must be less than 1 MB. ');
          return;
        }

        // Check the file type.
        if (!allowedTypes.includes(file.type)) {
          alert ('Unsupported file type. Allowed file types are JPEG, PNG, and PDF. ');
          return;
        }

        // The file meets the upload requirements and can be uploaded.
        // Write your upload logic.
        console.log('Upload file:', file.name);
        // Write your upload implementation code.
      } else {
        alert('Select a file to upload. ');
      }
    }
  </script>
</body>
</html>