snap-core-0.9.4.0: Snap: A Haskell Web Framework (core interfaces and types)

Safe Haskell | None

Snap.Util.FileUploads

Contents

- Functions
- Uploaded parts
  - Policy
    - General upload policy
    - Per-file upload policy
- Exceptions

Description

This module contains primitives and helper functions for handling requests with `Content-type: multipart/form-data`, i.e. HTML forms and file uploads.

Typically most users will want to use `handleFileUploads`, which writes uploaded files to a temporary directory before sending them on to a handler specified by the user.

Users who wish to handle their file uploads differently can use the lower-level streaming `Iteratee` interface called `handleMultipart`. That function takes uploaded files and streams them to an `Iteratee` consumer of the user's choosing.

Using these functions requires making "policy" decisions which Snap can't really make for users, such as "what's the largest PDF file a user is allowed to upload?" and "should we read form inputs into the parameters mapping?". Policy is specified on a "global" basis (using `UploadPolicy`), and on a per-file basis (using `PartUploadPolicy`, which allows you to reject or limit the size of certain uploaded `Content-type`s).

Synopsis

- handleFileUploads :: MonadSnap m => FilePath -> UploadPolicy -> (PartInfo -> PartUploadPolicy) -> ([(PartInfo, Either PolicyViolationException FilePath)] -> m a) -> m a
- handleMultipart :: MonadSnap m => UploadPolicy -> (PartInfo -> Iteratee ByteString IO a) -> m [a]
- data PartInfo = PartInfo {
  - partFieldName :: !ByteString
  - partFileName :: !(Maybe ByteString)
  - partContentType :: !ByteString
  }
- data UploadPolicy
- defaultUploadPolicy :: UploadPolicy
- doProcessFormInputs :: UploadPolicy -> Bool
- setProcessFormInputs :: Bool -> UploadPolicy -> UploadPolicy
- getMaximumFormInputSize :: UploadPolicy -> Int64
- setMaximumFormInputSize :: Int64 -> UploadPolicy -> UploadPolicy
- getMaximumNumberOfFormInputs :: UploadPolicy -> Int
- setMaximumNumberOfFormInputs :: Int -> UploadPolicy -> UploadPolicy
- getMinimumUploadRate :: UploadPolicy -> Double
- setMinimumUploadRate :: Double -> UploadPolicy -> UploadPolicy
- getMinimumUploadSeconds :: UploadPolicy -> Int
- setMinimumUploadSeconds :: Int -> UploadPolicy -> UploadPolicy
- getUploadTimeout :: UploadPolicy -> Int
- setUploadTimeout :: Int -> UploadPolicy -> UploadPolicy
- data PartUploadPolicy
- disallow :: PartUploadPolicy
- allowWithMaximumSize :: Int64 -> PartUploadPolicy
- data FileUploadException
- fileUploadExceptionReason :: FileUploadException -> Text
- data BadPartException
- badPartExceptionReason :: BadPartException -> Text
- data PolicyViolationException
- policyViolationExceptionReason :: PolicyViolationException -> Text

# Functions

handleFileUploads

Arguments

:: MonadSnap m |
=> FilePath | temporary directory
-> UploadPolicy | general upload policy
-> (PartInfo -> PartUploadPolicy) | per-part upload policy
-> ([(PartInfo, Either PolicyViolationException FilePath)] -> m a) | user handler (see function description)
-> m a |

Reads uploaded files into a temporary directory and calls a user handler to process them.

Given a temporary directory, global and file-specific upload policies, and a user handler, this function consumes a request body uploaded with `Content-type: multipart/form-data`. Each file is read into the temporary directory, and then a list of the uploaded files is passed to the user handler. After the user handler runs (but before the `Response` body `Enumerator` is streamed to the client), the files are deleted from disk; so if you want to retain or use the uploaded files in the generated response, you would need to move or otherwise process them.

The argument passed to the user handler is a list of:

     (PartInfo, Either PolicyViolationException FilePath)

The first half of this tuple is a `PartInfo`, which contains the information the client browser sent about the given upload part (like filename, content-type, etc). The second half of this tuple is an `Either` stipulating that either:

1. the file was rejected on a policy basis because of the provided `PartUploadPolicy` handler
2. the file was accepted and exists at the given path.

If the request's `Content-type` was not "`multipart/formdata`", this function skips processing using `pass`.

If the client's upload rate passes below the configured minimum (see `setMinimumUploadRate` and `setMinimumUploadSeconds`), this function terminates the connection. This setting is there to protect the server against slowloris-style denial of service attacks.

If the given `UploadPolicy` stipulates that you wish form inputs to be placed in the `rqParams` parameter map (using `setProcessFormInputs`), and a form input exceeds the maximum allowable size, this function will throw a `PolicyViolationException`.

If an uploaded part contains MIME headers longer than a fixed internal threshold (currently 32KB), this function will throw a `BadPartException`.

handleMultipart

Arguments

:: MonadSnap m |
=> UploadPolicy | global upload policy
-> (PartInfo -> Iteratee ByteString IO a) | part processor
-> m [a] |

