code:
/* MegaZeux
 *
 * Copyright (C) 2002 Gilead Kutnick <exophase@adelphia.net>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#include "expr.h"

#include "world_struct.h"
#include "counter.h"
#include "robot.h"
#include "rasm.h"

// Only pass these evaluators valid expressions. The compiler should take care
// of that part, so don't call it outside of evaluating robot code.

enum op
{
  OP_ADDITION,
  OP_SUBTRACTION,
  OP_MULTIPLICATION,
  OP_DIVISION,
  OP_MODULUS,
  OP_EXPONENTIATION,
  OP_AND,
  OP_OR,
  OP_XOR,
  OP_BITSHIFT_LEFT,
  OP_BITSHIFT_RIGHT,
  OP_ARITHMETIC_BITSHIFT_RIGHT,
  OP_EQUAL,
  OP_LESS_THAN,
  OP_GREATER_THAN,
  OP_GREATER_THAN_OR_EQUAL,
  OP_LESS_THAN_OR_EQUAL,
  OP_NOT_EQUAL
};

static int last_val;

static char *expr_skip_whitespace(char *expression)
{
  while(isspace((int)*expression))
    expression++;
  return expression;
}

static int parse_argument(struct world *mzx_world, char **_argument,
 int *type, int id)
{
  char *argument = *_argument;
  int first_char = *argument;

  // Test the first one.

  switch(first_char)
  {
    // Addition operator
    case '+':
    {
      *type = 1;
      *_argument = argument + 1;
      return OP_ADDITION;
    }

    // Subtraction operator
    case '-':
    {
      argument++;
      if(!isspace((int)*argument))
      {
        int t2, val = parse_argument(mzx_world, &argument, &t2, id);
#ifndef CONFIG_DEBYTECODE
        if((t2 != 0) && (t2 != 2))
        {
          *type = -1;
          *_argument = argument;
          return -1;
        }
#endif
        val = -val;
        *type = 2;
        *_argument = argument;
        return val;
      }

      *type = 1;
      *_argument = argument;
      return OP_SUBTRACTION;
    }

    // Multiplication operator
    case '*':
    {
      *type = 1;
      argument++;
#ifdef CONFIG_DEBYTECODE
      if(*argument == '*')
      {
        *_argument = argument + 1;
        return OP_EXPONENTIATION;
      }
      else
#endif
      {
        *_argument = argument;
        return OP_MULTIPLICATION;
      }
    }

    // Division operator
    case '/':
    {
      *type = 1;
      *_argument = argument + 1;
      return OP_DIVISION;
    }

    // Modulus operator
    case '%':
    {
      *type = 1;
      *_argument = argument + 1;
      return OP_MODULUS;
    }

#ifndef CONFIG_DEBYTECODE
    // Exponent operator
    case '^':
    {
      *type = 1;
      *_argument = argument + 1;
      return OP_EXPONENTIATION;
    }
#endif

    // Bitwise AND operator
#ifdef CONFIG_DEBYTECODE
    case '&':
#else
    case 'a':
#endif
    {
      *type = 1;
      *_argument = argument + 1;
      return OP_AND;
    }

    // Bitwise OR operator
#ifdef CONFIG_DEBYTECODE
    case '|':
#else
    case 'o':
#endif
    {
      *type = 1;
      *_argument = argument + 1;
      return OP_OR;
    }

    // Bitwise XOR operator
#ifdef CONFIG_DEBYTECODE
    case '^':
#else
    case 'x':
#endif
    {
      *type = 1;
      *_argument = argument + 1;
      return OP_XOR;
    }

    // Less than/bitshift left
    case '<':
    {
      *type = 1;
      argument++;
      if(*argument == '<')
      {
        *_argument = argument + 1;
        return OP_BITSHIFT_LEFT;
      }
      if(*argument == '=')
      {
        *_argument = argument + 1;
        return OP_LESS_THAN_OR_EQUAL;
      }
      *_argument = argument;
      return OP_LESS_THAN;
    }

    // Greater than/bitshift right
    case '>':
    {
      *type = 1;
      argument++;
      if(*argument == '>')
      {
        if(argument[1] == '>')
        {
          *_argument = argument + 2;
          return OP_ARITHMETIC_BITSHIFT_RIGHT;
        }
        *_argument = argument + 1;
        return OP_BITSHIFT_RIGHT;
      }
      if(*argument == '=')
      {
        *_argument = argument + 1;
        return OP_GREATER_THAN_OR_EQUAL;
      }
      *_argument = argument;
      return OP_GREATER_THAN;
    }

    // Equality
    case '=':
    {
      *type = 1;
      *_argument = argument + 1;
      return OP_EQUAL;
    }

    // Inequality (there will be a = afterwards)
    case '!':
    {
      *type = 1;
      *_argument = argument + 2;
      return OP_NOT_EQUAL;
    }

    // One's complement
    case '~':
    {
      int t2, val;

      argument++;
      val = parse_argument(mzx_world, &argument, &t2, id);

#ifndef CONFIG_DEBYTECODE
      if((t2 != 0) && (t2 != 2))
      {
        *type = -1;
        *_argument = argument;
        return -1;
      }
#endif

      val = ~val;
      *type = 2;
      *_argument = argument;
      return val;
    }

#ifndef CONFIG_DEBYTECODE
    // # is the last expression value, 32bit.
    case '#':
    {
      *type = 0;
      *_argument = argument + 1;
      return last_val;
    }

    // The evil null terminator...
    case '\0':
    {
      *type = -1;
      return -1;
    }
#endif

    // Parentheses! Recursive goodness...
    // Treat a valid return as an argument.
    case '(':
    {
      int val, error;
      argument++;
      val = parse_expression(mzx_world, &argument, &error, id);
#ifndef CONFIG_DEBYTECODE
      if(error)
      {
        *type = -1;
        *_argument = argument;
        return -1;
      }
#endif
      *type = 0;
      *_argument = argument;
      return val;
    }

#ifdef CONFIG_DEBYTECODE

    // Begins with a ` makes it a counter name
    case '`':
    {
      char name_translated[ROBOT_MAX_TR];
      argument++;

      *type = 0;
      *_argument = tr_msg_ext(mzx_world, argument, id, name_translated, '`');
      return get_counter(mzx_world, name_translated, id);
    }

#else // !CONFIG_DEBYTECODE

    // Begins with a ' or a & makes it a message ('counter')
    case '&':
    case '\'':
    {
      // Find where the next ' or & is
      char t_char = first_char;
      char temp[256];
      char temp2[256];
      int count = 0;

      argument++;

      // Remember, null terminator is evil; if it's hit exit completely.
      while(((*argument) != t_char) && (count < 256))
      {
        // If a nested expression is hit closing 's should be ignored
        if((*argument) == '(')
        {
          int close_paren_count = 1;
          // The number of )'s to expect.. finding one decreases it..
          // And finding a ( increases it.

          while((close_paren_count) && (count < 256))
          {
            temp[count] = *argument;
            argument++;
            count++;

            switch(*argument)
            {
              case '\0':
              {
                *type = -1;
                *_argument = argument;
                return -1;
              }
              case ')':
              {
                close_paren_count--;
                break;
              }
              case '(':
              {
                close_paren_count++;
                break;
              }
            }
          }
        }
        else
        {
          if(*argument == '\0')
          {
            *type = -1;
            *_argument = argument;
            return -1;
          }

          temp[count] = *argument;
          argument++;
          count++;
        }
      }

      *type = 0;
      *_argument = argument + 1;

      temp[count] = '\0';
      tr_msg(mzx_world, temp, id, temp2);
      return get_counter(mzx_world, temp2, id);
    }

#endif // !CONFIG_DEBYTECODE

    // Otherwise, it must be an integer, or just something invalid.
    default:
    {
      if((first_char >= '0') && (first_char <= '9'))
      {
        char *end_p;
        int val = (int)strtol(argument, &end_p, 0);
        *type = 0;
        *_argument = end_p;
        return val;
      }
      else
      {
#ifdef CONFIG_DEBYTECODE
        char *end_p = find_non_identifier_char(argument);
        char temp = *end_p;
        int value;

        *end_p = 0;
        value = get_counter(mzx_world, argument, id);
        *end_p = temp;

        *type = 0;
        *_argument = end_p;
        return value;
#else
        // It's not so good..
        *type = -1;
        *_argument = argument;
        return -1;
#endif
      }
    }
  }
}

static int evaluate_operation(int operand_a, enum op c_operator, int operand_b)
{
  switch(c_operator)
  {
    case OP_ADDITION:
      return operand_a + operand_b;

    case OP_SUBTRACTION:
      return operand_a - operand_b;

    case OP_MULTIPLICATION:
      return operand_a * operand_b;

    case OP_DIVISION:
    {
      if(operand_b == 0)
        return 0;

      return operand_a / operand_b;
    }

    case OP_MODULUS:
    {
      int val;

      if(operand_b == 0)
        return 0;

      val = operand_a % operand_b;

      // Converted C99 regulated truncated modulus to
      // the more useful (for us) floored modulus
      // Source:
      // Division and Modulus for Computer Scientists
      // DAAN LEIJEN
      // University of Utrecht

      if((val < 0) ^ (operand_b < 0))
        return val + operand_b;

      return val;
    }

    case OP_EXPONENTIATION:
    {
      int i;
      int val = 1;

      if(operand_a == 0)
        return 0;

      if(operand_a == 1)
        return 1;

      if(operand_b < 0)
      {
        if(operand_a == -1)
          operand_b *= -1;
        else
          return 0;
      }

      for(i = 0; i < operand_b; i++)
        val *= operand_a;

      return val;
    }

    case OP_AND:
      return operand_a & operand_b;

    case OP_OR:
      return operand_a | operand_b;

    case OP_XOR:
      return (operand_a ^ operand_b);

    case OP_BITSHIFT_LEFT:
      return operand_a << operand_b;

    case OP_BITSHIFT_RIGHT:
      return (unsigned int)(operand_a) >> operand_b;

    case OP_ARITHMETIC_BITSHIFT_RIGHT:
      return (signed int)(operand_a) >> operand_b;

    case OP_EQUAL:
      return operand_a == operand_b;

    case OP_LESS_THAN:
      return operand_a < operand_b;

    case OP_LESS_THAN_OR_EQUAL:
      return operand_a <= operand_b;

    case OP_GREATER_THAN:
      return operand_a > operand_b;

    case OP_GREATER_THAN_OR_EQUAL:
      return operand_a >= operand_b;

    case OP_NOT_EQUAL:
      return operand_a != operand_b;

    default:
      return operand_a;
  }
}

int parse_expression(struct world *mzx_world, char **_expression, int *error,
 int id)
{
  char *expression = *_expression;
  int operand_val;
  int current_arg;
  int c_operator;
  int value;

  *error = 0;

  // Skip initial whitespace..
  expression = expr_skip_whitespace(expression);
  value = parse_argument(mzx_world, &expression, &current_arg, id);

#ifndef CONFIG_DEBYTECODE
  if((current_arg != 0) && (current_arg != 2))
  {
    // First argument must be a value type.
    *error = 1;
    value = -99;
    goto err_out;
  }
#endif

  expression = expr_skip_whitespace(expression);

  while(1)
  {
    if(*expression == ')')
    {
      expression++;
      break;
    }

    c_operator = parse_argument(mzx_world, &expression, &current_arg, id);
    // Next arg must be an operator, unless it's a negative number,
    // in which case it's considered + num
    if(current_arg == 2)
    {
      value = evaluate_operation(value, OP_ADDITION, c_operator);
    }
    else
    {
#ifndef CONFIG_DEBYTECODE
      if(current_arg != 1)
      {
        *error = 2;
        value = -100;
        goto err_out;
      }
#endif

      expression = expr_skip_whitespace(expression);
      operand_val = parse_argument(mzx_world, &expression, &current_arg, id);

#ifndef CONFIG_DEBYTECODE
      // And now it must be an integer.
      if((current_arg != 0) && (current_arg != 2))
      {
        *error = 3;
        value = -102;
        goto err_out;
      }
#endif

      value = evaluate_operation(value, (enum op)c_operator, operand_val);
    }
    expression = expr_skip_whitespace(expression);
  }

  last_val = value;

#ifndef CONFIG_DEBYTECODE
err_out:
#endif
  *_expression = expression;
  return value;
}

#ifdef CONFIG_DEBYTECODE

int parse_string_expression(struct world *mzx_world, char **_expression,
 int id, char *output)
{
  char *expression = *_expression;
  int expression_length = 0;

  while(1)
  {
    expression = expr_skip_whitespace(expression);

    switch(*expression)
    {
      case '`':
      {
        struct string string;
        char name_translated[ROBOT_MAX_TR];
        expression++;

        expression =
         tr_msg_ext(mzx_world, expression, id, name_translated, '`');

        get_string(mzx_world, name_translated, &string, id);

        memcpy(output, string.value, string.length);
        output += string.length;

        expression_length += string.length;
        break;
      }

      case '"':
      {
        int literal_length;
        expression++;

        expression = tr_msg_ext(mzx_world, expression, id, output, '"');
        literal_length = strlen(output);

        output += literal_length;
        expression_length += literal_length;
        break;
      }

      case '>':
        expression++;
        *_expression = expression;
        return expression_length;

      default:
      {
        char temp;
        char *next;
        struct string string;

        next = find_non_identifier_char(expression);

        temp = *next;
        *next = 0;
        get_string(mzx_world, expression, &string, id);
        *next = temp;

        expression = next;

        memcpy(output, string.value, string.length);
        output += string.length;
        expression_length += string.length;
        break;
      }
    }
  }
}

#endif // CONFIG_DEBYTECODE


code:
/* MegaZeux
 *
 * Copyright (C) 2002 Gilead Kutnick <exophase@adelphia.net>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#ifndef __EXPR_H
#define __EXPR_H

#include "compat.h"

__M_BEGIN_DECLS

#include "world_struct.h"

int parse_expression(struct world *mzx_world, char **expression, int *error,
 int id);

#ifdef CONFIG_DEBYTECODE
int parse_string_expression(struct world *mzx_world, char **_expression,
 int id, char *output);
#endif

__M_END_DECLS

#endif // __EXPR_H

repo_name: pgimeno/megazeux

license: mit


code:
/* MegaZeux
 *
 * Copyright (C) 2007 Kevin Vance <kvance@kvance.com>
 * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#ifndef __EXTMEM_H
#define __EXTMEM_H

#include "compat.h"

__M_BEGIN_DECLS

#include "board_struct.h"
#include "world_struct.h"

#if defined(CONFIG_NDS) && !defined(CONFIG_DEBYTECODE)

// Move the board's memory from normal RAM to extra RAM.
void store_board_to_extram(struct board *board);

// Move the board's memory from normal RAM to extra RAM.
void retrieve_board_from_extram(struct board *board);

// Set the current board to cur_board, in regular memory.
void set_current_board(struct world *mzx_world, struct board *cur_board);

// Set the current board to cur_board, in extra memory.
void set_current_board_ext(struct world *mzx_world, struct board *cur_board);

#else // !CONFIG_NDS || CONFIG_DEBYTECODE

/* If we're not on NDS, these inline functions should get optimised
 * away. The implementations are obvious enough.
 */

