1/*
2 * Copyright 2019, Andrew Lindesay <apl@lindesay.co.nz>.
3 * All rights reserved. Distributed under the terms of the MIT License.
4 */
5
6#include "ValidationUtils.h"
7
8#include <ctype.h>
9
10#include <UnicodeChar.h>
11
12
13#define MIN_LENGTH_NICKNAME			4
14#define MAX_LENGTH_NICKNAME			32
15
16#define MIN_LENGTH_PASSWORD_CLEAR	8
17#define MIN_UPPER_PASSWORD_CLEAR	1
18#define MIN_DIGITS_PASSWORD_CLEAR	2
19
20/*! 1 if the character would be suitable for use in an email address mailbox
21    or domain part.
22*/
23
24static int
25hd_is_email_domain_or_mailbox_part(int c)
26{
27	if (0 == isspace(c) && c != 0x40)
28		return 1;
29	return 0;
30}
31
32
33/*! Returns true if the entire string is lower case alpha numeric.
34 */
35
36static int
37hd_is_lower_alnum(int c)
38{
39	if ((c >= 0x30 && c <= 0x39) || (c >= 0x60 && c <= 0x7a))
40		return 1;
41	return 0;
42}
43
44
45static bool
46hd_str_all_matches_fn(const BString& string, int (*hd_match_c)(int c))
47{
48	const char* c = string.String();
49	for (int32 i = 0; i < string.CountChars(); i++) {
50		if (0 == hd_match_c(c[i]))
51			return false;
52	}
53
54	return true;
55}
56
57
58static int32
59hd_str_count_upper_case(const BString& string)
60{
61	int32 upperCaseLetters = 0;
62	const char* c = string.String();
63	for (int32 i = 0; i < string.CountChars(); i++) {
64		uint32 unicodeChar = BUnicodeChar::FromUTF8(&c);
65		if (BUnicodeChar::IsUpper(unicodeChar))
66			upperCaseLetters++;
67	}
68	return upperCaseLetters;
69}
70
71
72static int32
73hd_str_count_digit(const BString& string)
74{
75	int32 digits = 0;
76	const char* c = string.String();
77	for (int32 i = 0; i < string.CountChars(); i++) {
78		uint32 unicodeChar = BUnicodeChar::FromUTF8(&c);
79		if (BUnicodeChar::IsDigit(unicodeChar))
80			digits++;
81	}
82	return digits;
83}
84
85
86/*static*/ bool
87ValidationUtils::IsValidNickname(const BString& value)
88{
89	return hd_str_all_matches_fn(value, &hd_is_lower_alnum)
90		&& value.CountChars() >= MIN_LENGTH_NICKNAME
91		&& value.CountChars() <= MAX_LENGTH_NICKNAME;
92}
93
94
95/*! Email addresses are quite difficult to validate 100% correctly so go fairly
96    light on the enforcement here; it should be a string with an '@' symbol,
97    something either side of the '@' and there should be no whitespace.
98*/
99
100/*static*/ bool
101ValidationUtils::IsValidEmail(const BString& value)
102{
103	const char* c = value.String();
104	size_t len = strlen(c);
105	bool foundAt = false;
106
107	for (size_t i = 0; i < len; i++) {
108		if (c[i] == 0x40 && !foundAt) {
109			if (i == 0 || i == len - 1)
110				return false;
111			foundAt = true;
112		}
113		else {
114			if (0 == hd_is_email_domain_or_mailbox_part(c[i]))
115				return false;
116		}
117	}
118
119	return foundAt;
120}
121
122
123// TODO: needs to reflect the data from the server rather than be hard coded
124/*static*/ bool
125ValidationUtils::IsValidPasswordClear(const BString& value)
126{
127	return value.Length() >= MIN_LENGTH_PASSWORD_CLEAR
128		&& hd_str_count_digit(value) >= MIN_DIGITS_PASSWORD_CLEAR
129		&& hd_str_count_upper_case(value) >= MIN_UPPER_PASSWORD_CLEAR;
130}
131
132