Exemple #1
0
def ip_address(reset=False):
    """
   If Drupal is behind a reverse proxy, we use the X-Forwarded-For header
   instead of $_SERVER['REMOTE_ADDR'], which would be the IP address of 
   the proxy server, and not the client's.  If Drupal is run in a cluster
   we use the X-Cluster-Client-Ip header instead.

   @param $reset
     Reset the current IP address saved in static.
   @return
     IP address of client machine, adjusted for reverse proxy and/or cluster
     environments.
  """
    php.static(ip_address, "ip_address")
    if ip_address.ip_address is None or reset:
        ip_address.ip_address = php.SERVER["REMOTE_ADDR"]
        if variable_get("reverse_proxy", 0):
            if php.array_key_exists("HTTP_X_FORWARDED_FOR", php.SERVER):
                # If an array of known reverse proxy IPs is provided, then trust
                # the XFF header if request really comes from one of them.
                reverse_proxy_addresses = variable_get("reverse_proxy_addresses", tuple())
                if not php.empty(reverse_proxy_addresses) and php.in_array(
                    ip_address.ip_address, reverse_proxy_addresses, True
                ):
                    # If there are several arguments, we need to check the most
                    # recently added one, i.e. the last one.
                    ip_address.ip_address = php.array_pop(php.explode(",", php.SERVER["HTTP_X_FORWARDED_FOR"]))
            # When Drupal is run in a cluster environment,
            # REMOTE_ADDR contains the IP
            # address of a server in the cluster, while the IP address
            # of the client is
            # stored in HTTP_X_CLUSTER_CLIENT_IP.
            if php.array_key_exists("HTTP_X_CLUSTER_CLIENT_IP", php.SERVER):
                ip_address.ip_address = php.SERVER["HTTP_X_CLUSTER_CLIENT_IP"]
    return ip_address.ip_address
Exemple #2
0
def munge_filename(filename, extensions, alerts = True):
  """
   Munge the filename as needed for security purposes + For instance the file
   name "exploit.php.pps" would become "exploit.php_.pps".
  
   @param filename The name of a file to modify.
   @param extensions A space separated list of extensions that should not
     be altered.
   @param alerts Whether alerts (watchdog, drupal_set_message()) should be
     displayed.
   @return filename The potentially modified filename.
  """
  original = filename
  # Allow potentially insecure uploads for very savvy users and admin
  if (not variable_get('allow_insecure_uploads', 0)):
    whitelist = array_unique(php.explode(' ', php.trim(extensions)))
    # Split the filename up by periods + The first part becomes the basename
    # the last part the final extension.
    filename_parts = php.explode('.', filename)
    new_filename = php.array_shift(filename_parts); # Remove file basename.
    final_extension = php.array_pop(filename_parts); # Remove final extension.
    # Loop through the middle parts of the name and add an underscore to the
    # end of each section that could be a file extension but isn't in the list
    # of allowed extensions.
    for filename_part in filename_parts:
      new_filename += '.' + filename_part
      if (not php.in_array(filename_part, whitelist) and \
          php.preg_match("/^[a-zA-Z]{2,5}\d?$/", filename_part)):
        new_filename += '_'
    filename = new_filename + '.' + final_extension
    if (alerts and original != filename):
      drupal_set_message(t('For security reasons, your upload has ' + \
        'been renamed to %filename.', {'%filename' : filename}))
  return filename
