/*
 * Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

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

#include "jni.h"
#include "jvm.h"

typedef unsigned short unicode;

static char *
skip_over_fieldname(char *name, jboolean slash_okay,
                    unsigned int len);
static char *
skip_over_field_signature(char *name, jboolean void_okay,
                          unsigned int len);

/*
 * Return non-zero if the character is a valid in JVM class name, zero
 * otherwise.  The only characters currently disallowed from JVM class
 * names are given in the table below:
 *
 * Character    Hex     Decimal
 * '.'          0x2e    46
 * '/'          0x2f    47
 * ';'          0x3b    59
 * '['          0x5b    91
 *
 * (Method names have further restrictions dealing with the '<' and
 * '>' characters.)
 */
static int isJvmIdentifier(unicode ch) {
  if( ch > 91 || ch < 46 )
    return 1;   /* Lowercase ASCII letters are > 91 */
  else { /* 46 <= ch <= 91 */
    if (ch <= 90 && ch >= 60) {
      return 1; /* Uppercase ASCII recognized here */
    } else { /* ch == 91 || 46 <= ch <= 59 */
      if (ch == 91 || ch == 59 || ch <= 47)
        return 0;
      else
        return 1;
    }
  }
}

static unicode
next_utf2unicode(char **utfstring_ptr, int * valid)
{
    unsigned char *ptr = (unsigned char *)(*utfstring_ptr);
    unsigned char ch, ch2, ch3;
    int length = 1;             /* default length */
    unicode result = 0x80;      /* default bad result; */
    *valid = 1;
    switch ((ch = ptr[0]) >> 4) {
        default:
            result = ch;
            break;

        case 0x8: case 0x9: case 0xA: case 0xB: case 0xF:
            /* Shouldn't happen. */
            *valid = 0;
            break;

        case 0xC: case 0xD:
            /* 110xxxxx  10xxxxxx */
            if (((ch2 = ptr[1]) & 0xC0) == 0x80) {
                unsigned char high_five = ch & 0x1F;
                unsigned char low_six = ch2 & 0x3F;
                result = (high_five << 6) + low_six;
                length = 2;
            }
            break;

        case 0xE:
            /* 1110xxxx 10xxxxxx 10xxxxxx */
            if (((ch2 = ptr[1]) & 0xC0) == 0x80) {
                if (((ch3 = ptr[2]) & 0xC0) == 0x80) {
                    unsigned char high_four = ch & 0x0f;
                    unsigned char mid_six = ch2 & 0x3f;
                    unsigned char low_six = ch3 & 0x3f;
                    result = (((high_four << 6) + mid_six) << 6) + low_six;
                    length = 3;
                } else {
                    length = 2;
                }
            }
            break;
        } /* end of switch */

    *utfstring_ptr = (char *)(ptr + length);
    return result;
}

/* Take pointer to a string.  Skip over the longest part of the string that
 * could be taken as a fieldname.  Allow '/' if slash_okay is JNI_TRUE.
 *
 * Return a pointer to just past the fieldname.  Return NULL if no fieldname
 * at all was found, or in the case of slash_okay being true, we saw
 * consecutive slashes (meaning we were looking for a qualified path but
 * found something that was badly-formed).
 */
static char *
skip_over_fieldname(char *name, jboolean slash_okay,
                    unsigned int length)
{
    char *p;
    unicode ch;
    unicode last_ch = 0;
    int valid = 1;
    /* last_ch == 0 implies we are looking at the first char. */
    for (p = name; p != name + length; last_ch = ch) {
        char *old_p = p;
        ch = *p;
        if (ch < 128) {
            p++;
            if (isJvmIdentifier(ch)) {
                continue;
            }
        } else {
            char *tmp_p = p;
            ch = next_utf2unicode(&tmp_p, &valid);
            if (valid == 0)
              return 0;
            p = tmp_p;
            if (isJvmIdentifier(ch)) {
                        continue;
            }
        }

        if (slash_okay && ch == '/' && last_ch) {
            if (last_ch == '/') {
                return 0;       /* Don't permit consecutive slashes */
            }
        } else if (ch == '_' || ch == '$') {
        } else {
            return last_ch ? old_p : 0;
        }
    }
    return last_ch ? p : 0;
}

/* Take pointer to a string.  Skip over the longest part of the string that
 * could be taken as a field signature.  Allow "void" if void_okay.
 *
 * Return a pointer to just past the signature.  Return NULL if no legal
 * signature is found.
 */

static char *
skip_over_field_signature(char *name, jboolean void_okay,
                          unsigned int length)
{
    unsigned int array_dim = 0;
    for (;length > 0;) {
        switch (name[0]) {
            case JVM_SIGNATURE_VOID:
                if (!void_okay) return 0;
                /* FALL THROUGH */
            case JVM_SIGNATURE_BOOLEAN:
            case JVM_SIGNATURE_BYTE:
            case JVM_SIGNATURE_CHAR:
            case JVM_SIGNATURE_SHORT:
            case JVM_SIGNATURE_INT:
            case JVM_SIGNATURE_FLOAT:
            case JVM_SIGNATURE_LONG:
            case JVM_SIGNATURE_DOUBLE:
                return name + 1;

            case JVM_SIGNATURE_CLASS: {
                /* Skip over the classname, if one is there. */
                char *p =
                    skip_over_fieldname(name + 1, JNI_TRUE, --length);
                /* The next character better be a semicolon. */
                if (p && p - name - 1 > 0 && p[0] == ';')
                    return p + 1;
                return 0;
            }

            case JVM_SIGNATURE_ARRAY:
                array_dim++;
                /* JVMS 2nd ed. 4.10 */
                /*   The number of dimensions in an array is limited to 255 ... */
                if (array_dim > 255) {
                    return 0;
                }
                /* The rest of what's there better be a legal signature.  */
                name++;
                length--;
                void_okay = JNI_FALSE;
                break;

            default:
                return 0;
        }
    }
    return 0;
}


/* Used in java/lang/Class.c */
/* Determine if the specified name is legal
 * UTF name for a classname.
 *
 * Note that this routine expects the internal form of qualified classes:
 * the dots should have been replaced by slashes.
 */
JNIEXPORT jboolean
VerifyClassname(char *name, jboolean allowArrayClass)
{
    unsigned int length = strlen(name);
    char *p;

    if (length > 0 && name[0] == JVM_SIGNATURE_ARRAY) {
        if (!allowArrayClass) {
            return JNI_FALSE;
        } else {
            /* Everything that's left better be a field signature */
            p = skip_over_field_signature(name, JNI_FALSE, length);
        }
    } else {
        /* skip over the fieldname.  Slashes are okay */
        p = skip_over_fieldname(name, JNI_TRUE, length);
    }
    return (p != 0 && p - name == (ptrdiff_t)length);
}

/*
 * Translates '.' to '/'.  Returns JNI_TRUE is any / were present.
 */
JNIEXPORT jboolean
VerifyFixClassname(char *name)
{
    char *p = name;
    jboolean slashesFound = JNI_FALSE;
    int valid = 1;

    while (valid != 0 && *p != '\0') {
        if (*p == '/') {
            slashesFound = JNI_TRUE;
            p++;
        } else if (*p == '.') {
            *p++ = '/';
        } else {
            next_utf2unicode(&p, &valid);
        }
    }

    return slashesFound && valid != 0;
}
