So far we have written code to create contribution objects. However, we are not done yet if we want to create reliable and secure objects because:
We stated initially that the random value used for an arc's radius can vary between 1 and 15. However, attribute random_number is currently of type positive32 which means that there is no protection against assigning values greater than 15.
Attributes first_name and last_name are both of type string. Besides allowing for nonsense names like #!314$ this opens the door for invalid and malicious data. For example too long names can lead to buffer overflow problems and attacks, and the acceptance of invalid characters in the names (such as < and >) can lead to SQL injection and cross-site scripting attacks.
![]() | Note |
|---|---|
| Preventing bad data to be stored in objects is one of the most effective ways to construct reliable and secure software. According to the Open Web Application Security Project (OWASP), injection and cross-site-scripting are the two top items in their list of Top ten security risks |
Luckily Obix makes it easy to define specific types that protect against invalid data.
A solution for attributes first_name and last_name is to define a new type that holds a string composed only of letters and spaces, and limited to a length of 40 characters. Let's call this type user_name. Create it like this:
Create a new source code file named ty_user_name.osc in subdirectory contribution.
Insert the following code:
type user_name default_factory:yes
inherit simple_non_empty_string
attribute value and_check:i_value.matches_regex ( fa_regex.create ( "^[a-zA-Z ]{1,40}$"~ ) ) end
end
endIn the above code we use several techniques:
Type inheritance is used to inherit from type simple_non_empty_string which is defined in Obix's standard library. As the name suggests, simple_non_empty_string is a string that cannot be empty.
We then use a combination of Feature redefinition in child types and Contract Programming (Design by Contract) to specify a new validation rule for type user_name. Besides requiring that the string value cannot be empty the value must also match the regular expression ^[a-zA-Z ]{1,40}$ in order to guarantee that a user name only contains letters and spaces, and doesn't exceed 40 characters.
The default_factory:yes clause relieves you from explicitly defining a factory for type ty_user_name. The compiler will create a default factory with identifier fa_user_name and a creator named create which you can use to create user_name objects.
![]() | Note |
|---|---|
| This chapter and the following one introduce a number of techniques which are unique to Obix. Please note that the details of these techniques are not fully explained in this tutorial. The aim is to give you a quick overview about the techniques available in Obix, without digging too deep. Please refer to the links in the text, the language manual, the API documention and the system source code if you need more information, or post a message. |
Types that hold string values restricted by a regular expression are very common. Just think of things like a telephone number, an email adress, an ISBN number, etc. Therefore Obix's standard library contains a source code template you can use in order to minimize the code to type and to avoid code duplication. The template that shortens our code is named non_empty_regex_string_type. Its source code is defined as follows:
template non_empty_regex_string_type
param type_suffix end
param regex end
type {type_suffix} default_factory:yes
inherit simple_non_empty_string
attribute value
and_check
script
const regex regex = fa_regex.create ( "{regex}"~ )
check i_value.matches_regex ( c_regex ) &
error_message: """'{{i_value.to_string}}' is invalid because it doesn't match the regular expression '{{c_regex}}'""" &
error_id: matches_regex_violation
end
end
end
end
end
endThis template has two parameters:
type_suffix is the type suffix of the type we want to create
regex is the regular expression to apply in the type
To use this template replace the content of file ty_user_name.osc with:
%non_empty_regex_string_type < &
type_suffix: "user_name" &
regex: "^[a-zA-Z ]{1,40}$" >What remains to be done is to change the type of attributes first_name and last_name in type contribution from string to user_name.
Replace the old code:
attribute first_name type:string end attribute last_name type:string end
with this one:
attribute first_name type:user_name end attribute last_name type:user_name end
Remark: Don't re-compile now. Wait until the end of this chapter.
We can now use the same technique to restrict attribute random_number to values between 1 and 15:
Create a new source code file named ty_random_1_15.osc in subdirectory contribution.
Insert the following code:
type random_1_15
inherit simple_positive32
attribute value and_check: i_value <= 15~ error_message: "Value must be between 1 and 15" end
end
endTypes that hold positive integer numbers limited to a maximum value are also very common. Therefore you can again use a template in Obix's standard library in order to simplify the code and avoid code duplication. Replace the above code with:
%positive32_with_maximum_type < & type_suffix: "random_1_15" & max_value: "15" >
Finally you have to change the declaration of attribute random_number in type contribution.
The old code:
attribute random_number type:positive32 end
becomes:
attribute random_number type:random_1_15 end
Because we have now changed the types of 3 attributes in type contribution we have to adapt the test script in factory contribution. The type of attribute first_name, for example, changed from string to user_name. Therefore we can't assign anymore a string literal (like "Albert") to first_name. Static typing requires us to use an object of type user_name, and such an object can be created with the syntax fa_user_name.create ( "Albert"~ ). Please note the ~ symbol in this expression. Writing "Albert" denotes a string literal, whereas "Albert"~ denotes a string_value literal.
The code below shows the adapted code of the factory's test script. Please note the changes made for attributes first_name, last_name and random_number and adapt your source code file:
test
script
// create a 'contribution' object and assign it to constant 'c'
const contribution c = fa_contribution.create ( &
identifier = 123 &
first_name = fa_user_name.create ( "Albert"~ ) &
last_name = fa_user_name.create ( "Newton"~ ) &
random_number = fa_random_1_15.create ( 5~ ) &
date_time = fa_local_date_time.create ( "2011-08-27T16:16:30"~ ) )
// verify all attributes
verify c.identifier =v 123
verify c.first_name.value =v "Albert"~
verify c.last_name.value =v "Newton"~
verify c.random_number.value =v 5~
verify c.date_time.to_string =v "2011-08-27T16:16:30"
// verify the value returned by the 'to_string' command
verify c.to_string =v "Albert Newton contributed '5' on 2011-08-27T16:16:30"
end
end
To check that everything is still ok, compile and build your project, then execute run_tests.
You can now relax because the result of the above effort is reliable and secure code! Contribution objects can now only be created if all data are valid. Malicious data is rejected at runtime and inhibits the creation of objects. And once a valid object is created it remains in a valid state and nothing wrong can happen anymore because it is immutable. Therefore it can also be freely shared without any precautions to be taken, such as synchronization. It is prepared to be fearlessly used in multi-threading and multi-processor environments.
The increased type safety achieved through the addition of specific types for attributes first_name, last_name and random_number has also a nice side effect. The compiler now protects against bugs related to semantically incompatible assignments. Suppose, for example, that variable age_of_elephant of type positive32 exists in a script. Then a stupid assignment like
identifier = age_of_elephant
in an instruction that creates a contribution object is immediately rejected by the compiler. Although both values are integers, they are semantically incompatible and the Obix compiler is able to detect this.
Another benefit results from types user_name and random_1_15. Their conditions coded to validate data can be programmatically used to check untrusted data. For example, a first_name value coming from an external source can be checked with the following expression:
const attribute_check_error input_error = ty_user_name.value.check ( first_name_entered_in_web_page ) if input_error #r void then // ... end if
Server side code of a web application can use this technique to check data received in a HTTP request, without the need to duplicate the data validation code. We will use this technique in a later chapter.
Finally it is interesting to note that you can add specific features (behavior) to types defined in the above way. Suppose, for example, you create a type ISBN (which is a string restrained by a regular expression) in a bookstore application. You could then add command get_publisher_code to type ISBN in order to extract the publisher code of a book.
One last point we didn't consider yet is the requirement for unique, auto-incremented values for attribute identifier. We will take care about that later.