19 Development
rugk edited this page 6 years ago

This fork of the original PrivateBin of Sebsauvage was refactored into an object oriented MVC structure. The index.php only loads an autoloader (lib/auto.php) and starts the application by instancing a privatebin object.

Code Style

To make it easy for you to adopt our code style, we provide you some configuration files for different tools. You just have to install the necessary tools in your IDE and they should use our configuration files automatically.

Structure

The main code or controller is found in lib/PrivateBin.php.

The data storage abstractions are in the lib/Data/ folder.

The models are in the lib/Model/ folder.

The view is in lib/View.php and uses PHP template files located in the tpl/ folder.

Any option that not every server administrator might want to use or provide should be implemented with configuration options in the file cfg/conf.ini, default settings are defined in lib/Configuration.php.

More details can be found in the API documentation.

Unit Tests

PHP

For unit tests of the php code the folder tst/ can be used. Apart from helping to implement new features correctly it helps to keep regressions at bay. By default a code coverage report is generated after phpunit is run, the one of the latest release is publicly available.

In order to run these tests, you will need to install the following packages and their dependencies:

  • phpunit
  • php-gd
  • php-sqlite3
  • php-xdebug (for code coverage reports)

Example for Debian and Ubuntu:

$ sudo apt install phpunit php-gd php-sqlite php-xdebug

To run the tests, change into the tst directory and run phpunit:

$ cd PrivateBin/tst
$ phpunit

Additionally there is the ConfigurationTestGenerator. Based on the configurations defined in its constructor, it generates the unit test file tst/ConfigurationCombinationsTest.php, containing all possible combinations of these configurations and tests for (most of the) valid combinations. Some of combinations can't be tested with this method, i.e. a valid option combined with an invalid one. Other very specific test cases (i.e. to trigger multiple errors) are covered in tst/PrivateBinTest.php. Here is how to generate the configuration test and run it:

$ cd PrivateBin/tst
$ php ConfigurationTestGenerator.php
$ phpunit ConfigurationCombinationsTest.php

Note that it can take an hour or longer to run the several thousand tests.

JavaScript

For unit tests of the JS code the folder js/ can be used. Apart from helping to implement new features correctly it helps to keep regressions at bay. By default a code coverage report is generated after istanbul is run.

In order to run these tests, you will need to install the following packages and its dependencies:

  • npm

Then you can use the node package manager to install the latest stable release of mocha and istanbul (for code coverage reports) globally and jsVerify, jsdom and jsdom-global locally:

$ npm install -g mocha istanbul
$ cd PrivateBin/js
$ npm install jsverify jsdom jsdom-global

Example for Debian and Ubuntu, including steps to allow current user to install node modules globally:

$ sudo apt install npm
$ sudo mkdir /usr/local/lib/node_modules
$ sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}
$ ln -s /usr/bin/nodejs /usr/local/bin/node
$ npm install -g mocha istanbul
$ cd PrivateBin/js
$ npm install jsverify jsdom jsdom-global

To run the tests, just change into the js directory and run istanbul:

$ cd PrivateBin/js
$ istanbul cover _mocha

Property based unit testing

In the JavaScript unit tests we use the JSVerify library to leverage property based unit testing. Instead of artificially creating specific test cases to cover all relevant paths of the tested code (with the generated coverage reports providing means to check the tested paths), property based testing allows us to describe the patterns of data that are valid input.

With each run of the tests, for each jsc.property 100 random inputs are generated and tested. For example we tell the test to generate random strings, which will include empty strings, numeric strings, long strings, unicode sequences, etc. This is great for finding corner cases that one might not think of when explicitly writing one test case at a time.

There is another benefit, too: When an error is found, JSVerify will try to find the smallest, still failing test case for you and print this out including the associated random number generator (RNG) state, so you can reproduce it easily:

[...]

  30 passing (3s)
  1 failing

  1) Helper getCookie returns the requested cookie:
     Error: Failed after 30 tests and 11 shrinks. rngState: 88caf85079d32e416b; Counterexample: ["{", "9", "9", "YD8%fT"]; [" ", "_|K:"];

[...]

Of course it may just be that you need to adjust a test case if the random pattern generated is ambiguous. In the above example the cookie string would contain two identical keys "9", something that may not be valid, but that our code could encounter and needs to be able to handle.

After you adjusted the code of the library or the test you can rerun the test with the same RNG state as follows:

$ istanbul cover _mocha -- test.js --jsverifyRngState 88caf85079d32e416b

Creating new config options

  1. Add your config option to cfg/conf.sample.php.
  2. Add the default value for that config option to lib/Configuration.php in the $_defaults array.
  3. If you want to use the config option in a template, you have to add it to the _view function in lib/PrivateBin.php. You can then just use the variable in the template file to conditionally include or exclude HTML code.
  4. If you want to use it in other parts of the PHP code, you can usually query the config option via the getKey method of the Configuration object.

A good example of a change, where an option was added is this one.

Data Model

If you want to create your own data models, you might want to know how the arrays, that you have to store, look like:

public function create($pasteid, $paste)
{
    $pasteid = substr(hash('md5', $paste['data']), 0, 16);

    $paste['data']                      // text
    $paste['meta']['postdate']          // int UNIX timestamp
    $paste['meta']['expire_date']       // int UNIX timestamp
    $paste['meta']['opendiscussion']    // true (if false it is unset)
    $paste['meta']['burnafterreading']  // true (if false it is unset; if true, then opendiscussion is unset)
    $paste['meta']['formatter']         // string
    $paste['meta']['attachment']        // text
    $paste['meta']['attachmentname']    // text
}

public function createComment($pasteid, $parentid, $commentid, $comment)
{
    $pasteid  // the id of the paste this comment belongs to
    $parentid // the id of the parent of this comment, may be the paste id itself
    $commentid = substr(hash('md5', $paste['data']), 0, 16);

    $comment['data']                    // text
    $comment['meta']['nickname']        // text or null (if anonymous)
    $comment['meta']['vizhash']         // text or null (if anonymous)
    $comment['meta']['postdate']        // int UNIX timestamp
}

Composer

If you need to execute composer, because you e.g. want to include or update the dependencies of PrivateBin, please run this command:

composer install --no-dev --optimize-autoloader

Note that the .gitignore will exclude all non-PHP files in the vendor dir when you create a commit.

Subresource Integrity for Javascript resources

Because we implemented Subresource Integrity (SRI) the SRI hashes in the templates need to be regenerated after each change of a javascript file. To make this easy, we implemented a mechanism for this into the unit testing bootstrap process. After changing any javascript file, follow this process:

  • run the unit tests as described above
  • deploy the updated javascript file(s) and the updated templates on your webserver

Of course you also manually adjust the hashes in the template files. For more information how to do this have a look at this article or e.g. use some online generator.