def query_range(query):
  """
   Runs a limited-range query in the active database.
  
   Use this as a substitute for db_query() when a subset of the query is to be
   returned. User-supplied arguments to the query should be passed in as
   separate parameters so that they can be properly escaped to avoid SQL
   injection attacks.
  
   @param query
     A string containing an SQL query.
   @param ...
     A variable number of arguments which are substituted into the query
     using printf() syntax. The query arguments can be enclosed in one
     array instead.
     Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose
     in '') and %%.
     NOTE: using this syntax will cast None and False values to decimal 0,
     and True values to decimal 1.
   @param from
     The first result row to return.
   @param count
     The maximum number of result rows to return.
   @return
     A database query result resource, or False if the query was not executed
     correctly.
  """
  args = func_get_args()
  count = php.array_pop(args)
  from_ = php.array_pop(args)
  php.array_shift(args)
  query = db_prefix_tables(query)
  # 'All arguments in one array' syntax
  if (php.isset(args, 0) and php.is_array(args, 0)): 
    args = args[0]
  _db_query_callback(args, True)
  query = php.preg_replace_callback(DB_QUERY_REGEXP, \
    '_db_query_callback', query)
  query += ' LIMIT ' +  int(from_)  + ', ' . int(count)
  return _db_query(query)
Exemple #4
0
def query_range(query):
    """
   Runs a limited-range query in the active database.
  
   Use this as a substitute for db_query() when a subset of the query is to be
   returned. User-supplied arguments to the query should be passed in as
   separate parameters so that they can be properly escaped to avoid SQL
   injection attacks.
  
   @param query
     A string containing an SQL query.
   @param ...
     A variable number of arguments which are substituted into the query
     using printf() syntax. The query arguments can be enclosed in one
     array instead.
     Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose
     in '') and %%.
     NOTE: using this syntax will cast None and False values to decimal 0,
     and True values to decimal 1.
   @param from
     The first result row to return.
   @param count
     The maximum number of result rows to return.
   @return
     A database query result resource, or False if the query was not executed
     correctly.
  """
    args = func_get_args()
    count = php.array_pop(args)
    from_ = php.array_pop(args)
    php.array_shift(args)
    query = db_prefix_tables(query)
    # 'All arguments in one array' syntax
    if (php.isset(args, 0) and php.is_array(args, 0)):
        args = args[0]
    _db_query_callback(args, True)
    query = php.preg_replace_callback(DB_QUERY_REGEXP, \
      '_db_query_callback', query)
    query += ' LIMIT ' + int(from_) + ', '.int(count)
    return _db_query(query)
def query_temporary(query):
  """
   Runs a SELECT query and stores its results in a temporary table.
  
   Use this as a substitute for db_query() when the results need to stored
   in a temporary table. Temporary tables exist for the duration of the page
   request.
   User-supplied arguments to the query should be passed in as
   separate parameters
   so that they can be properly escaped to avoid SQL injection attacks.
  
   Note that if you need to know how many results were returned, you should do
   a SELECT COUNT(*) on the temporary table afterwards. db_affected_rows() does
   not give consistent result across different database types in this case.
  
   @param query
     A string containing a normal SELECT SQL query.
   @param ...
     A variable number of arguments which are substituted into the query
     using printf() syntax. The query arguments can be enclosed in one
     array instead.
     Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose
     in '') and %%.
  
     NOTE: using this syntax will cast None and False values to decimal 0,
     and True values to decimal 1.
  
   @param table
     The name of the temporary table to select into. This name will not be
     prefixed as there is no risk of collision.
   @return
     A database query result resource, or False if the query was not executed
     correctly.
  """
  args = func_get_args()
  tablename = php.array_pop(args)
  php.array_shift(args)
  query = php.preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE ' +  \
    tablename  + ' Engine=HEAP SELECT', db_prefix_tables(query))
  # 'All arguments in one array' syntax
  if (php.isset(args, 0) and php.is_array(args, 0)): 
    args = args[0]
  _db_query_callback(args, True)
  query = php.preg_replace_callback(DB_QUERY_REGEXP, \
    '_db_query_callback', query)
  return _db_query(query)