static inline void store_board_to_extram(struct board *board) {}
static inline void retrieve_board_from_extram(struct board *board) {}

static inline void set_current_board(struct world *mzx_world,
 struct board *cur_board)
{
  mzx_world->current_board = cur_board;
}

static inline void set_current_board_ext(struct world *mzx_world,
 struct board *cur_board)
{
  set_current_board(mzx_world, cur_board);
}

#endif // CONFIG_NDS && !CONFIG_DEBYTECODE

__M_END_DECLS

#endif // __BOARD_H


code:
/* MegaZeux
 *
 * Copyright (C) 2004-2005 Alistair Strachan <alistair@devzero.co.uk>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/stat.h>

#include "fsafeopen.h"
#include "const.h"
#include "util.h"

#ifndef __WIN32__

// convert to lowercase

static void case1(char *string)
{
  int i, len = strlen(string);

  // lowercase it
  for(i = 0; i < len; i++)
    string[i] = tolower((int)string[i]);
}

// convert to uppercase

static void case2(char *string)
{
  int i, len = strlen(string);

  // uppercase it
  for(i = 0; i < len; i++)
    string[i] = toupper((int)string[i]);
}

// convert from anything to filename.EXT

static void case3(char *string)
{
  int i, len = strlen(string);

  // upper case extension
  for(i = len; i > 0; i--)
  {
    string[i] = toupper((int)string[i]);

    // last separator
    if(string[i] == '.')
      break;
  }

  // lowercase rest
  for(i--; i >= 0; i--)
    string[i] = tolower((int)string[i]);
}

// convert from anything to FILENAME.ext

static void case4(char *string)
{
  int i, len = strlen(string);

  // lowercase extension
  for(i = len; i > 0; i--)
  {
    string[i] = tolower((int)string[i]);

    // last separator
    if(string[i] == '.')
      break;
  }

  // uppercase rest
  for(i--; i >= 0; i--)
    string[i] = toupper((int)string[i]);
}

// brute force method; returns -1 if no permutation can be found to work

static int case5(char *path, char *string)
{
  int ret = -FSAFE_BRUTE_FORCE_FAILED;
  int dirlen = string - path;
  struct mzx_dir wd;
  char *newpath;

  newpath = cmalloc(PATH_BUF_LEN);

  // prepend the working directory
  strcpy(newpath, "./");

  // copy everything sans last token
  if(dirlen > 0)
  {
    if(dirlen + 2 >= PATH_BUF_LEN)
      dirlen = PATH_BUF_LEN - 2;
    strncpy(newpath + 2, path, dirlen - 1);
    newpath[dirlen + 2 - 1] = 0;
  }

  if(dir_open(&wd, newpath))
  {
    while(ret != FSAFE_SUCCESS)
    {
      // somebody bad happened, or there's no new entry
      if(!dir_get_next_entry(&wd, newpath))
        break;

      // okay, we got something, but does it match?
      if(strcasecmp(string, newpath) == 0)
      {
        strcpy(string, newpath);
        ret = FSAFE_SUCCESS;
      }
    }

    dir_close(&wd);
  }

  free(newpath);
  return ret;
}

static int match(char *path)
{
  char *oldtoken = NULL, *token = NULL;
  struct stat inode;
  int i;

  if(path == NULL)
    return -FSAFE_MATCH_FAILED;

  /* FOUR likely permutations on files, and two likely permutations on
   * directories before we start having to do anything fancy:
   *
   * filename.ext FILENAME.EXT filename.EXT FILENAME.ext
   * directory    DIRECTORY
   */

  while(1)
  {
    if(token == NULL)
    {
      // initial token
      token = strtok(path, "/\\");
    }
    else
    {
      // subsequent tokens
      token = strtok(NULL, "/\\");

      // this token is the file
      if(token == NULL)
      {
        for(i = 0; i < 5; i++)
        {
          // check file
          if(stat(path, &inode) == 0)
            break;

          // try normal cases, then try brute force
          switch(i)
          {
            case 0:
              case1(oldtoken);
              break;

            case 1:
              case2(oldtoken);
              break;

            case 2:
              case3(oldtoken);
              break;

            case 3:
              case4(oldtoken);
              break;

            default:
              // try brute force
              if(case5(path, oldtoken) < 0)
              {
                debug("%s:%d: file matches for %s failed.\n",
                 __FILE__, __LINE__, path);
                return -FSAFE_MATCH_FAILED;
              }
          }
        }
        break;
      }

      for(i = 0; i < 3; i++)
      {
        // check directory
        if(stat(path, &inode) == 0)
          break;

        // try normal cases, then try brute force
        switch(i)
        {
          case 0:
            case1(oldtoken);
            break;

          case 1:
            case2(oldtoken);
            break;

          default:
            // try brute force
            if(case5(path, oldtoken) < 0)
            {
              debug("%s:%d: directory matches for %s failed.\n",
               __FILE__, __LINE__, path);
              return -FSAFE_MATCH_FAILED;
            }
        }
      }

      /* this "hack" overwrites the token's \0 to re-formulate
       * the string versus strtok(); it has the nice side-affect of
       * also converting windows style path to UNIX ones, so they'll
       * work on everything.
       */
      oldtoken[strlen(oldtoken)] = '/';
    }

    // backup last token pointer
    oldtoken = token;
  }

  return FSAFE_SUCCESS;
}
#endif // !__WIN32__

