/*++

Copyright (c) 1991  Microsoft Corporation

Module Name:

    uilist.c

Abstract:

    Contains routine to convert a list of workstation names from UI/Service
    list format to API list format

    Contents:
        RtlConvertUiListToApiList
        (NextElement)
        (ValidateName)

Author:

    Richard L Firth (rfirth) 01-May-1992

Environment:

    User mode (makes Windows calls)

Revision History:

--*/

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <wchar.h>

//
// macros
//

#define IS_DELIMITER(c,_BlankOk) \
    (((c) == L' ' && (_BlankOk)) || \
    ((c) == L'\t') || ((c) == L',') || ((c) == L';'))


//
// prototypes
//

static
ULONG
NextElement(
    IN OUT PWSTR* InputBuffer,
    IN OUT PULONG InputBufferLength,
    OUT PWSTR OutputBuffer,
    IN ULONG OutputBufferLength,
    IN BOOLEAN BlankIsDelimiter
    );

static
BOOLEAN
ValidateName(
    IN  PWSTR Name,
    IN  ULONG Length
    );

//
// functions
//

NTSTATUS
RtlConvertUiListToApiList(
    IN  PUNICODE_STRING UiList OPTIONAL,
    OUT PUNICODE_STRING ApiList,
    IN BOOLEAN BlankIsDelimiter
    )

/*++

Routine Description:

    Converts a list of workstation names in UI/Service format into a list of
    canonicalized names in API list format. UI/Service list format allows
    multiple delimiters, leading and trailing delimiters. Delimiters are the
    set "\t,;". API list format has no leading or trailing delimiters and
    elements are delimited by a single comma character.

    For each name parsed from UiList, the name is canonicalized (which checks
    the character set and name length) as a workstation name. If this fails,
    an error is returned. No information is returned as to which element
    failed canonicalization: the list should be discarded and a new one re-input

Arguments:

    UiList  - The list to canonicalize in UI/Service list format
    ApiList - The place to store the canonicalized version of the list in
              API list format.  The list will have a trailing zero character.
    BlankIsDelimiter - TRUE indicates blank should be considered a delimiter
              character.

Return Value:

    NTSTATUS
        Success = STATUS_SUCCESS
                    List converted ok

        Failure = STATUS_INVALID_PARAMETER
                    UiList parameter is in error

                  STATUS_INVALID_COMPUTER_NAME
                    A name parsed from UiList has an incorrect format for a
                    computer (aka workstation) name
--*/

{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG inLen;
    PWSTR input;
    PWSTR buffer;
    PWSTR output;
    ULONG cLen;
    ULONG len;
    ULONG outLen = 0;
    WCHAR element[MAX_COMPUTERNAME_LENGTH+1];
    BOOLEAN firstElement = TRUE;
    BOOLEAN ok;

    try {
        if (ARGUMENT_PRESENT(UiList)) {
            inLen = UiList->MaximumLength;  // read memory test
            inLen = UiList->Length;
            input = UiList->Buffer;
            if (inLen & sizeof(WCHAR)-1) {
                status = STATUS_INVALID_PARAMETER;
            }
        }
        RtlInitUnicodeString(ApiList, NULL);
    } except (EXCEPTION_EXECUTE_HANDLER) {
        status = STATUS_ACCESS_VIOLATION;
    }
    if (NT_SUCCESS(status) && ARGUMENT_PRESENT(UiList) && inLen) {
        buffer = RtlAllocateHeap(RtlProcessHeap(), 0, inLen + sizeof(WCHAR));
        if (buffer == NULL) {
            status = STATUS_NO_MEMORY;
        } else {
            ApiList->Buffer = buffer;
            ApiList->MaximumLength = (USHORT)inLen + sizeof(WCHAR);
            output = buffer;
            ok = TRUE;
            while (len = NextElement(&input,
                                     &inLen,
                                     element,
                                     sizeof(element) - sizeof(element[0]),
                                     BlankIsDelimiter )) {
                if (len == (ULONG)-1L) {
                    ok = FALSE;
                } else {
                    cLen = len/sizeof(WCHAR);
                    element[cLen] = 0;
                    ok = ValidateName(element, cLen);
                }
                if (ok) {
                    if (!firstElement) {
                        *output++ = L',';

                        outLen += sizeof(WCHAR);
                    } else {
                        firstElement = FALSE;
                    }
                    wcscpy(output, element);
                    outLen += len;
                    output += cLen;
                } else {
                    RtlFreeHeap(RtlProcessHeap(), 0, buffer);
                    ApiList->Buffer = NULL;
                    status = STATUS_INVALID_COMPUTER_NAME;
                    break;
                }
            }
        }
        if (NT_SUCCESS(status)) {
            ApiList->Length = (USHORT)outLen;
            if (!outLen) {
                ApiList->MaximumLength = 0;
                ApiList->Buffer = NULL;
                RtlFreeHeap(RtlProcessHeap(), 0, buffer);
            }
        }
    }
    return status;
}