Exemple #6
0
def query_temporary(query):
    """
   Runs a SELECT query and stores its results in a temporary table.
  
   Use this as a substitute for db_query() when the results need to stored
   in a temporary table. Temporary tables exist for the duration of the page
   request.
   User-supplied arguments to the query should be passed in as
   separate parameters
   so that they can be properly escaped to avoid SQL injection attacks.
  
   Note that if you need to know how many results were returned, you should do
   a SELECT COUNT(*) on the temporary table afterwards. db_affected_rows() does
   not give consistent result across different database types in this case.
  
   @param query
     A string containing a normal SELECT SQL query.
   @param ...
     A variable number of arguments which are substituted into the query
     using printf() syntax. The query arguments can be enclosed in one
     array instead.
     Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose
     in '') and %%.
  
     NOTE: using this syntax will cast None and False values to decimal 0,
     and True values to decimal 1.
  
   @param table
     The name of the temporary table to select into. This name will not be
     prefixed as there is no risk of collision.
   @return
     A database query result resource, or False if the query was not executed
     correctly.
  """
    args = func_get_args()
    tablename = php.array_pop(args)
    php.array_shift(args)
    query = php.preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE ' +  \
      tablename  + ' Engine=HEAP SELECT', db_prefix_tables(query))
    # 'All arguments in one array' syntax
    if (php.isset(args, 0) and php.is_array(args, 0)):
        args = args[0]
    _db_query_callback(args, True)
    query = php.preg_replace_callback(DB_QUERY_REGEXP, \
      '_db_query_callback', query)
    return _db_query(query)
Exemple #7
0
def ip_address(reset=False):
    """
   If Drupal is behind a reverse proxy, we use the X-Forwarded-For header
   instead of $_SERVER['REMOTE_ADDR'], which would be the IP address of 
   the proxy server, and not the client's.  If Drupal is run in a cluster
   we use the X-Cluster-Client-Ip header instead.

   @param $reset
     Reset the current IP address saved in static.
   @return
     IP address of client machine, adjusted for reverse proxy and/or cluster
     environments.
  """
    php.static(ip_address, 'ip_address')
    if (ip_address.ip_address is None or reset):
        ip_address.ip_address = php.SERVER['REMOTE_ADDR']
        if (variable_get('reverse_proxy', 0)):
            if (php.array_key_exists('HTTP_X_FORWARDED_FOR', php.SERVER)):
                # If an array of known reverse proxy IPs is provided, then trust
                # the XFF header if request really comes from one of them.
                reverse_proxy_addresses = variable_get('reverse_proxy_addresses', \
                  tuple())
                if (not php.empty(reverse_proxy_addresses) and \
                    php.in_array(ip_address.ip_address, reverse_proxy_addresses, \
                    True)):
                    # If there are several arguments, we need to check the most
                    # recently added one, i.e. the last one.
                    ip_address.ip_address = php.array_pop(\
                      php.explode(',', php.SERVER['HTTP_X_FORWARDED_FOR']))
            # When Drupal is run in a cluster environment,
            # REMOTE_ADDR contains the IP
            # address of a server in the cluster, while the IP address
            # of the client is
            # stored in HTTP_X_CLUSTER_CLIENT_IP.
            if (php.array_key_exists('HTTP_X_CLUSTER_CLIENT_IP', php.SERVER)):
                ip_address.ip_address = php.SERVER['HTTP_X_CLUSTER_CLIENT_IP']
    return ip_address.ip_address
Exemple #8
0
def munge_filename(filename, extensions, alerts=True):
    """
   Munge the filename as needed for security purposes + For instance the file
   name "exploit.php.pps" would become "exploit.php_.pps".
  
   @param filename The name of a file to modify.
   @param extensions A space separated list of extensions that should not
     be altered.
   @param alerts Whether alerts (watchdog, drupal_set_message()) should be
     displayed.
   @return filename The potentially modified filename.
  """
    original = filename
    # Allow potentially insecure uploads for very savvy users and admin
    if (not variable_get('allow_insecure_uploads', 0)):
        whitelist = array_unique(php.explode(' ', php.trim(extensions)))
        # Split the filename up by periods + The first part becomes the basename
        # the last part the final extension.
        filename_parts = php.explode('.', filename)
        new_filename = php.array_shift(filename_parts)
        # Remove file basename.
        final_extension = php.array_pop(filename_parts)
        # Remove final extension.
        # Loop through the middle parts of the name and add an underscore to the
        # end of each section that could be a file extension but isn't in the list
        # of allowed extensions.
        for filename_part in filename_parts:
            new_filename += '.' + filename_part
            if (not php.in_array(filename_part, whitelist) and \
                php.preg_match("/^[a-zA-Z]{2,5}\d?$/", filename_part)):
                new_filename += '_'
        filename = new_filename + '.' + final_extension
        if (alerts and original != filename):
            drupal_set_message(t('For security reasons, your upload has ' + \
              'been renamed to %filename.', {'%filename' : filename}))
    return filename
