Update authors in README
[coquelicot.git] / HACKING
1 Development notes
2 =================
3
4 [Coquelicot] is written in Ruby and should be quite easy to improve for
5 anyone a little bit familiar with the Sinatra web framework. It is
6 mostly written using Behaviour Driven Development, making the test suite
7 a fine net to hack in confidence. So please go ahead!
8
9 [Coquelicot]: https://coquelicot.potager.org/
10
11 Setup a work environment
12 ------------------------
13
14 As Coquelicot uses Bundle, the first step to work on Coquelicot
15 after cloning its Git repository is installing the proper dependencies by
16 issuing:
17
18     bundle install
19
20 Basic operations
21 ----------------
22
23 Coquelicot test suite is written using RSpec. Running the test suite is
24 just a matter of typing:
25
26     bundle exec rspec
27
28 Running a test server can be done with:
29
30     bundle exec coquelicot start --no-daemon
31
32 To update the translation source files, use:
33
34     bundle exec rake gettext:po:update
35
36 This will update `po/coquelicot.pot` and merge the new strings in the various
37 `po/*/coquelicot.po` files.
38
39 Authentication mechanisms
40 -------------------------
41
42 The authentication part of Coquelicot has been made modular. Adding a
43 new authentication mechanism should be fairly straightforward.
44
45 A new authentication mechanism needs to provide the following 3 files,
46 with the following responsabilities:
47
48  * `lib/coquelicot/auth/<METHOD>.rb`:
49
50    A class implementing the actual authentication. This class must
51    implement an `authenticate` method. It will receive the form fields
52    as usual (params). This method must return true if upload
53    should be allowed.
54
55  * `public/javascripts/coquelicot.auth.<METHOD>.js:`
56
57     This file must define 'authentication' as an object with the
58     following methods:
59
60     - `getData()`: return an object of all the necessary data
61       to authenticate on the app side. Keys must have the same name
62       as the input fields used to authenticate without JavaScript.
63     - `focus()`: set the focus on the first authentication form field.
64     - (optional) `handleSuccess()`: arbitrary action upon successful
65       authentication. This is called after the livebox with
66       authentication fields is closed.
67     - (optional) `handleReject()`: arbitrary action when access
68       is denied. One can reset authentication fields after a failed
69       authentication.
70     - (optional) `handleFailure()`: arbitrary action when there was
71       a problem in the authentication procedure.
72
73  * `views/auth/<METHOD>.haml`:
74
75    A template with the necessary form fields that will be used for
76    authentication.
77
78 The authentication mechanism is set in the configuration file and
79 can include options specific to the method chosen.
80
81 Implementation details
82 ----------------------
83
84 Common application code lies in `Coquelicot::Application`, except for
85 one specific (and important) type of requests, namely `POST /update`.
86 These requests are handled directly at bare Rack level by
87 `Coquelicot::Rack::Upload`.
88
89 This allows to work directly with POST data as the browser is sending
90 it, so we can directly stream the uploaded file to our encrypted
91 on-disk containers.
92
93 The POST data must be in a very specific order, as we need to handle
94 authentication and other option fields before we start recording the
95 file content. Thanks to the W3C, the [HTML specification] states that
96 parts of the POST data must be delivered in the same order as the
97 controls appear in the `<form/>` container.
98
99 `Coquelicot::Rack::Multipart` exposes a simple DSL to parse the fields
100 as they are delivered. The later is used by `Coquelicot::Rack::Upload`
101 to perform its logic pretty nicely.
102
103 [HTML specification]: http://www.w3.org/TR/html4/interact/forms.html
104
105 Watch for buffered inputs!
106 --------------------------
107
108 Coquelicot is written in Ruby using Sinatra. Sinatra is based on the
109 Rack webserver interface. Rack specification mandates that applications
110 must be able to seek and rewind freely in the request content.
111
112 Request data is always received as a stream through the network. So in
113 order to comply with the specification, webservers implementing Rack
114 either buffer the input in memory (Webrick) or in a temporary file
115 (Thin, Passenger or Mongrel).
116
117 On top of that, when parsing `multipart/form-data` POST content,
118 `Rack::Request` (used by Sinatra) creates a new temporary file for
119 each files in the POST request.
120
121 For the specific needs of Coquelicot, these behaviours prevent users
122 from uploading large files (if `/tmp` is in memory) or breach their
123 privacy by writing a clear text version to disk.
124
125 To overcome these limitations, Coquelicot first uses a specific feature
126 of the Rainbows! webserver of streaming its input directly to
127 applications, and second bypasses `Rack::Request` to directly handle
128 POST content. Usage of any other Rack webserver is strongly discouraged
129 and should be restricted to development and testing.
130
131 Storage details
132 ---------------
133
134 Files are stored in the directory specified by the 'depot_path' setting.
135 One file in Coquelicot is actually stored in two files: one for metadata and
136 one for the file content.
137
138 ### Metadata file
139
140 The format is the following:
141
142     --- 
143     Coquelicot: "2.0"
144     Salt: <8 bytes stored as Base64>
145     Expire-at: <expiration time in seconds since epoch>
146     --- 
147     <encrypted metadata>
148
149 Encryption is done using OpenSSL. Cipher is AES-256-CBC with key and IV
150 created using the `pbkdf2_hmac_sha1()` implementation of PKCS5. The later
151 is fed using the former *Salt* and the given passphrase, using 2000
152 iterations.
153
154 Once decrypted, the metadata have the following format:
155
156     --- 
157     Created-at: <upload time in seconds since epoch>
158     Filename: "<original file name>"
159     Content-Type: "<MIME type>"
160     Length: <content length is bytes>
161     One-time-only: <true|false>
162
163 Headers must be valid YAML.
164
165 ### Content file
166
167 The content file contains the stored file in encrypted form. Encryption is done
168 with the same algorithm and keys as the encrypted metadata (see above).
169
170 The file name of the content file is the same as the one for metada, with an
171 added suffix of '.content'. For example, if the metadata file name is
172 `mqeb4pfcru2ymq3e6se7`, the associated content file will be
173 `mqeb4pfcru2ymq3e6se7.content`.
174
175 ### Expired files
176
177 Both the content file and the metadata file are truncated to zero length when
178 they are "expired".
179
180 ### URL mapping
181
182 In order to map download URLs to file name, a simple text file ".links"
183 is used. It contains a line for each file in the form:
184
185     <URL name> <metadata file name>
186
187 ### Changes history
188
189   version 2.0
190   :    Current version described above.
191
192   version 1.0
193   :    File content is in the same file as the metadata. Content is put in the
194        after the metadata and an extra "--- \n".
195
196 How to make a new release?
197 --------------------------
198
199  1. Bump version number in `lib/coquelicot/version.rb` and `Gemfile.lock`.
200     Don't forget to commit the changes.
201
202  2. Add a new entry in the NEWS file. For an outline:
203
204         git log --reverse --oneline $(git describe --abbrev=0)..
205
206     Don't forget to commit the changes.
207
208  3. Tag the release:
209
210         git tag -s coquelicot-$VERSION -m "coquelicot $VERSION"
211
212  4. Push changes to the main repository:
213
214         git push origin master coquelicot-$VERSION
215
216  5. Create a source tarball:
217
218         bundle exec rake create_archive
219
220  6. Sign it:
221
222         gpg --armor --detach-sign coquelicot-$VERSION.tar.gz
223
224  7. Switch to the website:
225
226         cd ../website
227
228  8. Move the source tarball and signature to the website:
229
230         mv ../git/coquelicot-$VERSION.tar.gz* static/dist/
231
232  9. Add them to the website repository:
233
234         git add static/dist/coquelicot-$VERSION.tar.gz*
235
236  10. Update the version on the website homepage:
237
238          sed -e "s/coquelicot-$PREVIOUS_VERSION/coquelicot-$VERSION/g" \
239              -i dynamic/index.md
240
241  11. Commit changes to the website.
242
243  12. Push the updated website:
244
245          make push
246          git push origin master
247
248  13. Announce the release on `coquelicot@potager.org` mailing-list.
249
250  14. Announce the release on `freecode.com`.