static
ULONG
NextElement(
    IN OUT PWSTR* InputBuffer,
    IN OUT PULONG InputBufferLength,
    OUT PWSTR OutputBuffer,
    IN ULONG OutputBufferLength,
    IN BOOLEAN BlankIsDelimiter
    )

/*++

Routine Description:

    Locates the next (non-delimter) element in a string and extracts it to a
    buffer. Delimiters are the set [\t,;]

Arguments:

    InputBuffer         - pointer to pointer to input buffer including delimiters
                          Updated on successful return
    InputBufferLength   - pointer to length of characters in InputBuffer.
                          Updated on successful return
    OutputBuffer        - pointer to buffer where next element is copied
    OutputBufferLength  - size of OutputBuffer (in bytes)
    BlankIsDelimiter    - TRUE indicates blank should be considered a delimiter
              character.

Return Value:

    ULONG
                           -1 = error - extracted element breaks OutputBuffer
                            0 = no element extracted (buffer is empty or all
                                delimiters)
        1..OutputBufferLength = OutputBuffer contains extracted element

--*/

{
    ULONG elementLength = 0;
    ULONG inputLength = *InputBufferLength;
    PWSTR input = *InputBuffer;

    while (IS_DELIMITER(*input, BlankIsDelimiter) && inputLength) {
        ++input;
        inputLength -= sizeof(*input);
    }
    while (!IS_DELIMITER(*input, BlankIsDelimiter) && inputLength) {
        if (!OutputBufferLength) {
            return (ULONG)-1L;
        }
        *OutputBuffer++ = *input++;
        OutputBufferLength -= sizeof(*input);
        elementLength += sizeof(*input);
        inputLength -= sizeof(*input);
    }
    *InputBuffer = input;
    *InputBufferLength = inputLength;
    return elementLength;
}

//
// Illegal names characters same as those in net\api. Move to common
// include directory
//

#define ILLEGAL_NAME_CHARS      L"\001\002\003\004\005\006\007" \
                            L"\010\011\012\013\014\015\016\017" \
                            L"\020\021\022\023\024\025\026\027" \
                            L"\030\031\032\033\034\035\036\037" \
                            L"\"/\\[]:|<>+=;,?*"

static
BOOLEAN
ValidateName(
    IN  PWSTR Name,
    IN  ULONG Length
    )

/*++

Routine Description:

    Determines whether a computer name is valid or not

Arguments:

    Name    - pointer to zero terminated wide-character computer name
    Length  - of Name in characters, excluding zero-terminator

Return Value:

    BOOLEAN
        TRUE    Name is valid computer name
        FALSE   Name is not valid computer name

--*/

{
    if (Length > MAX_COMPUTERNAME_LENGTH || Length < 1) {
        return FALSE;
    }

    //
    // Don't allow leading or trailing blanks in the computername.
    //

    if ( Name[0] == ' ' || Name[Length-1] == ' ' ) {
        return(FALSE);
    }

    return (BOOLEAN)((ULONG)wcscspn(Name, ILLEGAL_NAME_CHARS) == Length);
}