Exemple #9
0
def save_upload(source, validators = {}, dest = False, \
    replace = FILE_EXISTS_RENAME):
    """
   Saves a file upload to a new location + The source file is validated as a
   proper upload and handled as such.
  
   The file will be added to the files table as a temporary file.
   Temporary files
   are periodically cleaned + To make the file permanent file call
   file_set_status() to change its status.
  
   @param source
     A string specifying the name of the upload field to save.
   @param validators
     An optional, associative array of callback functions used to validate the
     file + The keys are function names and the values arrays of callback
     parameters which will be passed in after the user and file objects + The
     functions should return an array of error messages, an empty array
     indicates that the file passed validation.
     The functions will be called in
     the order specified.
   @param dest
     A string containing the directory source should be copied to + If this is
     not provided or is not writable, the temporary directory will be used.
   @param replace
     A boolean indicating whether an existing file of the same name in the
     destination directory should overwritten + A False value will generate a
     new, unique filename in the destination directory.
   @return
     An object containing the file information, or False
     in the event of an error.
  """
    php.static(file_save_upload, 'upload_cache', {})
    # Add in our check of the the file name length.
    validators['file_validate_name_length'] = {}
    # Return cached objects without processing since the file will have
    # already been processed and the paths in FILES will be invalid.
    if (php.isset(file_save_upload.uploadcache, source)):
        return file_save_upload.uploadcache[source]
    # If a file was uploaded, process it.
    if (php.isset(p.FILES, 'files') and p.FILES['files']['name'][source] and \
        php.is_uploaded_file(p.FILES['files']['tmp_name'][source])):
        # Check for file upload errors and return False if a
        # lower level system error occurred.
        # @see http://php.net/manual/en/features.file-upload.errors.php
        if p.FILES['files']['error'][source] == UPLOAD_ERR_OK:
            pass
        elif p.FILES['files']['error'][source] == UPLOAD_ERR_INI_SIZE or \
            p.FILES['files']['error'][source] == UPLOAD_ERR_FORM_SIZE:
            drupal_set_message(t(\
              'The file %file could not be saved, because it exceeds %maxsize, ' + \
              'the maximum allowed size for uploads.', \
              {'%file' : source, '%maxsize' : \
              format_size(file_upload_max_size())}), 'error')
            return False
        elif p.FILES['files']['error'][source] == UPLOAD_ERR_PARTIAL or \
            p.FILES['files']['error'][source] == UPLOAD_ERR_NO_FILE:
            drupal_set_message(t('The file %file could not be saved, ' + \
              'because the upload did not complete.', {'%file' : source}), 'error')
            return False
        # Unknown error
        else:
            drupal_set_message(t('The file %file could not be saved. ' + \
              'An unknown error has occurred.', {'%file' : source}), 'error')
            return False
        # Build the list of non-munged extensions.
        # @todo: this should not be here + we need to figure out the right place.
        extensions = ''
        for rid, name in lib_appglobals.user.roles.items():
            extensions += ' ' + variable_get("upload_extensions_rid",
            variable_get('upload_extensions_default', \
              'jpg jpeg gif png txt html doc xls pdf ppt pps odt ods odp'))
        # Begin building file object.
        file = php.stdClass()
        file.filename = file_munge_filename(php.trim(\
          basename(p.FILES['files']['name'][source]), '.'), extensions)
        file.filepath = p.FILES['files']['tmp_name'][source]
        file.filemime = p.FILES['files']['type'][source]
        # Rename potentially executable files, to help prevent exploits.
        if (php.preg_match('/\.(php|pl|py|cgi|asp|js)$/i', file.filename) and \
            (php.substr(file.filename, -4) != '.txt')):
            file.filemime = 'text/plain'
            file.filepath += '.txt'
            file.filename += '.txt'
        # If the destination is not provided, or is not writable, then use the
        # temporary directory.
        if (php.empty(dest) or file_check_path(dest) == False):
            dest = file_directory_temp()
        file.source = source
        file.destination = file_destination(file_create_path(dest + '/' + \
          file.filename), replace)
        file.filesize = FILES['files']['size'][source]
        # Call the validation functions.
        errors = {}
        for function, args in validators.items():
            array_unshift(args, file)
            errors = php.array_merge(errors, function(*args))
        # Check for validation errors.
        if (not php.empty(errors)):
            message = t('The selected file %name could not be uploaded.', \
              {'%name' : file.filename})
            if (php.count(errors) > 1):
                message += '<ul><li>' + php.implode('</li><li>',
                                                    errors) + '</li></ul>'
            else:
                message += ' ' + php.array_pop(errors)
            form_set_error(source, message)
            return False
        # Move uploaded files from PHP's upload_tmp_dir to
        # Drupal's temporary directory.
        # This overcomes open_basedir restrictions for future file operations.
        file.filepath = file.destination
        if (not move_uploaded_file(p.FILES['files']['tmp_name'][source], \
            file.filepath)):
            form_set_error(source, t('File upload error. ' + \
              'Could not move uploaded file.'))
            watchdog('file', 'Upload error + Could not move uploaded file ' + \
              '%file to destination %destination.', \
              {'%file' : file.filename, '%destination' : file.filepath})
            return False
        # If we made it this far it's safe to record this file in the database.
        file.uid = lib_appglobals.user.uid
        file.status = FILE_STATUS_TEMPORARY
        file.timestamp = time()
        drupal_write_record('files', file)
        # Add file to the cache.
        file_save_upload.upload_cache[source] = file
        return file
    return False
