pub fn store_file_impl<'a>(
    conn: &'a mut storage::DatabaseConnection,
    room: &'a Room,
    user: &User,
    auth: AuthorizationRequired,
    data_b64: &str,
    filename: Option<&str>,
    expires: bool,
) -> Result<FileUpload<'a>, Rejection> {
    // Determine the file size from the base64 data without decoding it (we'll do that later
    // directly to the destination file).
    let mut bytes: usize = data_b64.len() / 4 * 3;
    match data_b64.len() % 4 {
        0 => {
            // Even multiple of 4, but we might have padding:
            if data_b64.ends_with('=') {
                // Every 3 bytes of data becomes 4 bytes in base64.  If the end is a 4 byte value
                // with padding then two padding chars means it was created from a single byte (and
                // we're using 6+2 bits in the first two significant chars), and one padding char
                // means it was created from two bytes (using 6+6+4 bytes of encoded significance).
                bytes -= if data_b64.ends_with("==") { 2 } else { 1 };
            }
        }
        // Input of size 3n+{1,2} will produce 4n+{2,3} (unpadded) bytes:
        2 => {
            bytes += 1;
        }
        3 => {
            bytes += 2;
        }
        // This is just invalid base64, so bail now:
        _ => {
            error!("Invalid file data: data is not properly base64 encoded");
            return Err(Error::ValidationFailed.into());
        }
    };

    let files_dir = format!("uploads/{}", room.token);
    if let Err(e) = std::fs::create_dir_all(&files_dir) {
        error!("Unable to mkdir {} for room file storage: {}", files_dir, e);
        return Err(Error::DatabaseFailedInternally.into());
    }

    let mut upload = FileUpload::new(storage::get_transaction(conn)?, &room);

    require_authorization(&upload.tx.as_ref().unwrap(), &user, &room, auth)?;

    let db_filename: Option<String> =
        filename.map(|f| UPLOAD_FILENAME_BAD.replace_all(f, "_").into());
    let expiry: Option<f64> = if expires {
        Some(
            (SystemTime::now() + UPLOAD_DEFAULT_EXPIRY)
                .duration_since(SystemTime::UNIX_EPOCH)
                .unwrap()
                .as_secs_f64(),
        )
    } else {
        None
    };
    upload.id = match upload
        .tx
        .as_ref()
        .unwrap()
        .prepare_cached(
            "INSERT INTO files (room, uploader, size, expiry, filename, path) \
             VALUES (?, ?, ?, ?, ?, 'tmp') \
             RETURNING id",
        )
        .map_err(db_error)?
        .query_row(params![room.id, user.id, bytes, db_filename, expiry], |row| row.get(0))
    {
        Ok(r) => r,
        Err(e) => {
            error!("Couldn't insert file row: {}.", e);
            return Err(Error::DatabaseFailedInternally.into());
        }
    };

    let mut fs_filename: String = db_filename.unwrap_or("(unnamed)".to_string());

    assert!(UPLOAD_FILENAME_KEEP_PREFIX + 3 + UPLOAD_FILENAME_KEEP_SUFFIX <= UPLOAD_FILENAME_MAX);
    if fs_filename.len() > UPLOAD_FILENAME_MAX {
        fs_filename.replace_range(
            UPLOAD_FILENAME_KEEP_PREFIX..fs_filename.len() - UPLOAD_FILENAME_KEEP_SUFFIX,
            "...",
        );
    }
    fs_filename = format!("{}/{}_{}", files_dir, upload.id, fs_filename);
    if let Err(e) = upload
        .prepare_cached("UPDATE files SET path = ? WHERE id = ?")
        .map_err(db_error)?
        .execute(params![fs_filename, upload.id])
    {
        error!("Unable to update stored path to '{}': {}", fs_filename, e);
        return Err(Error::DatabaseFailedInternally.into());
    }

    // Write to file
    let path = Path::new(&fs_filename);
    // TODO: this could possibly be done async, but that seems dangerous while we hold an open
    // transaction here that could cause other threads to block (and thus deadlock).
    let mut file = match std::fs::File::create(path) {
        Ok(file) => file,
        Err(e) => {
            error!("Couldn't open file '{}': {}.", fs_filename, e);
            return Err(Error::DatabaseFailedInternally.into());
        }
    };
    upload.path = fs_filename;

    // Decode 65535 of output data at a time (we can't do 65536 because we need to read off the
    // base64 in groups of 4 chars = groups of 3 bytes, and no power of 2 is divisible by 3).
    let mut buf = Vec::<u8>::new();
    let mut pos: usize = 0;
    while pos < data_b64.len() {
        let end = (pos + 87380).min(data_b64.len());
        if let Err(e) =
            base64::decode_config_buf(&data_b64.as_bytes()[pos..end], base64::STANDARD, &mut buf)
        {
            warn!("Invalid upload data: base64 decoding failed: {}", e);
            return Err(Error::ValidationFailed.into());
        }
        if let Err(e) = file.write_all(&buf) {
            error!("Writing to file {} failed: {}", upload.path, e);
            return Err(Error::DatabaseFailedInternally.into());
        }
        pos = end;
        buf.clear();
    }

    Ok(upload)
}
/// Does the actual work involved in storing a file, inserting into the database, etc.
///
/// Returns a FileUpload on success.  The caller may optionally use this to perform additional
/// actions, but *must* call `.commit()` on success -- if dropped the FileUpload will clean up the
/// temporary file and drop the transaction inserting the records.
Does the actual work involved in storing a file, inserting into the database, etc.
Returns a FileUpload on success.  The caller may optionally use this to perform additional
actions, but *must* call `.commit()` on success -- if dropped the FileUpload will clean up the
temporary file and drop the transaction inserting the records.
Does the actual work involved in storing a file inserting into the database etc . Returns a FileUpload on success . The caller may optionally use this to perform additional actions but * must * call ` . commit () ` on success -- if dropped the FileUpload will clean up the temporary file and drop the transaction inserting the records .
pub fn store_file_impl<'a>(
    conn: &'a mut storage::DatabaseConnection,
    room: &'a Room,
    user: &User,
    auth: AuthorizationRequired,
    data_b64: &str,
    filename: Option<&str>,
    expires: bool,
) -> Result<FileUpload<'a>, Rejection> {
    // Determine the file size from the base64 data without decoding it (we'll do that later
    // directly to the destination file).
    let mut bytes: usize = data_b64.len() / 4 * 3;
    match data_b64.len() % 4 {
        0 => {
            // Even multiple of 4, but we might have padding:
            if data_b64.ends_with('=') {
                // Every 3 bytes of data becomes 4 bytes in base64.  If the end is a 4 byte value
                // with padding then two padding chars means it was created from a single byte (and
                // we're using 6+2 bits in the first two significant chars), and one padding char
                // means it was created from two bytes (using 6+6+4 bytes of encoded significance).
                bytes -= if data_b64.ends_with("==") { 2 } else { 1 };
            }
        }
        // Input of size 3n+{1,2} will produce 4n+{2,3} (unpadded) bytes:
        2 => {
            bytes += 1;
        }
        3 => {
            bytes += 2;
        }
        // This is just invalid base64, so bail now:
        _ => {
            error!("Invalid file data: data is not properly base64 encoded");
            return Err(Error::ValidationFailed.into());
        }
    };
    let files_dir = format!("uploads/{}", room.token);
    if let Err(e) = std::fs::create_dir_all(&files_dir) {
        error!("Unable to mkdir {} for room file storage: {}", files_dir, e);
        return Err(Error::DatabaseFailedInternally.into());
    }
    let mut upload = FileUpload::new(storage::get_transaction(conn)?, &room);
    require_authorization(&upload.tx.as_ref().unwrap(), &user, &room, auth)?;
    let db_filename: Option<String> =
        filename.map(|f| UPLOAD_FILENAME_BAD.replace_all(f, "_").into());
    let expiry: Option<f64> = if expires {
        Some(
            (SystemTime::now() + UPLOAD_DEFAULT_EXPIRY)
                .duration_since(SystemTime::UNIX_EPOCH)
                .unwrap()
                .as_secs_f64(),
        )
    } else {
        None
    };
    upload.id = match upload
        .tx
        .as_ref()
        .unwrap()
        .prepare_cached(
            "INSERT INTO files (room, uploader, size, expiry, filename, path) \
             VALUES (?, ?, ?, ?, ?, 'tmp') \
             RETURNING id",
        )
        .map_err(db_error)?
        .query_row(params![room.id, user.id, bytes, db_filename, expiry], |row| row.get(0))
    {
        Ok(r) => r,
        Err(e) => {
            error!("Couldn't insert file row: {}.", e);
            return Err(Error::DatabaseFailedInternally.into());
        }
    };
    let mut fs_filename: String = db_filename.unwrap_or("(unnamed)".to_string());
    assert!(UPLOAD_FILENAME_KEEP_PREFIX + 3 + UPLOAD_FILENAME_KEEP_SUFFIX <= UPLOAD_FILENAME_MAX);
    if fs_filename.len() > UPLOAD_FILENAME_MAX {
        fs_filename.replace_range(
            UPLOAD_FILENAME_KEEP_PREFIX..fs_filename.len() - UPLOAD_FILENAME_KEEP_SUFFIX,
            "...",
        );
    }
    fs_filename = format!("{}/{}_{}", files_dir, upload.id, fs_filename);
    if let Err(e) = upload
        .prepare_cached("UPDATE files SET path = ? WHERE id = ?")
        .map_err(db_error)?
        .execute(params![fs_filename, upload.id])
    {
        error!("Unable to update stored path to '{}': {}", fs_filename, e);
        return Err(Error::DatabaseFailedInternally.into());
    }
    // Write to file
    let path = Path::new(&fs_filename);
    // TODO: this could possibly be done async, but that seems dangerous while we hold an open
    // transaction here that could cause other threads to block (and thus deadlock).
    let mut file = match std::fs::File::create(path) {
        Ok(file) => file,
        Err(e) => {
            error!("Couldn't open file '{}': {}.", fs_filename, e);
            return Err(Error::DatabaseFailedInternally.into());
        }
    };
    upload.path = fs_filename;
    // Decode 65535 of output data at a time (we can't do 65536 because we need to read off the
    // base64 in groups of 4 chars = groups of 3 bytes, and no power of 2 is divisible by 3).
    let mut buf = Vec::<u8>::new();
    let mut pos: usize = 0;
    while pos < data_b64.len() {
        let end = (pos + 87380).min(data_b64.len());
        if let Err(e) =
            base64::decode_config_buf(&data_b64.as_bytes()[pos..end], base64::STANDARD, &mut buf)
        {
            warn!("Invalid upload data: base64 decoding failed: {}", e);
            return Err(Error::ValidationFailed.into());
        }
        if let Err(e) = file.write_all(&buf) {
            error!("Writing to file {} failed: {}", upload.path, e);
            return Err(Error::DatabaseFailedInternally.into());
        }
        pos = end;
        buf.clear();
    }
    Ok(upload)
}
pub fn store_file_impl < ' a > ( conn : & ' a mut storage :: DatabaseConnection , room : & ' a Room , user : & User , auth : AuthorizationRequired , data_b64 : & str , filename : Option < & str > , expires : bool , ) -> Result < FileUpload < ' a > , Rejection > { // Determine the file size from the base64 data without decoding it (we'll do that later // directly to the destination file). let mut bytes : usize = data_b64 . len ( ) / 4 * 3 ; match data_b64 . len ( ) % 4 { 0 => { // Even multiple of 4, but we might have padding: if data_b64 . ends_with ( '=' ) { // Every 3 bytes of data becomes 4 bytes in base64.  If the end is a 4 byte value // with padding then two padding chars means it was created from a single byte (and // we're using 6+2 bits in the first two significant chars), and one padding char // means it was created from two bytes (using 6+6+4 bytes of encoded significance). bytes -= if data_b64 . ends_with ( " " ) { 2 } else { 1 } ; } } // Input of size 3n+{1,2} will produce 4n+{2,3} (unpadded) bytes: 2 => { bytes += 1 ; } 3 => { bytes += 2 ; } // This is just invalid base64, so bail now: _ => { error ! ( " " ) ; return Err ( Error :: ValidationFailed . into ( ) ) ; } } ; let files_dir = format ! ( " " room token ) ; if let Err ( e ) = std :: fs :: create_dir_all ( & files_dir ) { error ! ( " " files_dir e ) ; return Err ( Error :: DatabaseFailedInternally . into ( ) ) ; } let mut upload = FileUpload :: new ( storage :: get_transaction ( conn ) ? , & room ) ; require_authorization ( & upload . tx . as_ref ( ) . unwrap ( ) , & user , & room , auth ) ? ; let db_filename : Option < String > = filename . map ( | f | UPLOAD_FILENAME_BAD . replace_all ( f , " " ) . into ( ) ) ; let expiry : Option < f64 > = if expires { Some ( ( SystemTime :: now ( ) + UPLOAD_DEFAULT_EXPIRY ) . duration_since ( SystemTime :: UNIX_EPOCH ) . unwrap ( ) . as_secs_f64 ( ) , ) } else { None } ; upload . id = match upload . tx . as_ref ( ) . unwrap ( ) . prepare_cached ( " \
 \
 " , ) . map_err ( db_error ) ? . query_row ( params ! [ room id user id bytes db_filename expiry ] , | row | row . get ( 0 ) ) { Ok ( r ) => r , Err ( e ) => { error ! ( " " e ) ; return Err ( Error :: DatabaseFailedInternally . into ( ) ) ; } } ; let mut fs_filename : String = db_filename . unwrap_or ( " " . to_string ( ) ) ; assert ! ( UPLOAD_FILENAME_KEEP_PREFIX 3 UPLOAD_FILENAME_KEEP_SUFFIX UPLOAD_FILENAME_MAX ) ; if fs_filename . len ( ) > UPLOAD_FILENAME_MAX { fs_filename . replace_range ( UPLOAD_FILENAME_KEEP_PREFIX .. fs_filename . len ( ) - UPLOAD_FILENAME_KEEP_SUFFIX , " " , ) ; } fs_filename = format ! ( " " files_dir upload id fs_filename ) ; if let Err ( e ) = upload . prepare_cached ( " " ) . map_err ( db_error ) ? . execute ( params ! [ fs_filename upload id ] ) { error ! ( " " fs_filename e ) ; return Err ( Error :: DatabaseFailedInternally . into ( ) ) ; } // Write to file let path = Path :: new ( & fs_filename ) ; // TODO: this could possibly be done async, but that seems dangerous while we hold an open // transaction here that could cause other threads to block (and thus deadlock). let mut file = match std :: fs :: File :: create ( path ) { Ok ( file ) => file , Err ( e ) => { error ! ( " " fs_filename e ) ; return Err ( Error :: DatabaseFailedInternally . into ( ) ) ; } } ; upload . path = fs_filename ; // Decode 65535 of output data at a time (we can't do 65536 because we need to read off the // base64 in groups of 4 chars = groups of 3 bytes, and no power of 2 is divisible by 3). let mut buf = Vec :: < u8 > :: new ( ) ; let mut pos : usize = 0 ; while pos < data_b64 . len ( ) { let end = ( pos + 87380 ) . min ( data_b64 . len ( ) ) ; if let Err ( e ) = base64 :: decode_config_buf ( & data_b64 . as_bytes ( ) [ pos .. end ] , base64 :: STANDARD , & mut buf ) { warn ! ( " " e ) ; return Err ( Error :: ValidationFailed . into ( ) ) ; } if let Err ( e ) = file . write_all ( & buf ) { error ! ( " " upload path e ) ; return Err ( Error :: DatabaseFailedInternally . into ( ) ) ; } pos = end ; buf . clear ( ) ; } Ok ( upload ) }
Does the actual work involved in storing a file, inserting into the database, etc.
Does the actual work involved in storing a file inserting into the database etc .
conn
&'a mut storage::DatabaseConnection
room
&'a Room
user
&User
auth
AuthorizationRequired
data_b64
&str
filename
Option<&str>
expires
bool
conn
&'a mut storage::DatabaseConnection
room
&'a Room
user
&User
auth
AuthorizationRequired
data_b64
&str
filename
Option<&str>
expires
bool