/* OK before we do anything, we need to make some security checks. MZX games
 * shouldn't be able to open C:\Windows\Explorer.exe and overwrite it, so
 * we need to filter out any absolute pathnames, or relative pathnames
 * including "..". Do so here.
 */

static int fsafetest(const char *path, char *newpath)
{
  size_t i, pathlen;

  // we don't accept it if it's NULL or too short
  if((path == NULL) || (path[0] == 0))
    return -FSAFE_INVALID_ARGUMENT;

  pathlen = strlen (path);
  strcpy(newpath, path);

  // convert the slashes
  for(i = 0; i < pathlen; i++)
  {
    if(newpath[i] == '\\')
      newpath[i] = '/';
  }

  // check root specifiers
  if(newpath[0] == '/')
    return -FSAFE_ABSOLUTE_PATH_ERROR;

  // otherwise it's too short to contain anything hazardous
  if(pathlen == 1)
    return FSAFE_SUCCESS;

  // windows drive letters
  if(((newpath[0] >= 'A') && (newpath[0] <= 'Z')) ||
     ((newpath[0] >= 'a') && (newpath[0] <= 'z')))
  {
    if(newpath[1] == ':')
      return -FSAFE_WINDOWS_DRIVE_LETTER_ERROR;
  }

  // reject any pathname including /../
  for(i = 0; i < pathlen; i++)
  {
    // not a match for ".."
    if(strncmp(newpath + i, "..", 2) != 0)
      continue;

    // no need for delimeters -- any .. is invalid
    if((i == 0) || (pathlen < 4))
      return -FSAFE_PARENT_DIRECTORY_ERROR;

    /* Here we just make the assumption that path/name\dir can't happen;
     * it's a fairly decent assumption because msys doesn't like it, and
     * linux doesn't even recognise \ as a directory delimeter!
     */
    if((newpath[i - 1] == '/')  && (newpath[i + 2] == '/'))
      return -FSAFE_PARENT_DIRECTORY_ERROR;
  }

  return FSAFE_SUCCESS;
}