Exemple #10
0
def save_upload(source, validators = {}, dest = False, \
    replace = FILE_EXISTS_RENAME):
  """
   Saves a file upload to a new location + The source file is validated as a
   proper upload and handled as such.
  
   The file will be added to the files table as a temporary file.
   Temporary files
   are periodically cleaned + To make the file permanent file call
   file_set_status() to change its status.
  
   @param source
     A string specifying the name of the upload field to save.
   @param validators
     An optional, associative array of callback functions used to validate the
     file + The keys are function names and the values arrays of callback
     parameters which will be passed in after the user and file objects + The
     functions should return an array of error messages, an empty array
     indicates that the file passed validation.
     The functions will be called in
     the order specified.
   @param dest
     A string containing the directory source should be copied to + If this is
     not provided or is not writable, the temporary directory will be used.
   @param replace
     A boolean indicating whether an existing file of the same name in the
     destination directory should overwritten + A False value will generate a
     new, unique filename in the destination directory.
   @return
     An object containing the file information, or False
     in the event of an error.
  """
  php.static(file_save_upload, 'upload_cache', {})
  # Add in our check of the the file name length.
  validators['file_validate_name_length'] = {}
  # Return cached objects without processing since the file will have
  # already been processed and the paths in FILES will be invalid.
  if (php.isset(file_save_upload.uploadcache, source)):
    return file_save_upload.uploadcache[source]
  # If a file was uploaded, process it.
  if (php.isset(p.FILES, 'files') and p.FILES['files']['name'][source] and \
      php.is_uploaded_file(p.FILES['files']['tmp_name'][source])):
    # Check for file upload errors and return False if a
    # lower level system error occurred.
    # @see http://php.net/manual/en/features.file-upload.errors.php
    if p.FILES['files']['error'][source] == UPLOAD_ERR_OK:
      pass
    elif p.FILES['files']['error'][source] == UPLOAD_ERR_INI_SIZE or \
        p.FILES['files']['error'][source] == UPLOAD_ERR_FORM_SIZE:
      drupal_set_message(t(\
        'The file %file could not be saved, because it exceeds %maxsize, ' + \
        'the maximum allowed size for uploads.', \
        {'%file' : source, '%maxsize' : \
        format_size(file_upload_max_size())}), 'error')
      return False
    elif p.FILES['files']['error'][source] == UPLOAD_ERR_PARTIAL or \
        p.FILES['files']['error'][source] == UPLOAD_ERR_NO_FILE:
      drupal_set_message(t('The file %file could not be saved, ' + \
        'because the upload did not complete.', {'%file' : source}), 'error')
      return False
    # Unknown error
    else:
      drupal_set_message(t('The file %file could not be saved. ' + \
        'An unknown error has occurred.', {'%file' : source}), 'error')
      return False
    # Build the list of non-munged extensions.
    # @todo: this should not be here + we need to figure out the right place.
    extensions = ''
    for rid,name in lib_appglobals.user.roles.items():
      extensions += ' ' + variable_get("upload_extensions_rid",
      variable_get('upload_extensions_default', \
        'jpg jpeg gif png txt html doc xls pdf ppt pps odt ods odp'))
    # Begin building file object.
    file = php.stdClass()
    file.filename = file_munge_filename(php.trim(\
      basename(p.FILES['files']['name'][source]), '.'), extensions)
    file.filepath = p.FILES['files']['tmp_name'][source]
    file.filemime = p.FILES['files']['type'][source]
    # Rename potentially executable files, to help prevent exploits.
    if (php.preg_match('/\.(php|pl|py|cgi|asp|js)$/i', file.filename) and \
        (php.substr(file.filename, -4) != '.txt')):
      file.filemime = 'text/plain'
      file.filepath += '.txt'
      file.filename += '.txt'
    # If the destination is not provided, or is not writable, then use the
    # temporary directory.
    if (php.empty(dest) or file_check_path(dest) == False):
      dest = file_directory_temp()
    file.source = source
    file.destination = file_destination(file_create_path(dest + '/' + \
      file.filename), replace)
    file.filesize = FILES['files']['size'][source]
    # Call the validation functions.
    errors = {}
    for function,args in validators.items():
      array_unshift(args, file)
      errors = php.array_merge(errors, function(*args))
    # Check for validation errors.
    if (not php.empty(errors)):
      message = t('The selected file %name could not be uploaded.', \
        {'%name' : file.filename})
      if (php.count(errors) > 1):
        message += '<ul><li>' + php.implode('</li><li>', errors) + '</li></ul>'
      else:
        message += ' ' + php.array_pop(errors)
      form_set_error(source, message)
      return False
    # Move uploaded files from PHP's upload_tmp_dir to
    # Drupal's temporary directory.
    # This overcomes open_basedir restrictions for future file operations.
    file.filepath = file.destination
    if (not move_uploaded_file(p.FILES['files']['tmp_name'][source], \
        file.filepath)):
      form_set_error(source, t('File upload error. ' + \
        'Could not move uploaded file.'))
      watchdog('file', 'Upload error + Could not move uploaded file ' + \
        '%file to destination %destination.', \
        {'%file' : file.filename, '%destination' : file.filepath})
      return False
    # If we made it this far it's safe to record this file in the database.
    file.uid = lib_appglobals.user.uid
    file.status = FILE_STATUS_TEMPORARY
    file.timestamp = time()
    drupal_write_record('files', file)
    # Add file to the cache.
    file_save_upload.upload_cache[source] = file
    return file
  return False