def parent_name(self): """ Returns the path and name of this directory's parent ('\\' or '/' if the parent is the root directory), or None if this directory is the root. """ return filepath_parent(self._name)
def publish(): src = request.args.get('src', '') embed = request.args.get('embed', '') return render_template( 'publish.html', fields=ImageAttrs.validators(), image_info=image_engine.get_image_properties(src, False), embed=embed, src=src, path=filepath_parent(src) )
def folder_browse(): from_path = request.args.get('path', '') show_files = request.args.get('show_files', '') embed = request.args.get('embed', '') msg = request.args.get('msg', '') if from_path == '': from_path = os.path.sep db_session = data_engine.db_get_session() db_committed = False try: # This also checks for path existence folder_list = get_directory_listing(from_path, True) # Auto-populate the folders database db_folder = auto_sync_folder( from_path, data_engine, task_engine, _db_session=db_session ) db_session.commit() db_committed = True # Should never happen if db_folder is None: raise DoesNotExistError(from_path) # Require view permission or file admin permissions_engine.ensure_folder_permitted( db_folder, FolderPermission.ACCESS_VIEW, get_session_user() ) return render_template( 'folder_list.html', formats=image_engine.get_image_formats(), embed=embed, msg=msg, name=filepath_filename(from_path), path=from_path, pathsep=os.path.sep, parent_path=filepath_parent(from_path), folder_list=folder_list, show_files=show_files, db_info=db_folder, db_parent_info=db_folder.parent, STATUS_ACTIVE=Folder.STATUS_ACTIVE ) except Exception as e: log_security_error(e, request) if app.config['DEBUG']: raise return render_template( 'folder_list.html', embed=embed, msg=msg, name=filepath_filename(from_path), path=from_path, err_msg='This folder cannot be viewed: ' + str(e) ) finally: try: if not db_committed: db_session.rollback() finally: db_session.close()
def image(): logger.debug(request.method + ' ' + request.url) try: logged_in = session_logged_in() allow_uncache = app.config['BENCHMARKING'] or app.config['DEBUG'] args = request.args # Get URL parameters for the image src = args.get('src', '') page = args.get('page', None) iformat = args.get('format', None) template = args.get('tmp', None) width = args.get('width', None) height = args.get('height', None) halign = args.get('halign', None) valign = args.get('valign', None) autosizefit = args.get('autosizefit', None) rotation = args.get('angle', None) flip = args.get('flip', None) top = args.get('top', None) left = args.get('left', None) bottom = args.get('bottom', None) right = args.get('right', None) autocropfit = args.get('autocropfit', None) fill = args.get('fill', None) quality = args.get('quality', None) sharpen = args.get('sharpen', None) ov_src = args.get('overlay', None) ov_size = args.get('ovsize', None) ov_opacity = args.get('ovopacity', None) ov_pos = args.get('ovpos', None) icc_profile = args.get('icc', None) icc_intent = args.get('intent', None) icc_bpc = args.get('bpc', None) colorspace = args.get('colorspace', None) strip = args.get('strip', None) dpi = args.get('dpi', None) tile = args.get('tile', None) # Get URL parameters for handling options attach = args.get('attach', None) xref = args.get('xref', None) stats = args.get('stats', None) # Get protected admin/internal parameters cache = args.get('cache', '1') if logged_in or allow_uncache else '1' recache = args.get('recache', None) if allow_uncache else None # eRez compatibility mode src = erez_params_compat(src) # Tweak strings as necessary and convert non-string parameters # to the correct data types try: # Image options if page is not None: page = parse_int(page) if iformat is not None: iformat = iformat.lower() if template is not None: template = template.lower() if width is not None: width = parse_int(width) if height is not None: height = parse_int(height) if halign is not None: halign = halign.lower() if valign is not None: valign = valign.lower() if autosizefit is not None: autosizefit = parse_boolean(autosizefit) if rotation is not None: rotation = parse_float(rotation) if flip is not None: flip = flip.lower() if top is not None: top = parse_float(top) if left is not None: left = parse_float(left) if bottom is not None: bottom = parse_float(bottom) if right is not None: right = parse_float(right) if autocropfit is not None: autocropfit = parse_boolean(autocropfit) if fill is not None: fill = parse_colour(fill) if quality is not None: quality = parse_int(quality) if sharpen is not None: sharpen = parse_int(sharpen) if ov_size is not None: ov_size = parse_float(ov_size) if ov_pos is not None: ov_pos = ov_pos.lower() if ov_opacity is not None: ov_opacity = parse_float(ov_opacity) if icc_profile is not None: icc_profile = icc_profile.lower() if icc_intent is not None: icc_intent = icc_intent.lower() if icc_bpc is not None: icc_bpc = parse_boolean(icc_bpc) if colorspace is not None: colorspace = colorspace.lower() if strip is not None: strip = parse_boolean(strip) if dpi is not None: dpi = parse_int(dpi) if tile is not None: tile = parse_tile_spec(tile) # Handling options if attach is not None: attach = parse_boolean(attach) if xref is not None: validate_string(xref, 0, 1024) if stats is not None: stats = parse_boolean(stats) # Admin/internal options if cache is not None: cache = parse_boolean(cache) if recache is not None: recache = parse_boolean(recache) except (ValueError, TypeError) as e: raise httpexc.BadRequest(unicode(e)) # Package and validate the parameters try: # #2694 Enforce public image limits - perform easy parameter checks if not logged_in: width, height, autosizefit = _public_image_limits_pre_image_checks( width, height, autosizefit, tile, template ) # Store and normalise all the parameters image_attrs = ImageAttrs(src, -1, page, iformat, template, width, height, halign, valign, rotation, flip, top, left, bottom, right, autocropfit, autosizefit, fill, quality, sharpen, ov_src, ov_size, ov_pos, ov_opacity, icc_profile, icc_intent, icc_bpc, colorspace, strip, dpi, tile) image_engine.finalise_image_attrs(image_attrs) except ValueError as e: raise httpexc.BadRequest(unicode(e)) # Get/create the database ID (from cache, validating path on create) image_id = data_engine.get_or_create_image_id( image_attrs.filename(), return_deleted=False, on_create=on_image_db_create_anon_history ) if (image_id == 0): raise DoesNotExistError() # Deleted elif (image_id < 0): raise DBError('Failed to add image to database') image_attrs.set_database_id(image_id) # Require view permission or file admin permissions_engine.ensure_folder_permitted( image_attrs.folder_path(), FolderPermission.ACCESS_VIEW, get_session_user() ) # Ditto for overlays if ov_src: permissions_engine.ensure_folder_permitted( filepath_parent(ov_src), FolderPermission.ACCESS_VIEW, get_session_user() ) # v1.17 If this is a conditional request with an ETag, see if we can just return a 304 if 'If-None-Match' in request.headers and not recache: etag_valid, modified_time = _etag_is_valid( image_attrs, request.headers['If-None-Match'], False ) if etag_valid: # Success HTTP 304 return make_304_response(image_attrs, False, modified_time) # Get the requested image data image_wrapper = image_engine.get_image( image_attrs, 'refresh' if recache else cache ) if (image_wrapper is None): raise DoesNotExistError() # #2694 Enforce public image limits - check the dimensions # of images that passed the initial parameter checks if not logged_in: try: _public_image_limits_post_image_checks( image_attrs.width(), image_attrs.height(), image_attrs.template(), image_wrapper.data(), image_wrapper.attrs().format() ) except ValueError as e: raise httpexc.BadRequest(unicode(e)) # As for the pre-check # Success HTTP 200 return make_image_response(image_wrapper, False, stats, attach, xref) except httpexc.HTTPException: # Pass through HTTP 4xx and 5xx raise except ServerTooBusyError: logger.warn(u'503 Too busy for ' + request.url) raise httpexc.ServiceUnavailable() except ImageError as e: logger.warn(u'415 Invalid image file \'' + src + '\' : ' + unicode(e)) raise httpexc.UnsupportedMediaType(unicode(e)) except SecurityError as e: if app.config['DEBUG']: raise log_security_error(e, request) raise httpexc.Forbidden() except DoesNotExistError as e: # First time around the ID will be set. Next time around it # won't but we should check whether the disk file now exists. if image_attrs.database_id() > 0 or path_exists(image_attrs.filename(), require_file=True): image_engine.reset_image(image_attrs) logger.warn(u'404 Not found: ' + unicode(e)) raise httpexc.NotFound(unicode(e)) except Exception as e: if app.config['DEBUG']: raise logger.error(u'500 Error for ' + request.url + '\n' + unicode(e)) raise httpexc.InternalServerError(unicode(e))
def folder_path(self): """ Returns the server's folder path (without filename) for this image. """ return filepath_parent(self._filename) or ''
def move_folder(db_folder, target_path, user_account, data_manager, permissions_manager, logger): """ Moves a disk folder to the given new path (which must not already exist), and updates the associated database records. The folder is effectively renamed if the parent folder path remains the same. This method may take a long time, as the folder's sub-folders and images must also be moved, both on disk and in the database. The audit trail is also updated for every affected image, image IDs cached under the old path are cleared, and folder tree permissions are re-calculated. The user account must have Delete Folder permission for the original parent folder and Create Folder permission for the target parent folder, or alternatively have the file admin system permission. This method creates and commits its own separate database connection in an attempt to keep the operation is as atomic as possible. Note however that if there is an error moving the folder tree (in the database or on disk), operations already performed are not rolled back, and the database may become out of sync with the file system. Returns the updated folder object, including all affected sub-folders. Raises a DoesNotExistError if the source folder does not exist. Raises an AlreadyExistsError if the target path already exists. Raises an IOError or OSError on error moving the disk files or folders. Raises a ValueError if the source folder or target path is invalid. Raises a DBError for database errors. Raises a SecurityError if the current user does not have sufficient permission to perform the move or if the target path is outside of IMAGES_BASE_DIR. """ db_session = data_manager.db_get_session() success = False try: _validate_path_chars(target_path) target_path = filepath_normalize(target_path) target_path = _secure_folder_path( target_path, True, app.config['ALLOW_UNICODE_FILENAMES'] ) norm_src = strip_seps(db_folder.path) norm_tgt = strip_seps(target_path) # Cannot move the root folder if norm_src == '': raise ValueError('Cannot move the root folder') # Don't allow blank path (move to become root) either if norm_tgt == '': raise ValueError('Target folder path cannot be empty') # Cannot move a folder into itself if norm_tgt.startswith(add_sep(norm_src)): raise ValueError('Cannot move a folder into itself') # Do nothing if target path is the same as the source if norm_src == norm_tgt: success = True return db_folder # Connect db_folder to our database session db_folder = data_manager.get_folder(db_folder.id, _db_session=db_session) if not db_folder: raise DoesNotExistError('Folder ID %d does not exist' % db_folder.id) # Source folder must exist ensure_path_exists(db_folder.path, require_directory=True) # Target folder must not yet exist (we cannot merge) if path_exists(target_path): raise AlreadyExistsError('Path already exists: ' + target_path) renaming = ( strip_seps(filepath_parent(db_folder.path)) == strip_seps(filepath_parent(target_path)) ) # Get parent folders for permissions checking # Target parent may not exist yet so use the closest node in the tree db_source_parent = db_folder.parent db_target_parent = _get_nearest_parent_folder( target_path, data_manager, db_session ) # Require Create Folder permission for destination folder if user_account: permissions_manager.ensure_folder_permitted( db_target_parent, FolderPermission.ACCESS_CREATE_FOLDER, user_account ) # Require Delete Folder permission for source parent folder if user_account and not renaming: permissions_manager.ensure_folder_permitted( db_source_parent, FolderPermission.ACCESS_DELETE_FOLDER, user_account ) logger.info( 'Disk folder %s is being moved to %s by %s' % (db_folder.path, target_path, user_account.username if user_account else 'System') ) # We know there's no physical target folder, but if there is an # old (deleted) db record for the target path, purge it first. db_old_target_folder = data_manager.get_folder( folder_path=target_path, _db_session=db_session ) if db_old_target_folder: # This recurses to purge files and sub-folders too data_manager.delete_folder( db_old_target_folder, purge=True, _db_session=db_session, _commit=False ) # Move the disk files first, as this is the most likely thing to fail. # Note that this might involve moving files and directories we haven't # got database entries for (but that doesn't matter). filesystem_manager.move(db_folder.path, target_path) # Prep image history if renaming: history_info = 'Folder renamed from ' + filepath_filename(db_folder.path) + \ ' to ' + filepath_filename(target_path) else: history_info = 'Folder moved from ' + db_folder.path + ' to ' + target_path # Update the database data_manager.set_folder_path( db_folder, target_path, user_account, history_info, _db_session=db_session, _commit=False ) # OK! logger.info( 'Disk folder %s successfully moved to %s by %s' % (db_folder.path, target_path, user_account.username if user_account else 'System') ) success = True return db_folder finally: # Commit or rollback database try: if success: db_session.commit() else: db_session.rollback() finally: db_session.close() # Clear folder permissions cache as folder tree has changed if success: permissions_manager.reset()
def move_file(db_image, target_path, user_account, data_manager, permissions_manager): """ Moves an image file to the given new path and filename (the folder component of which must already exist), adds image history and updates the associated database records. The image file is effectively renamed if the folder part of the path remains the same. The user account must have Delete File permission for the source folder and Upload File permission for the target folder, or alternatively have the file admin system permission. This method creates and commits its own separate database connection so that the operation is atomic. Returns the updated image object. Raises a DoesNotExistError if the source image file does not exist or the target folder does not exist. Raises an AlreadyExistsError if the target file already exists. Raises an IOError or OSError if the target file cannot be created. Raises a ValueError if the target filename is invalid. Raises a DBError for database errors. Raises a SecurityError if the current user does not have sufficient permission to perform the move or if the target path is outside of IMAGES_BASE_DIR. """ db_session = data_manager.db_get_session() file_moved = False success = False try: _validate_path_chars(target_path) target_path = filepath_normalize(target_path) # Connect db_image to our database session db_image = data_manager.get_image(db_image.id, _db_session=db_session) if not db_image: raise DoesNotExistError('Image ID %d does not exist' % db_image.id) # Save the old path for rolling back source_path = db_image.src source_folder = filepath_parent(source_path) source_filename = filepath_filename(source_path) # Get and secure the target filename target_folder = filepath_parent(target_path) target_filename = filepath_filename(target_path) target_filename = secure_filename( target_filename, app.config['ALLOW_UNICODE_FILENAMES'] ) target_path = os.path.join(target_folder, target_filename) # Insist on minimum a.xyz file name (else raise ValueError) validate_filename(target_filename) # Target folder must exist ensure_path_exists(target_folder, require_directory=True) # Do nothing if target path is the same as the source if strip_sep(db_image.src, leading=True) == strip_sep(target_path, leading=True): success = True return db_image # Get source and target folder data db_source_folder = db_image.folder db_target_folder = auto_sync_existing_folder( target_folder, data_manager, _db_session=db_session ) if db_target_folder is None: raise DoesNotExistError(target_folder) # Should never happen # Check source file exists ensure_path_exists(db_image.src, require_file=True) # Check target file does not exist (we cannot merge) if path_exists(target_path, require_file=True): raise AlreadyExistsError('Target file already exists: ' + target_path) renaming = (db_source_folder == db_target_folder) # Check permissions for source and destination folders permissions_manager.ensure_folder_permitted( db_target_folder, FolderPermission.ACCESS_UPLOAD, user_account ) if not renaming: permissions_manager.ensure_folder_permitted( db_source_folder, FolderPermission.ACCESS_DELETE, user_account ) # We know there's no physical target file, but if there is an # old (deleted) db record for the target path, purge it first db_old_target_image = data_manager.get_image( src=target_path, _db_session=db_session ) if db_old_target_image: data_manager.delete_image( db_old_target_image, purge=True, _db_session=db_session, _commit=False ) # Move the physical file filesystem_manager.move(source_path, target_path) file_moved = True # Update the database db_image.status = Image.STATUS_ACTIVE db_image.folder = db_target_folder data_manager.set_image_src(db_image, target_path) # Add history if renaming: history_info = 'Renamed from ' + source_filename + ' to ' + target_filename else: history_info = 'Moved from ' + source_folder + ' to ' + target_folder data_manager.add_image_history( db_image, user_account, ImageHistory.ACTION_MOVED, history_info, _db_session=db_session, _commit=False ) # OK! success = True return db_image finally: # Rollback file move? if not success and file_moved: try: filesystem_manager.move(target_path, source_path) except: pass # Commit or rollback database try: if success: db_session.commit() else: db_session.rollback() finally: db_session.close()