int fsafetranslate(const char *path, char *newpath)
{
  struct stat file_info;
  int ret;

  // try to pass the basic security tests
  ret = fsafetest(path, newpath);
  if(ret == FSAFE_SUCCESS)
  {
    // see if file is already there
    if(stat(newpath, &file_info) != 0)
    {
#ifndef __WIN32__
      // it isn't, so try harder..
      ret = match(newpath);
      if(ret == FSAFE_SUCCESS)
      {
        // ..and update the stat information for the new path
        if(stat(newpath, &file_info) != 0)
          ret = -FSAFE_MATCH_FAILED;
      }
#else
      // on WIN32 we can't, so fail hard
      ret = -FSAFE_MATCH_FAILED;
#endif
    }

    if(ret == FSAFE_SUCCESS)
    {
      // most callers don't want directories
      if(S_ISDIR(file_info.st_mode))
        ret = -FSAFE_MATCHED_DIRECTORY;
    }
  }

#if !defined(__WIN32__)
  if(ret == -FSAFE_SUCCESS || ret == -FSAFE_MATCHED_DIRECTORY)
  {
    debug("%s:%d: translated %s to %s%s.\n", __FILE__, __LINE__,
     path, newpath, (ret == -FSAFE_MATCHED_DIRECTORY) ? "/" : "");
  }
  else
  {
    debug("%s:%d: failed to translate %s (err %d).\n",
     __FILE__, __LINE__, path, ret);
  }
#endif

  return ret;
}