Given an upload policy and a function to consume uploaded "parts", consume a request body uploaded with `Content-type: multipart/form-data`. Normally most users will want to use `handleFileUploads` (which writes uploaded files to a temporary directory and passes their names to a given handler) rather than this function; the lower-level `handleMultipart` function should be used if you want to stream uploaded files to your own iteratee function.

If the request's `Content-type` was not "`multipart/formdata`", this function skips processing using `pass`.

If the client's upload rate passes below the configured minimum (see `setMinimumUploadRate` and `setMinimumUploadSeconds`), this function terminates the connection. This setting is there to protect the server against slowloris-style denial of service attacks.

If the given `UploadPolicy` stipulates that you wish form inputs to be placed in the `rqParams` parameter map (using `setProcessFormInputs`), and a form input exceeds the maximum allowable size, this function will throw a `PolicyViolationException`.

If an uploaded part contains MIME headers longer than a fixed internal threshold (currently 32KB), this function will throw a `BadPartException`.

# Uploaded parts

data PartInfo

`PartInfo` contains information about a "part" in a request uploaded with `Content-type: multipart/form-data`.

Constructors

PartInfo |

Fields

partFieldName :: !ByteString
partFileName :: !(Maybe ByteString)
partContentType :: !ByteString

Instances

Show PartInfo |

## Policy

### General upload policy

data UploadPolicy

`UploadPolicy` controls overall policy decisions relating to `multipart/form-data` uploads, specifically:

- whether to treat parts without filenames as form input (reading them into the `rqParams` map)
- because form input is read into memory, the maximum size of a form input read in this manner, and the maximum number of form inputs
- the minimum upload rate a client must maintain before we kill the connection; if very low-bitrate uploads were allowed then a Snap server would be vulnerable to a trivial denial-of-service using a "slowloris"-type attack
- the minimum number of seconds which must elapse before we start killing uploads for having too low an upload rate.
- the amount of time we should wait before timing out the connection whenever we receive input from the client.

Instances

Eq UploadPolicy |
Show UploadPolicy |

defaultUploadPolicy :: UploadPolicy

A reasonable set of defaults for upload policy. The default policy is:

`maximum form input size`
  128kB
`maximum number of form inputs`
  10
`minimum upload rate`
  1kB/s
`seconds before rate limiting kicks in`
  10
`inactivity timeout`
  20 seconds

doProcessFormInputs :: UploadPolicy -> Bool

Does this upload policy stipulate that we want to treat parts without filenames as form input?

setProcessFormInputs :: Bool -> UploadPolicy -> UploadPolicy

Set the upload policy for treating parts without filenames as form input.

getMaximumFormInputSize :: UploadPolicy -> Int64

Get the maximum size of a form input which will be read into our `rqParams` map.

setMaximumFormInputSize :: Int64 -> UploadPolicy -> UploadPolicy

Set the maximum size of a form input which will be read into our `rqParams` map.

getMaximumNumberOfFormInputs :: UploadPolicy -> Int

Get the maximum size of a form input which will be read into our `rqParams` map.

setMaximumNumberOfFormInputs :: Int -> UploadPolicy -> UploadPolicy

Set the maximum size of a form input which will be read into our `rqParams` map.

getMinimumUploadRate :: UploadPolicy -> Double

Get the minimum rate (in bytes\second/) a client must maintain before we kill the connection.

setMinimumUploadRate :: Double -> UploadPolicy -> UploadPolicy

Set the minimum rate (in bytes\second/) a client must maintain before we kill the connection.

getMinimumUploadSeconds :: UploadPolicy -> Int

Get the amount of time which must elapse before we begin enforcing the upload rate minimum

setMinimumUploadSeconds :: Int -> UploadPolicy -> UploadPolicy

Set the amount of time which must elapse before we begin enforcing the upload rate minimum

getUploadTimeout :: UploadPolicy -> Int

Get the "upload timeout". Whenever input is received from the client, the connection timeout is set this many seconds in the future.

setUploadTimeout :: Int -> UploadPolicy -> UploadPolicy

Set the upload timeout.

### Per-file upload policy

data PartUploadPolicy

Upload policy can be set on an "general" basis (using `UploadPolicy`), but handlers can also make policy decisions on individual files/parts uploaded. For each part uploaded, handlers can decide:

- whether to allow the file upload at all
- the maximum size of uploaded files, if allowed

Instances

Eq PartUploadPolicy |
Show PartUploadPolicy |

disallow :: PartUploadPolicy

Disallows the file to be uploaded.

allowWithMaximumSize :: Int64 -> PartUploadPolicy

Allows the file to be uploaded, with maximum size n.

# Exceptions

data FileUploadException

All of the exceptions defined in this package inherit from `FileUploadException`, so if you write

     foo `catch` \(e :: FileUploadException) -> ...

you can catch a `BadPartException`, a `PolicyViolationException`, etc.

Instances

Show FileUploadException |
Typeable FileUploadException |
Exception FileUploadException |

fileUploadExceptionReason :: FileUploadException -> Text

data BadPartException

Instances

Show BadPartException |
Typeable BadPartException |
Exception BadPartException |

badPartExceptionReason :: BadPartException -> Text

data PolicyViolationException

Instances

Show PolicyViolationException |
Typeable PolicyViolationException |
Exception PolicyViolationException |

policyViolationExceptionReason :: PolicyViolationException -> Text