FILE *fsafeopen(const char *path, const char *mode)
{
  char *newpath;
  int i, ret;
  FILE *f;

  newpath = cmalloc(MAX_PATH);

  // validate pathname, and optionally retrieve a better name
  ret = fsafetranslate(path, newpath);

  // filename couldn't be "found", but there were no security issues
  if(ret == -FSAFE_MATCH_FAILED)
  {
    // if we're reading, any failure to translate the name means we back out
    for(i = 0; i < (int)strlen(mode); i++)
    {
      if(mode[i] == 'r' || mode[i] == '+')
      {
        free(newpath);
        return NULL;
      }
    }
  }
  else if(ret < 0)
  {
    // bad name, or security checks failed
    free(newpath);
    return NULL;
  }

  // _TRY_ opening the file
  f = fopen_unsafe(newpath, mode);
  free(newpath);
  return f;
}

/* It's conceivable that on some platforms (like Linux, or Macintosh classic),
 * fgets may return a string that still contains "EOL" characters considered
 * by another platform. For example, if a file is written out by Windows,
 * and a Linux user reads it, the buffers will not remove the \r character.
 *
 * This function provides a "safe" wrapper that removes all kinds of line
 * endings from the buffer, and should work at least until somebody invents
 * a new three byte string terminator ;-(
 */
char *fsafegets(char *s, int size, FILE *stream)
{
  char *ret = fgets(s, size, stream);

  if(ret)
  {
    size_t len = strlen(ret);

    if(len > 0)
      if(s[len - 1] == '\r' || s[len - 1] == '\n')
        s[len - 1] = '\0';

    if(len > 1)
      if(s[len - 2] == '\r' || s[len - 2] == '\n')
        s[len - 2] = '\0';
  }

  return ret;
}


code:
/* MegaZeux
 *
 * Copyright (C) 2004 Alistair Strachan <alistair@devzero.co.uk>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#ifndef __FSAFEOPEN_H
#define __FSAFEOPEN_H

#include "compat.h"

#include <stdio.h>

__M_BEGIN_DECLS

enum
{
  FSAFE_SUCCESS = 0,
  FSAFE_MATCHED_DIRECTORY,
  FSAFE_MATCH_FAILED,
  FSAFE_BRUTE_FORCE_FAILED,
  FSAFE_INVALID_ARGUMENT,
  FSAFE_ABSOLUTE_PATH_ERROR,
  FSAFE_WINDOWS_DRIVE_LETTER_ERROR,
  FSAFE_PARENT_DIRECTORY_ERROR
};

CORE_LIBSPEC char *fsafegets(char *s, int size, FILE *stream);

int fsafetranslate(const char *path, char *newpath);
FILE *fsafeopen(const char *path, const char *mode);

__M_END_DECLS

#endif // __FSAFEOPEN_H